Merge branch 'main' of https://github.com/cake-tech/cake_wallet into CW-659-Confirm-Seeds-Display-Correctly

This commit is contained in:
Blazebrain 2024-09-11 05:14:17 +01:00
commit 5779d30a48
118 changed files with 2015 additions and 698 deletions

View file

@ -171,6 +171,10 @@ jobs:
echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart
echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
echo "const letsExchangeBearerToken = '${{ secrets.LETS_EXCHANGE_TOKEN }}';" >> lib/.secrets.g.dart
echo "const letsExchangeAffiliateId = '${{ secrets.LETS_EXCHANGE_AFFILIATE_ID }}';" >> lib/.secrets.g.dart
echo "const stealthExBearerToken = '${{ secrets.STEALTH_EX_BEARER_TOKEN }}';" >> lib/.secrets.g.dart
echo "const stealthExAdditionalFeePercent = '${{ secrets.STEALTH_EX_ADDITIONAL_FEE_PERCENT }}';" >> lib/.secrets.g.dart
- name: Rename app
run: |

View file

@ -154,6 +154,10 @@ jobs:
echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart
echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart
echo "const letsExchangeBearerToken = '${{ secrets.LETS_EXCHANGE_TOKEN }}';" >> lib/.secrets.g.dart
echo "const letsExchangeAffiliateId = '${{ secrets.LETS_EXCHANGE_AFFILIATE_ID }}';" >> lib/.secrets.g.dart
echo "const stealthExBearerToken = '${{ secrets.STEALTH_EX_BEARER_TOKEN }}';" >> lib/.secrets.g.dart
echo "const stealthExAdditionalFeePercent = '${{ secrets.STEALTH_EX_ADDITIONAL_FEE_PERCENT }}';" >> lib/.secrets.g.dart
- name: Rename app
run: |

View file

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M16 1.37854C16 0.764286 16.6636 0.379192 17.1969 0.68395L23 4L29.4961 7.71208C29.8077 7.89012 30 8.22147 30 8.58032V16L23.9923 12.567C23.3774 12.2157 22.6226 12.2157 22.0077 12.567L16 16V8V1.37854ZM2 16V8.58032C2 8.22147 2.19229 7.89012 2.50386 7.71208L8.00772 4.56702C8.62259 4.21566 9.37741 4.21566 9.99228 4.56702L16 8L2 16ZM16 30.6215C16 31.2357 15.3364 31.6208 14.8031 31.3161L9 28L2.50386 24.2879C2.19229 24.1099 2 23.7785 2 23.4197V16L8.00772 19.433C8.62259 19.7843 9.37741 19.7843 9.99228 19.433L16 16V24V30.6215ZM22.0077 27.433C22.6226 27.7843 23.3774 27.7843 23.9923 27.433L29.4961 24.2879C29.8077 24.1099 30 23.7785 30 23.4197V16L16 24L22.0077 27.433Z"
fill="#159DFF"></path>
</svg>

After

Width:  |  Height:  |  Size: 846 B

BIN
assets/images/stealthex.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

View file

@ -17,6 +17,3 @@
-
uri: node.community.rino.io:18081
is_default: false
-
uri: node.moneroworld.com:18089
is_default: false

View file

@ -1,3 +1,2 @@
Scan and verify messages
Synchronization enhancements
Bug fixes
Enhance auto-address generation for Monero
Bug fixes and enhancements

View file

@ -1,3 +1,4 @@
Scan and verify messages
Synchronization enhancements
Bug fixes
Enable BIP39 by default for wallet creation also on Bitcoin/Litecoin (Electrum seed type is still accessible through advanced settings page)
Improve fee calculation for Bitcoin to protect against overpaying or underpaying
Enhance auto-address generation for Monero
Bug fixes and enhancements

View file

@ -1,6 +1,7 @@
import 'dart:convert';
import 'dart:math';
import 'dart:typed_data';
import 'package:crypto/crypto.dart';
import 'package:cryptography/cryptography.dart' as cryptography;
import 'package:cw_core/sec_random_native.dart';
@ -59,11 +60,7 @@ void maskBytes(Uint8List bytes, int bits) {
}
}
String bufferToBin(Uint8List data) {
final q1 = data.map((e) => e.toRadixString(2).padLeft(8, '0'));
final q2 = q1.join('');
return q2;
}
String bufferToBin(Uint8List data) => data.map((e) => e.toRadixString(2).padLeft(8, '0')).join('');
String encode(Uint8List data) {
final dataBitLen = data.length * 8;
@ -112,17 +109,18 @@ Future<bool> checkIfMnemonicIsElectrum2(String mnemonic) async {
Future<String> getMnemonicHash(String mnemonic) async {
final hmacSha512 = Hmac(sha512, utf8.encode('Seed version'));
final digest = hmacSha512.convert(utf8.encode(normalizeText(mnemonic)));
final hx = digest.toString();
return hx;
return digest.toString();
}
Future<Uint8List> mnemonicToSeedBytes(String mnemonic, {String prefix = segwit}) async {
Future<Uint8List> mnemonicToSeedBytes(String mnemonic,
{String prefix = segwit, String passphrase = ''}) async {
final pbkdf2 =
cryptography.Pbkdf2(macAlgorithm: cryptography.Hmac.sha512(), iterations: 2048, bits: 512);
final text = normalizeText(mnemonic);
// pbkdf2.deriveKey(secretKey: secretKey, nonce: nonce)
final passphraseBytes = utf8.encode(normalizeText(passphrase));
final key = await pbkdf2.deriveKey(
secretKey: cryptography.SecretKey(text.codeUnits), nonce: 'electrum'.codeUnits);
secretKey: cryptography.SecretKey(text.codeUnits),
nonce: [...'electrum'.codeUnits, ...passphraseBytes]);
final bytes = await key.extractBytes();
return Uint8List.fromList(bytes);
}

View file

@ -7,5 +7,6 @@ class MnemonicBip39 {
static String generate({int strength = 128}) => bip39.generateMnemonic(strength: strength);
/// Create root seed from mnemonic
static Uint8List toSeed(String mnemonic) => bip39.mnemonicToSeed(mnemonic);
static Uint8List toSeed(String mnemonic, {String? passphrase}) =>
bip39.mnemonicToSeed(mnemonic, passphrase: passphrase ?? '');
}

View file

@ -115,7 +115,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
break;
case DerivationType.electrum:
default:
seedBytes = await mnemonicToSeedBytes(mnemonic);
seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? "");
break;
}
return BitcoinWallet(
@ -195,7 +195,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
if (mnemonic != null) {
switch (walletInfo.derivationInfo!.derivationType) {
case DerivationType.electrum:
seedBytes = await mnemonicToSeedBytes(mnemonic);
seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? "");
break;
case DerivationType.bip39:
default:

View file

@ -3,16 +3,18 @@ import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_info.dart';
class BitcoinNewWalletCredentials extends WalletCredentials {
BitcoinNewWalletCredentials(
{required String name,
WalletInfo? walletInfo,
String? password,
DerivationType? derivationType,
String? derivationPath})
: super(
BitcoinNewWalletCredentials({
required String name,
WalletInfo? walletInfo,
String? password,
DerivationType? derivationType,
String? derivationPath,
String? passphrase,
}) : super(
name: name,
walletInfo: walletInfo,
password: password,
passphrase: passphrase,
);
}

View file

@ -1,6 +1,7 @@
import 'dart:io';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
import 'package:cw_bitcoin/bitcoin_mnemonics_bip39.dart';
import 'package:cw_bitcoin/mnemonic_is_incorrect_exception.dart';
import 'package:cw_bitcoin/bitcoin_wallet_creation_credentials.dart';
import 'package:cw_core/encryption_file_utils.dart';
@ -35,8 +36,21 @@ class BitcoinWalletService extends WalletService<
final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet;
credentials.walletInfo?.network = network.value;
final String mnemonic;
switch ( credentials.walletInfo?.derivationInfo?.derivationType) {
case DerivationType.bip39:
final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
mnemonic = await MnemonicBip39.generate(strength: strength);
break;
case DerivationType.electrum:
default:
mnemonic = await generateElectrumMnemonic();
break;
}
final wallet = await BitcoinWalletBase.create(
mnemonic: await generateElectrumMnemonic(),
mnemonic: mnemonic,
password: credentials.password!,
passphrase: credentials.passphrase,
walletInfo: credentials.walletInfo!,

View file

@ -107,22 +107,19 @@ class ElectrumClient {
}
},
onError: (Object error) {
socket = null;
final errorMsg = error.toString();
print(errorMsg);
unterminatedString = '';
final currentHost = socket?.address.host;
final isErrorForCurrentHost = errorMsg.contains(" ${currentHost} ");
if (currentHost != null && isErrorForCurrentHost)
_setConnectionStatus(ConnectionStatus.failed);
},
onDone: () {
unterminatedString = '';
if (host == socket?.address.host) {
socket = null;
_setConnectionStatus(ConnectionStatus.disconnected);
try {
if (host == socket?.address.host) {
socket?.destroy();
_setConnectionStatus(ConnectionStatus.disconnected);
}
} catch(e) {
print(e.toString());
}
},
cancelOnError: true,
@ -436,7 +433,6 @@ class ElectrumClient {
{required String id, required String method, List<Object> params = const []}) {
try {
if (socket == null) {
_setConnectionStatus(ConnectionStatus.failed);
return null;
}
final subscription = BehaviorSubject<T>();
@ -453,7 +449,6 @@ class ElectrumClient {
Future<dynamic> call(
{required String method, List<Object> params = const [], Function(int)? idCallback}) async {
if (socket == null) {
_setConnectionStatus(ConnectionStatus.failed);
return null;
}
final completer = Completer<dynamic>();
@ -467,10 +462,9 @@ class ElectrumClient {
}
Future<dynamic> callWithTimeout(
{required String method, List<Object> params = const [], int timeout = 4000}) async {
{required String method, List<Object> params = const [], int timeout = 5000}) async {
try {
if (socket == null) {
_setConnectionStatus(ConnectionStatus.failed);
return null;
}
final completer = Completer<dynamic>();

View file

@ -109,5 +109,4 @@ Map<DerivationType, List<DerivationInfo>> electrum_derivations = {
],
};
String electrum_path = electrum_derivations[DerivationType.electrum]!.first.derivationPath!;
String electrum_path = electrum_derivations[DerivationType.electrum]!.first.derivationPath!;

View file

@ -5,6 +5,7 @@ import 'dart:isolate';
import 'dart:math';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cw_core/encryption_file_utils.dart';
import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:collection/collection.dart';
@ -44,6 +45,7 @@ import 'package:mobx/mobx.dart';
import 'package:rxdart/subjects.dart';
import 'package:sp_scanner/sp_scanner.dart';
import 'package:hex/hex.dart';
import 'package:http/http.dart' as http;
part 'electrum_wallet.g.dart';
@ -101,6 +103,8 @@ abstract class ElectrumWalletBase
);
reaction((_) => syncStatus, _syncStatusReaction);
sharedPrefs.complete(SharedPreferences.getInstance());
}
static Bip32Slip10Secp256k1 getAccountHDWallet(CryptoCurrency? currency, BasedUtxoNetwork network,
@ -137,6 +141,8 @@ abstract class ElectrumWalletBase
Bip32Slip10Secp256k1 get sideHd => accountHD.childKey(Bip32KeyIndex(1));
final EncryptionFileUtils encryptionFileUtils;
@override
final String? passphrase;
@override
@ -194,6 +200,13 @@ abstract class ElectrumWalletBase
bool _isTryingToConnect = false;
Completer<SharedPreferences> sharedPrefs = Completer();
Future<bool> checkIfMempoolAPIIsEnabled() async {
bool isMempoolAPIEnabled = (await sharedPrefs.future).getBool("use_mempool_fee_api") ?? true;
return isMempoolAPIEnabled;
}
@action
Future<void> setSilentPaymentsScanning(bool active) async {
silentPaymentsScanningActive = active;
@ -263,7 +276,6 @@ abstract class ElectrumWalletBase
Future<Isolate>? _isolate;
void Function(FlutterErrorDetails)? _onError;
Timer? _reconnectTimer;
Timer? _autoSaveTimer;
Timer? _updateFeeRateTimer;
static const int _autoSaveInterval = 1;
@ -416,6 +428,10 @@ abstract class ElectrumWalletBase
@override
Future<void> startSync() async {
try {
if (syncStatus is SyncronizingSyncStatus) {
return;
}
syncStatus = SyncronizingSyncStatus();
if (hasSilentPaymentsScanning) {
@ -446,6 +462,20 @@ abstract class ElectrumWalletBase
@action
Future<void> updateFeeRates() async {
if (await checkIfMempoolAPIIsEnabled()) {
try {
final response =
await http.get(Uri.parse("http://mempool.cakewallet.com:8999/api/v1/fees/recommended"));
final result = json.decode(response.body) as Map<String, num>;
final slowFee = result['economyFee']?.toInt() ?? 0;
final mediumFee = result['hourFee']?.toInt() ?? 0;
final fastFee = result['fastestFee']?.toInt() ?? 0;
_feeRates = [slowFee, mediumFee, fastFee];
return;
} catch (_) {}
}
final feeRates = await electrumClient.feeRates(network: network);
if (feeRates != [0, 0, 0]) {
_feeRates = feeRates;
@ -1059,6 +1089,8 @@ abstract class ElectrumWalletBase
});
}
unspentCoins.removeWhere((utxo) => estimatedTx.utxos.any((e) => e.utxo.txHash == utxo.hash));
await updateBalance();
});
} catch (e) {
@ -1453,7 +1485,6 @@ abstract class ElectrumWalletBase
// Create a list of available outputs
final outputs = <BitcoinOutput>[];
for (final out in bundle.originalTransaction.outputs) {
// Check if the script contains OP_RETURN
final script = out.scriptPubKey.script;
if (script.contains('OP_RETURN') && memo == null) {
@ -2029,9 +2060,8 @@ abstract class ElectrumWalletBase
_isTryingToConnect = true;
_reconnectTimer?.cancel();
_reconnectTimer = Timer(Duration(seconds: 10), () {
if (this.syncStatus is! SyncedSyncStatus && this.syncStatus is! SyncedTipSyncStatus) {
Timer(Duration(seconds: 5), () {
if (this.syncStatus is NotConnectedSyncStatus || this.syncStatus is LostConnectionSyncStatus) {
this.electrumClient.connectToUri(
node!.uri,
useSSL: node!.useSSL ?? false,
@ -2056,13 +2086,42 @@ abstract class ElectrumWalletBase
tx.inputAddresses!.isEmpty ||
tx.outputAddresses == null ||
tx.outputAddresses!.isEmpty) {
tx = ElectrumTransactionInfo.fromElectrumBundle(
bundle,
walletInfo.type,
network,
addresses: addressesSet,
height: tx.height,
);
List<String> inputAddresses = [];
List<String> outputAddresses = [];
for (int i = 0; i < bundle.originalTransaction.inputs.length; i++) {
final input = bundle.originalTransaction.inputs[i];
final inputTransaction = bundle.ins[i];
final vout = input.txIndex;
final outTransaction = inputTransaction.outputs[vout];
final address = addressFromOutputScript(outTransaction.scriptPubKey, network);
if (address.isNotEmpty) inputAddresses.add(address);
}
for (int i = 0; i < bundle.originalTransaction.outputs.length; i++) {
final out = bundle.originalTransaction.outputs[i];
final address = addressFromOutputScript(out.scriptPubKey, network);
if (address.isNotEmpty) outputAddresses.add(address);
// Check if the script contains OP_RETURN
final script = out.scriptPubKey.script;
if (script.contains('OP_RETURN')) {
final index = script.indexOf('OP_RETURN');
if (index + 1 <= script.length) {
try {
final opReturnData = script[index + 1].toString();
final decodedString = utf8.decode(HEX.decode(opReturnData));
outputAddresses.add('OP_RETURN:$decodedString');
} catch (_) {
outputAddresses.add('OP_RETURN:');
}
}
}
}
tx.inputAddresses = inputAddresses;
tx.outputAddresses = outputAddresses;
transactionHistory.addOne(tx);
}

View file

@ -7,6 +7,7 @@ import 'package:bip39/bip39.dart' as bip39;
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
import 'package:cw_bitcoin/electrum_derivations.dart';
import 'package:cw_core/encryption_file_utils.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/unspent_coins_info.dart';
@ -36,6 +37,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
required Box<UnspentCoinsInfo> unspentCoinsInfo,
required Uint8List seedBytes,
required EncryptionFileUtils encryptionFileUtils,
String? passphrase,
String? addressPageType,
List<BitcoinAddressRecord>? initialAddresses,
ElectrumBalance? initialBalance,
@ -51,6 +53,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
initialBalance: initialBalance,
seedBytes: seedBytes,
encryptionFileUtils: encryptionFileUtils,
passphrase: passphrase,
currency: CryptoCurrency.ltc) {
walletAddresses = LitecoinWalletAddresses(
walletInfo,
@ -89,7 +92,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
break;
case DerivationType.electrum:
default:
seedBytes = await mnemonicToSeedBytes(mnemonic);
seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? "");
break;
}
return LitecoinWallet(
@ -100,6 +103,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
initialAddresses: initialAddresses,
initialBalance: initialBalance,
encryptionFileUtils: encryptionFileUtils,
passphrase: passphrase,
seedBytes: seedBytes,
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
@ -143,6 +147,31 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
);
}
walletInfo.derivationInfo ??= DerivationInfo();
// set the default if not present:
walletInfo.derivationInfo!.derivationPath ??= snp?.derivationPath ?? electrum_path;
walletInfo.derivationInfo!.derivationType ??= snp?.derivationType ?? DerivationType.electrum;
Uint8List? seedBytes = null;
final mnemonic = keysData.mnemonic;
final passphrase = keysData.passphrase;
if (mnemonic != null) {
switch (walletInfo.derivationInfo?.derivationType) {
case DerivationType.bip39:
seedBytes = await bip39.mnemonicToSeed(
mnemonic,
passphrase: passphrase ?? "",
);
break;
case DerivationType.electrum:
default:
seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? "");
break;
}
}
return LitecoinWallet(
mnemonic: keysData.mnemonic!,
password: password,
@ -150,7 +179,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: snp?.addresses,
initialBalance: snp?.balance,
seedBytes: await mnemonicToSeedBytes(keysData.mnemonic!),
seedBytes: seedBytes!,
passphrase: passphrase,
encryptionFileUtils: encryptionFileUtils,
initialRegularAddressIndex: snp?.regularAddressIndex,
initialChangeAddressIndex: snp?.changeAddressIndex,

View file

@ -1,4 +1,5 @@
import 'dart:io';
import 'package:cw_bitcoin/bitcoin_mnemonics_bip39.dart';
import 'package:cw_core/encryption_file_utils.dart';
import 'package:cw_core/unspent_coins_info.dart';
import 'package:hive/hive.dart';
@ -30,8 +31,21 @@ class LitecoinWalletService extends WalletService<
@override
Future<LitecoinWallet> create(BitcoinNewWalletCredentials credentials, {bool? isTestnet}) async {
final String mnemonic;
switch ( credentials.walletInfo?.derivationInfo?.derivationType) {
case DerivationType.bip39:
final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
mnemonic = await MnemonicBip39.generate(strength: strength);
break;
case DerivationType.electrum:
default:
mnemonic = await generateElectrumMnemonic();
break;
}
final wallet = await LitecoinWalletBase.create(
mnemonic: await generateElectrumMnemonic(),
mnemonic: mnemonic,
password: credentials.password!,
passphrase: credentials.passphrase,
walletInfo: credentials.walletInfo!,

View file

@ -350,6 +350,11 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
frontend_server_client:
dependency: transitive
description:
@ -736,6 +741,62 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.27.7"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180
url: "https://pub.dev"
source: hosted
version: "2.2.3"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "1ee8bf911094a1b592de7ab29add6f826a7331fb854273d55918693d5364a1f2"
url: "https://pub.dev"
source: hosted
version: "2.2.2"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: c4b35f6cb8f63c147312c054ce7c2254c8066745125264f0c88739c417fc9d9f
url: "https://pub.dev"
source: hosted
version: "2.5.2"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: "59dc807b94d29d52ddbb1b3c0d3b9d0a67fc535a64e62a5542c8db0513fcb6c2"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shelf:
dependency: transitive
description:
@ -944,4 +1005,4 @@ packages:
version: "2.2.1"
sdks:
dart: ">=3.3.0 <4.0.0"
flutter: ">=3.16.6"
flutter: ">=3.19.0"

View file

@ -17,6 +17,7 @@ dependencies:
mobx: ^2.0.7+4
flutter_mobx: ^2.0.6+1
intl: ^0.18.0
shared_preferences: ^2.0.15
cw_core:
path: ../cw_core
bitbox:

View file

@ -3,5 +3,4 @@ export 'bitcoin_cash_wallet_addresses.dart';
export 'bitcoin_cash_wallet_creation_credentials.dart';
export 'bitcoin_cash_wallet_service.dart';
export 'exceptions/exceptions.dart';
export 'mnemonic.dart';
export 'bitcoin_cash_address_utils.dart';

View file

@ -1,13 +1,14 @@
import 'package:bitbox/bitbox.dart' as bitbox;
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:cw_core/encryption_file_utils.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/bitcoin_mnemonics_bip39.dart';
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_bitcoin/electrum_wallet.dart';
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/encryption_file_utils.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/unspent_coins_info.dart';
import 'package:cw_core/wallet_info.dart';
@ -30,6 +31,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
required Box<UnspentCoinsInfo> unspentCoinsInfo,
required Uint8List seedBytes,
required EncryptionFileUtils encryptionFileUtils,
String? passphrase,
BitcoinAddressType? addressPageType,
List<BitcoinAddressRecord>? initialAddresses,
ElectrumBalance? initialBalance,
@ -45,7 +47,8 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
initialBalance: initialBalance,
seedBytes: seedBytes,
currency: CryptoCurrency.bch,
encryptionFileUtils: encryptionFileUtils) {
encryptionFileUtils: encryptionFileUtils,
passphrase: passphrase) {
walletAddresses = BitcoinCashWalletAddresses(
walletInfo,
initialAddresses: initialAddresses,
@ -67,6 +70,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo,
required EncryptionFileUtils encryptionFileUtils,
String? passphrase,
String? addressPageType,
List<BitcoinAddressRecord>? initialAddresses,
ElectrumBalance? initialBalance,
@ -79,11 +83,12 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: initialAddresses,
initialBalance: initialBalance,
seedBytes: await MnemonicBip39.toSeed(mnemonic),
seedBytes: await MnemonicBip39.toSeed(mnemonic, passphrase: passphrase),
encryptionFileUtils: encryptionFileUtils,
initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex,
addressPageType: P2pkhAddressType.p2pkh,
passphrase: passphrase,
);
}
@ -150,11 +155,12 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
}
}).toList(),
initialBalance: snp?.balance,
seedBytes: await MnemonicBip39.toSeed(keysData.mnemonic!),
seedBytes: await MnemonicBip39.toSeed(keysData.mnemonic!, passphrase: keysData.passphrase),
encryptionFileUtils: encryptionFileUtils,
initialRegularAddressIndex: snp?.regularAddressIndex,
initialChangeAddressIndex: snp?.changeAddressIndex,
addressPageType: P2pkhAddressType.p2pkh,
passphrase: keysData.passphrase,
);
}

View file

@ -2,17 +2,19 @@ import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_info.dart';
class BitcoinCashNewWalletCredentials extends WalletCredentials {
BitcoinCashNewWalletCredentials({required String name, WalletInfo? walletInfo, String? password})
: super(name: name, walletInfo: walletInfo, password: password);
BitcoinCashNewWalletCredentials(
{required String name, WalletInfo? walletInfo, String? password, String? passphrase})
: super(name: name, walletInfo: walletInfo, password: password, passphrase: passphrase);
}
class BitcoinCashRestoreWalletFromSeedCredentials extends WalletCredentials {
BitcoinCashRestoreWalletFromSeedCredentials(
{required String name,
required String password,
required this.mnemonic,
WalletInfo? walletInfo})
: super(name: name, password: password, walletInfo: walletInfo);
BitcoinCashRestoreWalletFromSeedCredentials({
required String name,
required String password,
required this.mnemonic,
WalletInfo? walletInfo,
String? passphrase,
}) : super(name: name, password: password, walletInfo: walletInfo, passphrase: passphrase);
final String mnemonic;
}

View file

@ -1,6 +1,8 @@
import 'dart:io';
import 'package:bip39/bip39.dart';
import 'package:collection/collection.dart';
import 'package:cw_bitcoin/bitcoin_mnemonics_bip39.dart';
import 'package:cw_bitcoin_cash/cw_bitcoin_cash.dart';
import 'package:cw_core/encryption_file_utils.dart';
import 'package:cw_core/pathForWallet.dart';
@ -9,7 +11,6 @@ import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_service.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:collection/collection.dart';
import 'package:hive/hive.dart';
class BitcoinCashWalletService extends WalletService<
@ -35,11 +36,12 @@ class BitcoinCashWalletService extends WalletService<
final strength = credentials.seedPhraseLength == 24 ? 256 : 128;
final wallet = await BitcoinCashWalletBase.create(
mnemonic: await MnemonicBip39.generate(strength: strength),
mnemonic: await MnemonicBip39.generate(strength: strength),
password: credentials.password!,
walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource,
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
passphrase: credentials.passphrase,
);
await wallet.save();
await wallet.init();
@ -54,11 +56,11 @@ class BitcoinCashWalletService extends WalletService<
try {
final wallet = await BitcoinCashWalletBase.open(
password: password,
name: name,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource,
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
password: password,
name: name,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource,
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
);
await wallet.init();
saveBackup(name);
@ -66,11 +68,11 @@ class BitcoinCashWalletService extends WalletService<
} catch (_) {
await restoreWalletFilesFromBackup(name);
final wallet = await BitcoinCashWalletBase.open(
password: password,
name: name,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource,
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
password: password,
name: name,
walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource,
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
);
await wallet.init();
return wallet;
@ -130,7 +132,9 @@ class BitcoinCashWalletService extends WalletService<
mnemonic: credentials.mnemonic,
walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource,
encryptionFileUtils: encryptionFileUtilsFor(isDirect));
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
passphrase: credentials.passphrase
);
await wallet.save();
await wallet.init();
return wallet;

View file

@ -46,6 +46,8 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
String? get hexSeed => null;
String? get passphrase => null;
Object get keys;
WalletAddresses get walletAddresses;

View file

@ -138,11 +138,17 @@ PendingTransactionDescription createTransactionMultDestSync(
int accountIndex = 0,
List<String> preferredInputs = const []}) {
final dstAddrs = outputs.map((e) => e.address).toList();
final amounts = outputs.map((e) => monero.Wallet_amountFromString(e.amount)).toList();
// print("multDest: dstAddrs: $dstAddrs");
// print("multDest: amounts: $amounts");
final txptr = monero.Wallet_createTransactionMultDest(
wptr!,
dstAddr: outputs.map((e) => e.address).toList(),
dstAddr: dstAddrs,
isSweepAll: false,
amounts: outputs.map((e) => monero.Wallet_amountFromString(e.amount)).toList(),
amounts: amounts,
mixinCount: 0,
pendingTransactionPriority: priorityRaw,
subaddr_account: accountIndex,
@ -307,7 +313,34 @@ class Transaction {
confirmations = monero.TransactionInfo_confirmations(txInfo),
fee = monero.TransactionInfo_fee(txInfo),
description = monero.TransactionInfo_description(txInfo),
key = monero.Wallet_getTxKey(wptr!, txid: monero.TransactionInfo_hash(txInfo));
key = getTxKey(txInfo);
static String getTxKey(monero.TransactionInfo txInfo) {
final txKey = monero.Wallet_getTxKey(wptr!, txid: monero.TransactionInfo_hash(txInfo));
final status = monero.Wallet_status(wptr!);
if (status != 0) {
return monero.Wallet_errorString(wptr!);
}
return breakTxKey(txKey);
}
static String breakTxKey(String input) {
final x = 64;
StringBuffer buffer = StringBuffer();
for (int i = 0; i < input.length; i += x) {
int endIndex = i + x;
if (endIndex > input.length) {
endIndex = input.length;
}
buffer.write(input.substring(i, endIndex));
if (endIndex != input.length) {
buffer.write('\n\n');
}
}
return buffer.toString().trim();
}
Transaction.dummy({
required this.displayLabel,

View file

@ -13,11 +13,9 @@ import 'package:cw_core/monero_transaction_priority.dart';
import 'package:cw_core/monero_wallet_keys.dart';
import 'package:cw_core/monero_wallet_utils.dart';
import 'package:cw_core/node.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/pending_transaction.dart';
import 'package:cw_core/sync_status.dart';
import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/unspent_coins_info.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_info.dart';

View file

@ -9,11 +9,9 @@ import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_service.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_core/get_height_by_date.dart';
import 'package:cw_monero/api/exceptions/wallet_opening_exception.dart';
import 'package:cw_monero/api/wallet_manager.dart' as monero_wallet_manager;
import 'package:cw_monero/api/wallet_manager.dart';
import 'package:cw_monero/monero_wallet.dart';
import 'package:flutter/widgets.dart';
import 'package:hive/hive.dart';
import 'package:polyseed/polyseed.dart';
import 'package:monero/monero.dart' as monero;
@ -120,7 +118,6 @@ class MoneroWalletService extends WalletService<
@override
Future<MoneroWallet> openWallet(String name, String password, {bool? retryOnFailure}) async {
MoneroWallet? wallet;
try {
final path = await pathForWallet(name: name, type: getType());
@ -147,41 +144,10 @@ class MoneroWalletService extends WalletService<
await wallet.init();
return wallet;
} catch (e, s) {
} catch (e) {
// TODO: Implement Exception for wallet list service.
final bool isBadAlloc = e.toString().contains('bad_alloc') ||
(e is WalletOpeningException &&
(e.message == 'std::bad_alloc' || e.message.contains('bad_alloc')));
final bool doesNotCorrespond = e.toString().contains('does not correspond') ||
(e is WalletOpeningException && e.message.contains('does not correspond'));
final bool isMissingCacheFilesIOS = e.toString().contains('basic_string') ||
(e is WalletOpeningException && e.message.contains('basic_string'));
final bool isMissingCacheFilesAndroid = e.toString().contains('input_stream') ||
e.toString().contains('input stream error') ||
(e is WalletOpeningException &&
(e.message.contains('input_stream') || e.message.contains('input stream error')));
final bool invalidSignature = e.toString().contains('invalid signature') ||
(e is WalletOpeningException && e.message.contains('invalid signature'));
final bool invalidPassword = e.toString().contains('invalid password') ||
(e is WalletOpeningException && e.message.contains('invalid password'));
if (!isBadAlloc &&
!doesNotCorrespond &&
!isMissingCacheFilesIOS &&
!isMissingCacheFilesAndroid &&
!invalidSignature &&
!invalidPassword &&
wallet != null &&
wallet.onError != null) {
wallet.onError!(FlutterErrorDetails(exception: e, stack: s));
}
if (invalidPassword || retryOnFailure == false) {
if (retryOnFailure == false) {
rethrow;
}

View file

@ -463,8 +463,8 @@ packages:
dependency: "direct main"
description:
path: "impls/monero.dart"
ref: bcb328a4956105dc182afd0ce2e48fe263f5f20b
resolved-ref: bcb328a4956105dc182afd0ce2e48fe263f5f20b
ref: 3cb38bee9385faf46b03fd73aab85f3ac4115bf7
resolved-ref: 3cb38bee9385faf46b03fd73aab85f3ac4115bf7
url: "https://github.com/mrcyjanek/monero_c"
source: git
version: "0.0.0"

View file

@ -25,7 +25,7 @@ dependencies:
monero:
git:
url: https://github.com/mrcyjanek/monero_c
ref: bcb328a4956105dc182afd0ce2e48fe263f5f20b # monero_c hash
ref: 3cb38bee9385faf46b03fd73aab85f3ac4115bf7 # monero_c hash
path: impls/monero.dart
mutex: ^3.1.0

View file

@ -466,21 +466,25 @@ class NanoClient {
blocks = blocks as Map<String, dynamic>;
// confirm all receivable blocks:
for (final blockHash in blocks.keys) {
final block = blocks[blockHash];
final String amountRaw = block["amount"] as String;
await receiveBlock(
blockHash: blockHash,
amountRaw: amountRaw,
privateKey: privateKey,
destinationAddress: destinationAddress,
);
// a bit of a hack:
await Future<void>.delayed(const Duration(seconds: 2));
try {
// confirm all receivable blocks:
for (final blockHash in blocks.keys) {
final block = blocks[blockHash];
final String amountRaw = block["amount"] as String;
await receiveBlock(
blockHash: blockHash,
amountRaw: amountRaw,
privateKey: privateKey,
destinationAddress: destinationAddress,
);
// a bit of a hack:
await Future<void>.delayed(const Duration(seconds: 2));
}
return blocks.keys.length;
} catch (_) {
// we failed to confirm all receivable blocks for w/e reason (PoW / node outage / etc)
return 0;
}
return blocks.keys.length;
}
void stop() {}

View file

@ -14,8 +14,11 @@ import 'package:bip39/bip39.dart' as bip39;
import 'package:nanodart/nanodart.dart';
import 'package:nanoutil/nanoutil.dart';
class NanoWalletService extends WalletService<NanoNewWalletCredentials,
NanoRestoreWalletFromSeedCredentials, NanoRestoreWalletFromKeysCredentials, NanoNewWalletCredentials> {
class NanoWalletService extends WalletService<
NanoNewWalletCredentials,
NanoRestoreWalletFromSeedCredentials,
NanoRestoreWalletFromKeysCredentials,
NanoNewWalletCredentials> {
NanoWalletService(this.walletInfoSource, this.isDirect);
final Box<WalletInfo> walletInfoSource;
@ -33,8 +36,12 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
String seedKey = NanoSeeds.generateSeed();
String mnemonic = NanoDerivations.standardSeedToMnemonic(seedKey);
// ensure default if not present:
credentials.walletInfo!.derivationInfo ??= DerivationInfo(derivationType: DerivationType.nano);
// should never happen but just in case:
if (credentials.walletInfo!.derivationInfo == null) {
credentials.walletInfo!.derivationInfo = DerivationInfo(derivationType: DerivationType.nano);
} else if (credentials.walletInfo!.derivationInfo!.derivationType == null) {
credentials.walletInfo!.derivationInfo!.derivationType = DerivationType.nano;
}
final wallet = NanoWallet(
walletInfo: credentials.walletInfo!,
@ -86,7 +93,8 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
}
@override
Future<NanoWallet> restoreFromKeys(NanoRestoreWalletFromKeysCredentials credentials, {bool? isTestnet}) async {
Future<NanoWallet> restoreFromKeys(NanoRestoreWalletFromKeysCredentials credentials,
{bool? isTestnet}) async {
if (credentials.seedKey.contains(' ')) {
throw Exception("Invalid key!");
} else {
@ -106,6 +114,13 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
}
}
// should never happen but just in case:
if (credentials.walletInfo!.derivationInfo == null) {
credentials.walletInfo!.derivationInfo = DerivationInfo(derivationType: DerivationType.nano);
} else if (credentials.walletInfo!.derivationInfo!.derivationType == null) {
credentials.walletInfo!.derivationInfo!.derivationType = DerivationType.nano;
}
final wallet = await NanoWallet(
password: credentials.password!,
mnemonic: mnemonic ?? credentials.seedKey,
@ -119,11 +134,13 @@ class NanoWalletService extends WalletService<NanoNewWalletCredentials,
@override
Future<NanoWallet> restoreFromHardwareWallet(NanoNewWalletCredentials credentials) {
throw UnimplementedError("Restoring a Nano wallet from a hardware wallet is not yet supported!");
throw UnimplementedError(
"Restoring a Nano wallet from a hardware wallet is not yet supported!");
}
@override
Future<NanoWallet> restoreFromSeed(NanoRestoreWalletFromSeedCredentials credentials, {bool? isTestnet}) async {
Future<NanoWallet> restoreFromSeed(NanoRestoreWalletFromSeedCredentials credentials,
{bool? isTestnet}) async {
if (credentials.mnemonic.contains(' ')) {
if (!bip39.validateMnemonic(credentials.mnemonic)) {
throw nm.NanoMnemonicIsIncorrectException();

View file

@ -463,8 +463,8 @@ packages:
dependency: "direct main"
description:
path: "impls/monero.dart"
ref: bcb328a4956105dc182afd0ce2e48fe263f5f20b
resolved-ref: bcb328a4956105dc182afd0ce2e48fe263f5f20b
ref: 3cb38bee9385faf46b03fd73aab85f3ac4115bf7
resolved-ref: 3cb38bee9385faf46b03fd73aab85f3ac4115bf7
url: "https://github.com/mrcyjanek/monero_c"
source: git
version: "0.0.0"

View file

@ -25,7 +25,7 @@ dependencies:
monero:
git:
url: https://github.com/mrcyjanek/monero_c
ref: bcb328a4956105dc182afd0ce2e48fe263f5f20b # monero_c hash
ref: 3cb38bee9385faf46b03fd73aab85f3ac4115bf7 # monero_c hash
path: impls/monero.dart
mutex: ^3.1.0

View file

@ -29,8 +29,9 @@ class CWBitcoin extends Bitcoin {
@override
WalletCredentials createBitcoinNewWalletCredentials(
{required String name, WalletInfo? walletInfo, String? password}) =>
BitcoinNewWalletCredentials(name: name, walletInfo: walletInfo, password: password);
{required String name, WalletInfo? walletInfo, String? password, String? passphrase}) =>
BitcoinNewWalletCredentials(
name: name, walletInfo: walletInfo, password: password, passphrase: passphrase);
@override
WalletCredentials createBitcoinHardwareWalletCredentials(
@ -202,8 +203,8 @@ class CWBitcoin extends Bitcoin {
await bitcoinWallet.updateAllUnspents();
}
WalletService createBitcoinWalletService(
Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource, bool alwaysScan, bool isDirect) {
WalletService createBitcoinWalletService(Box<WalletInfo> walletInfoSource,
Box<UnspentCoinsInfo> unspentCoinSource, bool alwaysScan, bool isDirect) {
return BitcoinWalletService(walletInfoSource, unspentCoinSource, alwaysScan, isDirect);
}
@ -315,7 +316,7 @@ class CWBitcoin extends Bitcoin {
for (DerivationType dType in electrum_derivations.keys) {
late Uint8List seedBytes;
if (dType == DerivationType.electrum) {
seedBytes = await mnemonicToSeedBytes(mnemonic);
seedBytes = await mnemonicToSeedBytes(mnemonic, passphrase: passphrase ?? "");
} else if (dType == DerivationType.bip39) {
seedBytes = bip39.mnemonicToSeed(mnemonic, passphrase: passphrase ?? '');
}

View file

@ -15,14 +15,16 @@ class CWBitcoinCash extends BitcoinCash {
required String name,
WalletInfo? walletInfo,
String? password,
String? passphrase,
}) =>
BitcoinCashNewWalletCredentials(name: name, walletInfo: walletInfo, password: password);
BitcoinCashNewWalletCredentials(
name: name, walletInfo: walletInfo, password: password, passphrase: passphrase);
@override
WalletCredentials createBitcoinCashRestoreWalletFromSeedCredentials(
{required String name, required String mnemonic, required String password}) =>
{required String name, required String mnemonic, required String password, String? passphrase}) =>
BitcoinCashRestoreWalletFromSeedCredentials(
name: name, mnemonic: mnemonic, password: password);
name: name, mnemonic: mnemonic, password: password, passphrase: passphrase);
@override
TransactionPriority deserializeBitcoinCashTransactionPriority(int raw) =>

View file

@ -75,8 +75,10 @@ class WalletCreationService {
bool get _hasSeedPhraseLengthOption {
switch (type) {
case WalletType.ethereum:
case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.bitcoinCash:
case WalletType.ethereum:
case WalletType.polygon:
case WalletType.solana:
case WalletType.tron:
@ -84,8 +86,6 @@ class WalletCreationService {
case WalletType.monero:
case WalletType.wownero:
case WalletType.none:
case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.haven:
case WalletType.nano:
case WalletType.banano:

View file

@ -146,7 +146,7 @@ import 'package:cake_wallet/view_model/cake_pay/cake_pay_purchase_view_model.dar
import 'package:cake_wallet/view_model/nano_account_list/nano_account_edit_or_create_view_model.dart';
import 'package:cake_wallet/view_model/nano_account_list/nano_account_list_view_model.dart';
import 'package:cake_wallet/view_model/node_list/pow_node_list_view_model.dart';
import 'package:cake_wallet/view_model/seed_type_view_model.dart';
import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart';
import 'package:cake_wallet/view_model/restore/restore_from_qr_vm.dart';
import 'package:cake_wallet/view_model/settings/display_settings_view_model.dart';
@ -179,6 +179,7 @@ import 'package:cake_wallet/store/dashboard/trades_store.dart';
import 'package:cake_wallet/store/dashboard/transaction_filter_store.dart';
import 'package:cake_wallet/store/node_list_store.dart';
import 'package:cake_wallet/store/secret_store.dart';
import 'package:cake_wallet/store/seed_settings_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/store/templates/exchange_template_store.dart';
import 'package:cake_wallet/store/templates/send_template_store.dart';
@ -331,6 +332,7 @@ Future<void> setup({
YatStore(appStore: getIt.get<AppStore>(), secureStorage: getIt.get<SecureStorage>())..init());
getIt.registerSingleton<AnonpayTransactionsStore>(
AnonpayTransactionsStore(anonpayInvoiceInfoSource: _anonpayInvoiceInfoSource));
getIt.registerSingleton<SeedSettingsStore>(SeedSettingsStore());
getIt.registerLazySingleton(() => LedgerViewModel());
@ -361,6 +363,7 @@ Future<void> setup({
getIt.get<WalletCreationService>(param1: type),
_walletInfoSource,
getIt.get<AdvancedPrivacySettingsViewModel>(param1: type),
getIt.get<SeedSettingsViewModel>(),
type: type));
getIt.registerFactoryParam<WalletUnlockPage, WalletUnlockArguments, bool>((args, closable) {
@ -422,14 +425,21 @@ Future<void> setup({
walletType: args.walletType ?? currentWalletType);
});
getIt.registerFactoryParam<WalletRestorationFromQRVM, WalletType, void>((WalletType type, _) {
return WalletRestorationFromQRVM(getIt.get<AppStore>(),
getIt.get<WalletCreationService>(param1: type), _walletInfoSource, type);
});
getIt.registerFactoryParam<WalletRestorationFromQRVM, WalletType, void>((WalletType type, _) =>
WalletRestorationFromQRVM(
getIt.get<AppStore>(),
getIt.get<WalletCreationService>(param1: type),
_walletInfoSource,
type,
getIt.get<SeedSettingsViewModel>()));
getIt.registerFactoryParam<WalletHardwareRestoreViewModel, WalletType, void>((type, _) =>
WalletHardwareRestoreViewModel(getIt.get<LedgerViewModel>(), getIt.get<AppStore>(),
getIt.get<WalletCreationService>(param1: type), _walletInfoSource,
WalletHardwareRestoreViewModel(
getIt.get<LedgerViewModel>(),
getIt.get<AppStore>(),
getIt.get<WalletCreationService>(param1: type),
_walletInfoSource,
getIt.get<SeedSettingsViewModel>(),
type: type));
getIt.registerFactory<WalletAddressListViewModel>(() => WalletAddressListViewModel(
@ -833,7 +843,7 @@ Future<void> setup({
getIt.registerFactory(() => WalletSeedViewModel(getIt.get<AppStore>().wallet!));
getIt.registerFactory<SeedTypeViewModel>(() => SeedTypeViewModel(getIt.get<AppStore>()));
getIt.registerFactory<SeedSettingsViewModel>(() => SeedSettingsViewModel(getIt.get<AppStore>(), getIt.get<SeedSettingsStore>()));
getIt.registerFactoryParam<WalletSeedPage, bool, void>((bool isWalletCreated, _) =>
WalletSeedPage(getIt.get<WalletSeedViewModel>(), isNewWalletCreated: isWalletCreated));
@ -1018,12 +1028,12 @@ Future<void> setup({
getIt.registerFactory(() => FaqPage(getIt.get<SettingsStore>()));
getIt.registerFactoryParam<WalletRestoreViewModel, WalletType, void>((type, _) =>
WalletRestoreViewModel(
getIt.get<AppStore>(), getIt.get<WalletCreationService>(param1: type), _walletInfoSource,
WalletRestoreViewModel(getIt.get<AppStore>(), getIt.get<WalletCreationService>(param1: type),
_walletInfoSource, getIt.get<SeedSettingsViewModel>(),
type: type));
getIt.registerFactoryParam<WalletRestorePage, WalletType, void>((type, _) => WalletRestorePage(
getIt.get<WalletRestoreViewModel>(param1: type), getIt.get<SeedTypeViewModel>()));
getIt.get<WalletRestoreViewModel>(param1: type), getIt.get<SeedSettingsViewModel>()));
getIt.registerFactoryParam<WalletRestoreChooseDerivationViewModel, List<DerivationInfo>, void>(
(derivations, _) => WalletRestoreChooseDerivationViewModel(derivationInfos: derivations));
@ -1275,7 +1285,7 @@ Future<void> setup({
getIt.registerFactory(
() => WalletConnectConnectionsView(web3walletService: getIt.get<Web3WalletService>()));
getIt.registerFactory(() => NFTViewModel(appStore, getIt.get<BottomSheetService>()));
getIt.registerFactory<TorPage>(() => TorPage(getIt.get<AppStore>()));

View file

@ -40,6 +40,7 @@ const solanaDefaultNodeUri = 'rpc.ankr.com';
const tronDefaultNodeUri = 'trx.nownodes.io';
const newCakeWalletBitcoinUri = 'btc-electrum.cakewallet.com:50002';
const wowneroDefaultNodeUri = 'node3.monerodevs.org:34568';
const moneroWorldNodeUri = '.moneroworld.com';
Future<void> defaultSettingsMigration(
{required int version,
@ -245,6 +246,9 @@ Future<void> defaultSettingsMigration(
_fixNodesUseSSLFlag(nodes);
await changeDefaultNanoNode(nodes, sharedPreferences);
break;
case 40:
await removeMoneroWorld(sharedPreferences: sharedPreferences, nodes: nodes);
break;
default:
break;
}
@ -488,15 +492,7 @@ Node? getBitcoinCashDefaultElectrumServer({required Box<Node> nodes}) {
Node getMoneroDefaultNode({required Box<Node> nodes}) {
final timeZone = DateTime.now().timeZoneOffset.inHours;
var nodeUri = '';
if (timeZone >= 1) {
// Eurasia
nodeUri = 'xmr-node-eu.cakewallet.com:18081';
} else if (timeZone <= -4) {
// America
nodeUri = 'xmr-node-usa-east.cakewallet.com:18081';
}
var nodeUri = newCakeWalletMoneroUri;
try {
return nodes.values.firstWhere((Node node) => node.uriRaw == nodeUri);
@ -1260,3 +1256,22 @@ Future<void> replaceTronDefaultNode({
// If it's not, we switch user to the new default node: NowNodes
await changeTronCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
}
Future<void> removeMoneroWorld(
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
const cakeWalletMoneroNodeUriPattern = '.moneroworld.com';
final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
final currentMoneroNode = nodes.values.firstWhere((node) => node.key == currentMoneroNodeId);
final needToReplaceCurrentMoneroNode = currentMoneroNode.uri.toString().contains(cakeWalletMoneroNodeUriPattern);
nodes.values.forEach((node) async {
if (node.type == WalletType.monero &&
node.uri.toString().contains(cakeWalletMoneroNodeUriPattern)) {
await node.delete();
}
});
if (needToReplaceCurrentMoneroNode) {
await changeMoneroCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
}
}

View file

@ -60,7 +60,7 @@ class LanguageService {
'yo': 'nga',
'ha': 'hau',
'tl': 'phl',
'hy': 'arm'
'hy': 'arm',
};
static final list = <String, String>{};

View file

@ -61,6 +61,7 @@ class PreferencesKey {
static const useEtherscan = 'use_etherscan';
static const usePolygonScan = 'use_polygonscan';
static const useTronGrid = 'use_trongrid';
static const useMempoolFeeAPI = 'use_mempool_fee_api';
static const defaultNanoRep = 'default_nano_representative';
static const defaultBananoRep = 'default_banano_representative';
static const lookupsTwitter = 'looks_up_twitter';
@ -77,6 +78,7 @@ class PreferencesKey {
static const exchangeProvidersSelection = 'exchange-providers-selection';
static const autoGenerateSubaddressStatusKey = 'auto_generate_subaddress_status';
static const moneroSeedType = 'monero_seed_type';
static const bitcoinSeedType = 'bitcoin_seed_type';
static const clearnetDonationLink = 'clearnet_donation_link';
static const onionDonationLink = 'onion_donation_link';
static const donationLinkWalletName = 'donation_link_wallet_name';

View file

@ -1,18 +1,19 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cw_core/enumerable_item.dart';
import 'package:cw_core/wallet_info.dart';
class SeedType extends EnumerableItem<int> with Serializable<int> {
const SeedType({required String title, required int raw}) : super(title: title, raw: raw);
class MoneroSeedType extends EnumerableItem<int> with Serializable<int> {
const MoneroSeedType({required String title, required int raw}) : super(title: title, raw: raw);
static const all = [SeedType.legacy, SeedType.polyseed];
static const all = [MoneroSeedType.legacy, MoneroSeedType.polyseed];
static const defaultSeedType = polyseed;
static const legacy = SeedType(raw: 0, title: 'Legacy (25 words)');
static const polyseed = SeedType(raw: 1, title: 'Polyseed (16 words)');
static const wowneroSeed = SeedType(raw: 2, title: 'Wownero (14 words)');
static const legacy = MoneroSeedType(raw: 0, title: 'Legacy (25 words)');
static const polyseed = MoneroSeedType(raw: 1, title: 'Polyseed (16 words)');
static const wowneroSeed = MoneroSeedType(raw: 2, title: 'Wownero (14 words)');
static SeedType deserialize({required int raw}) {
static MoneroSeedType deserialize({required int raw}) {
switch (raw) {
case 0:
return legacy;
@ -28,14 +29,39 @@ class SeedType extends EnumerableItem<int> with Serializable<int> {
@override
String toString() {
switch (this) {
case SeedType.legacy:
case MoneroSeedType.legacy:
return S.current.seedtype_legacy;
case SeedType.polyseed:
case MoneroSeedType.polyseed:
return S.current.seedtype_polyseed;
case SeedType.wowneroSeed:
case MoneroSeedType.wowneroSeed:
return S.current.seedtype_wownero;
default:
return '';
}
}
}
class BitcoinSeedType extends EnumerableItem<int> with Serializable<int> {
const BitcoinSeedType(this.type, {required String title, required int raw})
: super(title: title, raw: raw);
final DerivationType type;
static const all = [BitcoinSeedType.electrum, BitcoinSeedType.bip39];
static const defaultDerivationType = bip39;
static const electrum = BitcoinSeedType(DerivationType.electrum, raw: 0, title: 'Electrum');
static const bip39 = BitcoinSeedType(DerivationType.bip39, raw: 1, title: 'BIP39');
static BitcoinSeedType deserialize({required int raw}) {
switch (raw) {
case 0:
return electrum;
case 1:
return bip39;
default:
throw Exception('Unexpected token: $raw for SeedType deserialize');
}
}
}

View file

@ -27,6 +27,10 @@ class ExchangeProviderDescription extends EnumerableItem<int> with Serializable<
ExchangeProviderDescription(title: 'ThorChain', raw: 8, image: 'assets/images/thorchain.png');
static const quantex =
ExchangeProviderDescription(title: 'Quantex', raw: 9, image: 'assets/images/quantex.png');
static const letsExchange =
ExchangeProviderDescription(title: 'LetsExchange', raw: 10, image: 'assets/images/letsexchange_icon.svg');
static const stealthEx =
ExchangeProviderDescription(title: 'StealthEx', raw: 11, image: 'assets/images/stealthex.png');
static ExchangeProviderDescription deserialize({required int raw}) {
switch (raw) {
@ -50,6 +54,10 @@ class ExchangeProviderDescription extends EnumerableItem<int> with Serializable<
return thorChain;
case 9:
return quantex;
case 10:
return letsExchange;
case 11:
return stealthEx;
default:
throw Exception('Unexpected token: $raw for ExchangeProviderDescription deserialize');
}

View file

@ -0,0 +1,292 @@
import 'dart:convert';
import 'dart:developer';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/exchange/limits.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/trade_not_created_exception.dart';
import 'package:cake_wallet/exchange/trade_request.dart';
import 'package:cake_wallet/exchange/trade_state.dart';
import 'package:cake_wallet/exchange/utils/currency_pairs_utils.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:http/http.dart' as http;
class LetsExchangeExchangeProvider extends ExchangeProvider {
LetsExchangeExchangeProvider() : super(pairList: supportedPairs(_notSupported));
static const List<CryptoCurrency> _notSupported = [];
static const apiKey = secrets.letsExchangeBearerToken;
static const _baseUrl = 'api.letsexchange.io';
static const _infoPath = '/api/v1/info';
static const _infoRevertPath = '/api/v1/info-revert';
static const _createTransactionPath = '/api/v1/transaction';
static const _createTransactionRevertPath = '/api/v1/transaction-revert';
static const _getTransactionPath = '/api/v1/transaction';
static const _affiliateId = secrets.letsExchangeAffiliateId;
@override
String get title => 'LetsExchange';
@override
bool get isAvailable => true;
@override
bool get isEnabled => true;
@override
bool get supportsFixedRate => true;
@override
ExchangeProviderDescription get description => ExchangeProviderDescription.letsExchange;
@override
Future<bool> checkIsAvailable() async => true;
@override
Future<Limits> fetchLimits(
{required CryptoCurrency from,
required CryptoCurrency to,
required bool isFixedRateMode}) async {
final networkFrom = _getNetworkType(from);
final networkTo = _getNetworkType(to);
try {
final params = {
'from': from.title,
'to': to.title,
if (networkFrom != null) 'network_from': networkFrom,
if (networkTo != null) 'network_to': networkTo,
'amount': '1',
'affiliate_id': _affiliateId
};
final responseJSON = await _getInfo(params, isFixedRateMode);
final min = double.tryParse(responseJSON['min_amount'] as String);
final max = double.tryParse(responseJSON['max_amount'] as String);
return Limits(min: min, max: max);
} catch (e) {
log(e.toString());
throw Exception('Failed to fetch limits');
}
}
@override
Future<double> fetchRate(
{required CryptoCurrency from,
required CryptoCurrency to,
required double amount,
required bool isFixedRateMode,
required bool isReceiveAmount}) async {
final networkFrom = _getNetworkType(from);
final networkTo = _getNetworkType(to);
try {
final params = {
'from': from.title,
'to': to.title,
if (networkFrom != null) 'network_from': networkFrom,
if (networkTo != null) 'network_to': networkTo,
'amount': amount.toString(),
'affiliate_id': _affiliateId
};
final responseJSON = await _getInfo(params, isFixedRateMode);
final amountToGet = double.tryParse(responseJSON['amount'] as String) ?? 0.0;
return isFixedRateMode ? amount / amountToGet : amountToGet / amount;
} catch (e) {
log(e.toString());
return 0.0;
}
}
@override
Future<Trade> createTrade(
{required TradeRequest request,
required bool isFixedRateMode,
required bool isSendAll}) async {
final networkFrom = _getNetworkType(request.fromCurrency);
final networkTo = _getNetworkType(request.toCurrency);
try {
final params = {
'from': request.fromCurrency.title,
'to': request.toCurrency.title,
if (networkFrom != null) 'network_from': networkFrom,
if (networkTo != null) 'network_to': networkTo,
'amount': isFixedRateMode ? request.toAmount.toString() : request.fromAmount.toString(),
'affiliate_id': _affiliateId
};
final responseInfoJSON = await _getInfo(params, isFixedRateMode);
final rateId = responseInfoJSON['rate_id'] as String;
final withdrawalAddress = _normalizeBchAddress(request.toAddress);
final returnAddress = _normalizeBchAddress(request.refundAddress);
final tradeParams = {
'coin_from': request.fromCurrency.title,
'coin_to': request.toCurrency.title,
if (!isFixedRateMode) 'deposit_amount': request.fromAmount.toString(),
'withdrawal': withdrawalAddress,
if (isFixedRateMode) 'withdrawal_amount': request.toAmount.toString(),
'withdrawal_extra_id': '',
'return': returnAddress,
'rate_id': rateId,
if (networkFrom != null) 'network_from': networkFrom,
if (networkTo != null) 'network_to': networkTo,
'affiliate_id': _affiliateId
};
final headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': apiKey
};
final uri = Uri.https(_baseUrl,
isFixedRateMode ? _createTransactionRevertPath : _createTransactionPath, tradeParams);
final response = await http.post(uri, headers: headers);
if (response.statusCode != 200) {
throw Exception('LetsExchange create trade failed: ${response.body}');
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final id = responseJSON['transaction_id'] as String;
final from = responseJSON['coin_from'] as String;
final to = responseJSON['coin_to'] as String;
final payoutAddress = responseJSON['withdrawal'] as String;
final depositAddress = responseJSON['deposit'] as String;
final refundAddress = responseJSON['return'] as String;
final depositAmount = responseJSON['deposit_amount'] as String;
final receiveAmount = responseJSON['withdrawal_amount'] as String;
final status = responseJSON['status'] as String;
final createdAtString = responseJSON['created_at'] as String;
final expiredAtTimestamp = responseJSON['expired_at'] as int;
final createdAt = DateTime.parse(createdAtString);
final expiredAt = DateTime.fromMillisecondsSinceEpoch(expiredAtTimestamp * 1000);
CryptoCurrency fromCurrency;
if (request.fromCurrency.tag != null && request.fromCurrency.title == from) {
fromCurrency = request.fromCurrency;
} else {
fromCurrency = CryptoCurrency.fromString(from);
}
CryptoCurrency toCurrency;
if (request.toCurrency.tag != null && request.toCurrency.title == to) {
toCurrency = request.toCurrency;
} else {
toCurrency = CryptoCurrency.fromString(to);
}
return Trade(
id: id,
from: fromCurrency,
to: toCurrency,
provider: description,
inputAddress: depositAddress,
payoutAddress: payoutAddress,
refundAddress: refundAddress,
amount: depositAmount,
receiveAmount: receiveAmount,
state: TradeState.deserialize(raw: status),
createdAt: createdAt,
expiredAt: expiredAt,
);
} catch (e) {
log(e.toString());
throw TradeNotCreatedException(description);
}
}
@override
Future<Trade> findTradeById({required String id}) async {
final headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': apiKey
};
final url = Uri.https(_baseUrl, '$_getTransactionPath/$id');
final response = await http.get(url, headers: headers);
if (response.statusCode != 200) {
throw Exception('LetsExchange fetch trade failed: ${response.body}');
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final from = responseJSON['coin_from'] as String;
final to = responseJSON['coin_to'] as String;
final payoutAddress = responseJSON['withdrawal'] as String;
final depositAddress = responseJSON['deposit'] as String;
final refundAddress = responseJSON['return'] as String;
final depositAmount = responseJSON['deposit_amount'] as String;
final receiveAmount = responseJSON['withdrawal_amount'] as String;
final status = responseJSON['status'] as String;
final createdAtString = responseJSON['created_at'] as String;
final expiredAtTimestamp = responseJSON['expired_at'] as int;
final createdAt = DateTime.parse(createdAtString);
final expiredAt = DateTime.fromMillisecondsSinceEpoch(expiredAtTimestamp * 1000);
return Trade(
id: id,
from: CryptoCurrency.fromString(from),
to: CryptoCurrency.fromString(to),
provider: description,
inputAddress: depositAddress,
payoutAddress: payoutAddress,
refundAddress: refundAddress,
amount: depositAmount,
receiveAmount: receiveAmount,
state: TradeState.deserialize(raw: status),
createdAt: createdAt,
expiredAt: expiredAt,
isRefund: status == 'refund',
);
}
Future<Map<String, dynamic>> _getInfo(Map<String, String> params, bool isFixedRateMode) async {
final headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': apiKey
};
try {
final uri = Uri.https(_baseUrl, isFixedRateMode ? _infoRevertPath : _infoPath, params);
final response = await http.post(uri, headers: headers);
if (response.statusCode != 200) {
throw Exception('LetsExchange fetch info failed: ${response.body}');
}
return json.decode(response.body) as Map<String, dynamic>;
} catch (e) {
throw Exception('LetsExchange failed to fetch info ${e.toString()}');
}
}
String? _getNetworkType(CryptoCurrency currency) {
if (currency.tag != null && currency.tag!.isNotEmpty) {
switch (currency.tag!) {
case 'TRX':
return 'TRC20';
case 'ETH':
return 'ERC20';
case 'BSC':
return 'BEP20';
case 'POLY':
return 'MATIC';
default:
return currency.tag!;
}
}
return currency.title;
}
String _normalizeBchAddress(String address) =>
address.startsWith('bitcoincash:') ? address.substring(12) : address;
}

View file

@ -0,0 +1,299 @@
import 'dart:convert';
import 'dart:developer';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/exchange/limits.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/trade_not_created_exception.dart';
import 'package:cake_wallet/exchange/trade_request.dart';
import 'package:cake_wallet/exchange/trade_state.dart';
import 'package:cake_wallet/exchange/utils/currency_pairs_utils.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:http/http.dart' as http;
class StealthExExchangeProvider extends ExchangeProvider {
StealthExExchangeProvider() : super(pairList: supportedPairs(_notSupported));
static const List<CryptoCurrency> _notSupported = [];
static final apiKey = secrets.stealthExBearerToken;
static final _additionalFeePercent = double.tryParse(secrets.stealthExAdditionalFeePercent);
static const _baseUrl = 'https://api.stealthex.io';
static const _rangePath = '/v4/rates/range';
static const _amountPath = '/v4/rates/estimated-amount';
static const _exchangesPath = '/v4/exchanges';
@override
String get title => 'StealthEX';
@override
bool get isAvailable => true;
@override
bool get isEnabled => true;
@override
bool get supportsFixedRate => true;
@override
ExchangeProviderDescription get description => ExchangeProviderDescription.stealthEx;
@override
Future<bool> checkIsAvailable() async => true;
@override
Future<Limits> fetchLimits(
{required CryptoCurrency from,
required CryptoCurrency to,
required bool isFixedRateMode}) async {
final curFrom = isFixedRateMode ? to : from;
final curTo = isFixedRateMode ? from : to;
final headers = {'Authorization': apiKey, 'Content-Type': 'application/json'};
final body = {
'route': {
'from': {'symbol': _getName(curFrom), 'network': _getNetwork(curFrom)},
'to': {'symbol': _getName(curTo), 'network': _getNetwork(curTo)}
},
'estimation': isFixedRateMode ? 'reversed' : 'direct',
'rate': isFixedRateMode ? 'fixed' : 'floating',
'additional_fee_percent': _additionalFeePercent,
};
try {
final response = await http.post(Uri.parse(_baseUrl + _rangePath),
headers: headers, body: json.encode(body));
if (response.statusCode != 200) {
throw Exception('StealthEx fetch limits failed: ${response.body}');
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final min = toDouble(responseJSON['min_amount']);
final max = responseJSON['max_amount'] as double?;
return Limits(min: min, max: max);
} catch (e) {
log(e.toString());
throw Exception('StealthEx failed to fetch limits');
}
}
@override
Future<double> fetchRate(
{required CryptoCurrency from,
required CryptoCurrency to,
required double amount,
required bool isFixedRateMode,
required bool isReceiveAmount}) async {
final response = await getEstimatedExchangeAmount(
from: from, to: to, amount: amount, isFixedRateMode: isFixedRateMode);
final estimatedAmount = response['estimated_amount'] as double? ?? 0.0;
return estimatedAmount > 0.0
? isFixedRateMode
? amount / estimatedAmount
: estimatedAmount / amount
: 0.0;
}
@override
Future<Trade> createTrade(
{required TradeRequest request,
required bool isFixedRateMode,
required bool isSendAll}) async {
String? rateId;
String? validUntil;
try {
if (isFixedRateMode) {
final response = await getEstimatedExchangeAmount(
from: request.fromCurrency,
to: request.toCurrency,
amount: double.parse(request.toAmount),
isFixedRateMode: isFixedRateMode);
rateId = response['rate_id'] as String?;
validUntil = response['valid_until'] as String?;
if (rateId == null) throw TradeNotCreatedException(description);
}
final headers = {'Authorization': apiKey, 'Content-Type': 'application/json'};
final body = {
'route': {
'from': {
'symbol': _getName(request.fromCurrency),
'network': _getNetwork(request.fromCurrency)
},
'to': {'symbol': _getName(request.toCurrency), 'network': _getNetwork(request.toCurrency)}
},
'estimation': isFixedRateMode ? 'reversed' : 'direct',
'rate': isFixedRateMode ? 'fixed' : 'floating',
if (isFixedRateMode) 'rate_id': rateId,
'amount':
isFixedRateMode ? double.parse(request.toAmount) : double.parse(request.fromAmount),
'address': request.toAddress,
'refund_address': request.refundAddress,
'additional_fee_percent': _additionalFeePercent,
};
final response = await http.post(Uri.parse(_baseUrl + _exchangesPath),
headers: headers, body: json.encode(body));
if (response.statusCode != 201) {
throw Exception('StealthEx create trade failed: ${response.body}');
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final deposit = responseJSON['deposit'] as Map<String, dynamic>;
final withdrawal = responseJSON['withdrawal'] as Map<String, dynamic>;
final id = responseJSON['id'] as String;
final from = deposit['symbol'] as String;
final to = withdrawal['symbol'] as String;
final payoutAddress = withdrawal['address'] as String;
final depositAddress = deposit['address'] as String;
final refundAddress = responseJSON['refund_address'] as String;
final depositAmount = toDouble(deposit['amount']);
final receiveAmount = toDouble(withdrawal['amount']);
final status = responseJSON['status'] as String;
final createdAtString = responseJSON['created_at'] as String;
final createdAt = DateTime.parse(createdAtString);
final expiredAt = validUntil != null
? DateTime.parse(validUntil)
: DateTime.now().add(Duration(minutes: 5));
CryptoCurrency fromCurrency;
if (request.fromCurrency.tag != null && request.fromCurrency.title.toLowerCase() == from) {
fromCurrency = request.fromCurrency;
} else {
fromCurrency = CryptoCurrency.fromString(from);
}
CryptoCurrency toCurrency;
if (request.toCurrency.tag != null && request.toCurrency.title.toLowerCase() == to) {
toCurrency = request.toCurrency;
} else {
toCurrency = CryptoCurrency.fromString(to);
}
return Trade(
id: id,
from: fromCurrency,
to: toCurrency,
provider: description,
inputAddress: depositAddress,
payoutAddress: payoutAddress,
refundAddress: refundAddress,
amount: depositAmount.toString(),
receiveAmount: receiveAmount.toString(),
state: TradeState.deserialize(raw: status),
createdAt: createdAt,
expiredAt: expiredAt,
);
} catch (e) {
log(e.toString());
throw TradeNotCreatedException(description);
}
}
@override
Future<Trade> findTradeById({required String id}) async {
final headers = {'Authorization': apiKey, 'Content-Type': 'application/json'};
final uri = Uri.parse('$_baseUrl$_exchangesPath/$id');
final response = await http.get(uri, headers: headers);
if (response.statusCode != 200) {
throw Exception('StealthEx fetch trade failed: ${response.body}');
}
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final deposit = responseJSON['deposit'] as Map<String, dynamic>;
final withdrawal = responseJSON['withdrawal'] as Map<String, dynamic>;
final respId = responseJSON['id'] as String;
final from = deposit['symbol'] as String;
final to = withdrawal['symbol'] as String;
final payoutAddress = withdrawal['address'] as String;
final depositAddress = deposit['address'] as String;
final refundAddress = responseJSON['refund_address'] as String;
final depositAmount = toDouble(deposit['amount']);
final receiveAmount = toDouble(withdrawal['amount']);
final status = responseJSON['status'] as String;
final createdAtString = responseJSON['created_at'] as String;
final createdAt = DateTime.parse(createdAtString);
return Trade(
id: respId,
from: CryptoCurrency.fromString(from),
to: CryptoCurrency.fromString(to),
provider: description,
inputAddress: depositAddress,
payoutAddress: payoutAddress,
refundAddress: refundAddress,
amount: depositAmount.toString(),
receiveAmount: receiveAmount.toString(),
state: TradeState.deserialize(raw: status),
createdAt: createdAt,
isRefund: status == 'refunded',
);
}
Future<Map<String, dynamic>> getEstimatedExchangeAmount(
{required CryptoCurrency from,
required CryptoCurrency to,
required double amount,
required bool isFixedRateMode}) async {
final headers = {'Authorization': apiKey, 'Content-Type': 'application/json'};
final body = {
'route': {
'from': {'symbol': _getName(from), 'network': _getNetwork(from)},
'to': {'symbol': _getName(to), 'network': _getNetwork(to)}
},
'estimation': isFixedRateMode ? 'reversed' : 'direct',
'rate': isFixedRateMode ? 'fixed' : 'floating',
'amount': amount,
'additional_fee_percent': _additionalFeePercent,
};
try {
final response = await http.post(Uri.parse(_baseUrl + _amountPath),
headers: headers, body: json.encode(body));
if (response.statusCode != 200) return {};
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final rate = responseJSON['rate'] as Map<String, dynamic>?;
return {
'estimated_amount': responseJSON['estimated_amount'] as double?,
if (rate != null) 'valid_until': rate['valid_until'] as String?,
if (rate != null) 'rate_id': rate['id'] as String?
};
} catch (e) {
log(e.toString());
return {};
}
}
double toDouble(dynamic value) {
if (value is int) {
return value.toDouble();
} else if (value is double) {
return value;
} else {
return 0.0;
}
}
String _getName(CryptoCurrency currency) {
if (currency == CryptoCurrency.usdcEPoly) return 'usdce';
return currency.title.toLowerCase();
}
String _getNetwork(CryptoCurrency currency) {
if (currency.tag == null) return 'mainnet';
if (currency == CryptoCurrency.maticpoly) return 'mainnet';
if (currency.tag == 'POLY') return 'matic';
return currency.tag!.toLowerCase();
}
}

View file

@ -40,7 +40,6 @@ class TradeState extends EnumerableItem<String> with Serializable<String> {
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) {
@ -107,6 +106,7 @@ class TradeState extends EnumerableItem<String> with Serializable<String> {
case 'waitingAuthorization':
return waitingAuthorization;
case 'failed':
case 'error':
return failed;
case 'completed':
return completed;
@ -119,12 +119,14 @@ class TradeState extends EnumerableItem<String> with Serializable<String> {
case 'refunded':
return refunded;
case 'confirmation':
case 'verifying':
return confirmation;
case 'confirmed':
return confirmed;
case 'exchanging':
return exchanging;
case 'sending':
case 'sending_confirmation':
return sending;
case 'success':
case 'done':

View file

@ -48,10 +48,14 @@ final rootKey = GlobalKey<RootState>();
final RouteObserver<PageRoute<dynamic>> routeObserver = RouteObserver<PageRoute<dynamic>>();
Future<void> main({Key? topLevelKey}) async {
await runAppWithZone(topLevelKey: topLevelKey);
}
Future<void> runAppWithZone({Key? topLevelKey}) async {
bool isAppRunning = false;
await runZonedGuarded(() async {
WidgetsFlutterBinding.ensureInitialized();
FlutterError.onError = ExceptionHandler.onError;
/// A callback that is invoked when an unhandled error occurs in the root
@ -61,42 +65,14 @@ Future<void> main({Key? topLevelKey}) async {
return true;
};
await initializeAppAtRoot();
await setDefaultMinimumWindowSize();
await CakeHive.close();
await initializeAppConfigs();
runApp(App(key: topLevelKey));
runApp(App());
isAppRunning = true;
}, (error, stackTrace) async {
if (!isAppRunning) {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
scrollBehavior: AppScrollBehavior(),
home: Scaffold(
body: SingleChildScrollView(
child: Container(
margin: EdgeInsets.only(top: 50, left: 20, right: 20, bottom: 20),
child: Column(
children: [
Text(
'Error:\n${error.toString()}',
style: TextStyle(fontSize: 22),
),
Text(
'Stack trace:\n${stackTrace.toString()}',
style: TextStyle(fontSize: 16),
),
],
),
),
),
),
),
TopLevelErrorWidget(error: error, stackTrace: stackTrace),
);
}
@ -104,6 +80,12 @@ Future<void> main({Key? topLevelKey}) async {
});
}
Future<void> initializeAppAtRoot({bool reInitializing = false}) async {
if (!reInitializing) await setDefaultMinimumWindowSize();
await CakeHive.close();
await initializeAppConfigs();
}
Future<void> initializeAppConfigs() async {
setRootDirFromEnv();
final appDir = await getAppDir();
@ -340,3 +322,41 @@ class _HomeState extends State<_Home> {
return const SizedBox.shrink();
}
}
class TopLevelErrorWidget extends StatelessWidget {
const TopLevelErrorWidget({
required this.error,
required this.stackTrace,
super.key,
});
final Object error;
final StackTrace stackTrace;
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
scrollBehavior: AppScrollBehavior(),
home: Scaffold(
body: SingleChildScrollView(
child: Container(
margin: EdgeInsets.only(top: 50, left: 20, right: 20, bottom: 20),
child: Column(
children: [
Text(
'Error:\n${error.toString()}',
style: TextStyle(fontSize: 22),
),
Text(
'Stack trace:\n${stackTrace.toString()}',
style: TextStyle(fontSize: 16),
),
],
),
),
),
),
);
}
}

View file

@ -24,7 +24,9 @@ void startCheckConnectionReaction(WalletBase wallet, SettingsStore settingsStore
return;
}
if (wallet.syncStatus is LostConnectionSyncStatus || wallet.syncStatus is FailedSyncStatus) {
if (wallet.type != WalletType.bitcoin &&
(wallet.syncStatus is LostConnectionSyncStatus ||
wallet.syncStatus is FailedSyncStatus)) {
final alive = await settingsStore.getCurrentNode(wallet.type).requestNode();
if (alive) {

View file

@ -17,6 +17,8 @@ import 'package:cake_wallet/src/screens/backup/edit_backup_password_page.dart';
import 'package:cake_wallet/src/screens/buy/buy_options_page.dart';
import 'package:cake_wallet/src/screens/buy/buy_webview_page.dart';
import 'package:cake_wallet/src/screens/buy/webview_page.dart';
import 'package:cake_wallet/src/screens/cake_pay/auth/cake_pay_account_page.dart';
import 'package:cake_wallet/src/screens/cake_pay/cake_pay.dart';
import 'package:cake_wallet/src/screens/connect_device/connect_device_page.dart';
import 'package:cake_wallet/src/screens/connect_device/select_hardware_wallet_account_page.dart';
import 'package:cake_wallet/src/screens/contact/contact_list_page.dart';
@ -27,8 +29,8 @@ import 'package:cake_wallet/src/screens/dashboard/edit_token_page.dart';
import 'package:cake_wallet/src/screens/dashboard/home_settings_page.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/address_page.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/nft_details_page.dart';
import 'package:cake_wallet/src/screens/dashboard/sign_page.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/transactions_page.dart';
import 'package:cake_wallet/src/screens/dashboard/sign_page.dart';
import 'package:cake_wallet/src/screens/disclaimer/disclaimer_page.dart';
import 'package:cake_wallet/src/screens/exchange/exchange_page.dart';
import 'package:cake_wallet/src/screens/exchange/exchange_template_page.dart';
@ -43,10 +45,9 @@ import 'package:cake_wallet/src/screens/new_wallet/new_wallet_page.dart';
import 'package:cake_wallet/src/screens/new_wallet/new_wallet_type_page.dart';
import 'package:cake_wallet/src/screens/nodes/node_create_or_edit_page.dart';
import 'package:cake_wallet/src/screens/nodes/pow_node_create_or_edit_page.dart';
import 'package:cake_wallet/src/screens/receive/address_list_page.dart';
import 'package:cake_wallet/src/screens/restore/sweeping_wallet_page.dart';
import 'package:cake_wallet/src/screens/order_details/order_details_page.dart';
import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
import 'package:cake_wallet/src/screens/receive/address_list_page.dart';
import 'package:cake_wallet/src/screens/receive/anonpay_invoice_page.dart';
import 'package:cake_wallet/src/screens/receive/anonpay_receive_page.dart';
import 'package:cake_wallet/src/screens/receive/fullscreen_qr_page.dart';
@ -69,11 +70,9 @@ import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
import 'package:cake_wallet/src/screens/settings/other_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/privacy_page.dart';
import 'package:cake_wallet/src/screens/settings/security_backup_page.dart';
import 'package:cake_wallet/src/screens/cake_pay/auth/cake_pay_account_page.dart';
import 'package:cake_wallet/src/screens/settings/silent_payments_settings.dart';
import 'package:cake_wallet/src/screens/settings/tor_page.dart';
import 'package:cake_wallet/src/screens/settings/trocador_providers_page.dart';
import 'package:cake_wallet/src/screens/settings/tor_page.dart';
import 'package:cake_wallet/src/screens/setup_2fa/modify_2fa_page.dart';
import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa.dart';
import 'package:cake_wallet/src/screens/setup_2fa/setup_2fa_enter_code_page.dart';
@ -85,19 +84,18 @@ import 'package:cake_wallet/src/screens/support/support_page.dart';
import 'package:cake_wallet/src/screens/support_chat/support_chat_page.dart';
import 'package:cake_wallet/src/screens/support_other_links/support_other_links_page.dart';
import 'package:cake_wallet/src/screens/trade_details/trade_details_page.dart';
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_page.dart';
import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart';
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_page.dart';
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_details_page.dart';
import 'package:cake_wallet/src/screens/unspent_coins/unspent_coins_list_page.dart';
import 'package:cake_wallet/src/screens/wallet/wallet_edit_page.dart';
import 'package:cake_wallet/src/screens/wallet_connect/wc_connections_listing_view.dart';
import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart';
import 'package:cake_wallet/src/screens/wallet_list/wallet_list_page.dart';
import 'package:cake_wallet/src/screens/wallet_unlock/wallet_unlock_arguments.dart';
import 'package:cake_wallet/src/screens/wallet_unlock/wallet_unlock_page.dart';
import 'package:cake_wallet/src/screens/welcome/create_welcome_page.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/src/screens/wallet_unlock/wallet_unlock_arguments.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/utils/payment_request.dart';
import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
@ -106,7 +104,7 @@ import 'package:cake_wallet/view_model/dashboard/sign_view_model.dart';
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
import 'package:cake_wallet/view_model/monero_account_list/account_list_item.dart';
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
import 'package:cake_wallet/view_model/seed_type_view_model.dart';
import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
import 'package:cake_wallet/view_model/wallet_hardware_restore_view_model.dart';
import 'package:cake_wallet/view_model/wallet_new_vm.dart';
import 'package:cake_wallet/wallet_type_utils.dart';
@ -120,7 +118,7 @@ import 'package:cw_core/wallet_type.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:cake_wallet/src/screens/cake_pay/cake_pay.dart';
import 'src/screens/dashboard/pages/nft_import_page.dart';
late RouteSettings currentRouteSettings;
@ -135,7 +133,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.newWalletFromWelcome:
if (SettingsStoreBase.walletPasswordDirectInput) {
if (availableWalletTypes.length == 1) {
return createRoute(RouteSettings(name: Routes.newWallet, arguments: availableWalletTypes.first));
return createRoute(
RouteSettings(name: Routes.newWallet, arguments: availableWalletTypes.first));
} else {
return createRoute(RouteSettings(name: Routes.newWalletType));
}
@ -162,10 +161,10 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.newWallet:
final type = settings.arguments as WalletType;
final walletNewVM = getIt.get<WalletNewVM>(param1: type);
final seedTypeViewModel = getIt.get<SeedTypeViewModel>();
final seedSettingsViewModel = getIt.get<SeedSettingsViewModel>();
return CupertinoPageRoute<void>(
builder: (_) => NewWalletPage(walletNewVM, seedTypeViewModel));
builder: (_) => NewWalletPage(walletNewVM, seedSettingsViewModel));
case Routes.chooseHardwareWalletAccount:
final arguments = settings.arguments as List<dynamic>;
@ -348,16 +347,14 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.auth:
return MaterialPageRoute<void>(
fullscreenDialog: true,
builder: (_)
=> SettingsStoreBase.walletPasswordDirectInput
? getIt.get<WalletUnlockPage>(
param1: WalletUnlockArguments(
builder: (_) => SettingsStoreBase.walletPasswordDirectInput
? getIt.get<WalletUnlockPage>(
param1: WalletUnlockArguments(
callback: settings.arguments as OnAuthenticationFinished),
instanceName: 'wallet_unlock_verifiable',
param2: true)
: getIt.get<AuthPage>(
param1: settings.arguments as OnAuthenticationFinished,
param2: true));
instanceName: 'wallet_unlock_verifiable',
param2: true)
: getIt.get<AuthPage>(
param1: settings.arguments as OnAuthenticationFinished, param2: true));
case Routes.totpAuthCodePage:
final args = settings.arguments as TotpAuthArgumentsModel;
@ -371,28 +368,25 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.walletUnlockLoadable:
return MaterialPageRoute<void>(
fullscreenDialog: true,
builder: (_)
=> getIt.get<WalletUnlockPage>(
builder: (_) => getIt.get<WalletUnlockPage>(
param1: settings.arguments as WalletUnlockArguments,
instanceName: 'wallet_unlock_loadable',
instanceName: 'wallet_unlock_loadable',
param2: true));
case Routes.unlock:
return MaterialPageRoute<void>(
fullscreenDialog: true,
builder: (_)
=> SettingsStoreBase.walletPasswordDirectInput
? WillPopScope(
child: getIt.get<WalletUnlockPage>(
builder: (_) => SettingsStoreBase.walletPasswordDirectInput
? WillPopScope(
child: getIt.get<WalletUnlockPage>(
param1: WalletUnlockArguments(
callback: settings.arguments as OnAuthenticationFinished),
callback: settings.arguments as OnAuthenticationFinished),
param2: false,
instanceName: 'wallet_unlock_verifiable'),
onWillPop: () async => false)
: WillPopScope(
child: getIt.get<AuthPage>(
param1: settings.arguments as OnAuthenticationFinished,
param2: false),
onWillPop: () async => false)
: WillPopScope(
child: getIt.get<AuthPage>(
param1: settings.arguments as OnAuthenticationFinished, param2: false),
onWillPop: () async => false));
case Routes.silentPaymentsSettings:
@ -437,11 +431,12 @@ Route<dynamic> createRoute(RouteSettings settings) {
return CupertinoPageRoute<void>(
builder: (context) => WillPopScope(
child: SettingsStoreBase.walletPasswordDirectInput
? getIt.get<WalletUnlockPage>(instanceName: 'wallet_password_login')
: getIt.get<AuthPage>(instanceName: 'login'),
? getIt.get<WalletUnlockPage>(instanceName: 'wallet_password_login')
: getIt.get<AuthPage>(instanceName: 'login'),
onWillPop: () async =>
// FIX-ME: Additional check does it works correctly
(await SystemChannels.platform.invokeMethod<bool>('SystemNavigator.pop') ?? false)),
// FIX-ME: Additional check does it works correctly
(await SystemChannels.platform.invokeMethod<bool>('SystemNavigator.pop') ??
false)),
fullscreenDialog: true);
case Routes.newPowNode:
@ -537,8 +532,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.support:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<SupportPage>());
fullscreenDialog: true, builder: (_) => getIt.get<SupportPage>());
case Routes.supportLiveChat:
return CupertinoPageRoute<void>(builder: (_) => getIt.get<SupportChatPage>());
@ -567,8 +561,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.cakePayBuyCardPage:
final args = settings.arguments as List;
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<CakePayBuyCardPage>(param1: args));
return CupertinoPageRoute<void>(builder: (_) => getIt.get<CakePayBuyCardPage>(param1: args));
case Routes.cakePayBuyCardDetailPage:
final args = settings.arguments as List;
@ -582,7 +575,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.cakePayVerifyOtpPage:
final args = settings.arguments as List;
return CupertinoPageRoute<void>(builder: (_) => getIt.get<CakePayVerifyOtpPage>(param1: args));
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<CakePayVerifyOtpPage>(param1: args));
case Routes.cakePayAccountPage:
return CupertinoPageRoute<void>(builder: (_) => getIt.get<CakePayAccountPage>());
@ -597,16 +591,19 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.advancedPrivacySettings:
final args = settings.arguments as Map<String, dynamic>;
final type = args['type'] as WalletType;
final isFromRestore = args['isFromRestore'] as bool? ?? false;
final useTestnet = args['useTestnet'] as bool;
final toggleTestnet = args['toggleTestnet'] as Function(bool? val);
return CupertinoPageRoute<void>(
builder: (_) => AdvancedPrivacySettingsPage(
useTestnet,
toggleTestnet,
getIt.get<AdvancedPrivacySettingsViewModel>(param1: type),
getIt.get<NodeCreateOrEditViewModel>(param1: type, param2: false),
getIt.get<SeedTypeViewModel>(),
isFromRestore: isFromRestore,
useTestnet: useTestnet,
toggleUseTestnet: toggleTestnet,
advancedPrivacySettingsViewModel:
getIt.get<AdvancedPrivacySettingsViewModel>(param1: type),
nodeViewModel: getIt.get<NodeCreateOrEditViewModel>(param1: type, param2: false),
seedSettingsViewModel: getIt.get<SeedSettingsViewModel>(),
));
case Routes.anonPayInvoicePage:
@ -709,7 +706,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
getIt.get<SignViewModel>(),
),
);
case Routes.connectDevices:
final params = settings.arguments as ConnectDevicePageParams;
return MaterialPageRoute<void>(

View file

@ -1,4 +1,5 @@
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/utils/image_utill.dart';
import 'package:flutter/material.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
@ -36,7 +37,8 @@ class TradeRow extends StatelessWidget {
children: [
ClipRRect(
borderRadius: BorderRadius.circular(50),
child: Image.asset(provider.image, width: 36, height: 36)),
child: ImageUtil.getImageFromPath(
imagePath: provider.image, height: 36, width: 36)),
SizedBox(width: 12),
Expanded(
child: Column(

View file

@ -1,23 +1,7 @@
import 'package:cake_wallet/core/wallet_name_validator.dart';
import 'package:cake_wallet/entities/generate_name.dart';
import 'package:cake_wallet/entities/seed_type.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/address_text_field.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/widgets/blockchain_height_widget.dart';
import 'package:cake_wallet/src/widgets/picker.dart';
import 'package:cake_wallet/src/widgets/seed_language_picker.dart';
import 'package:cake_wallet/src/widgets/seed_widget.dart';
import 'package:cake_wallet/themes/extensions/address_theme.dart';
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/seed_type_view_model.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:mobx/mobx.dart';
import 'package:polyseed/polyseed.dart';
class VerifyForm extends StatefulWidget {
VerifyForm({

View file

@ -2,6 +2,7 @@ import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/store/dashboard/trades_store.dart';
import 'package:cake_wallet/themes/extensions/exchange_page_theme.dart';
import 'package:cake_wallet/utils/image_utill.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
@ -102,7 +103,8 @@ class ExchangeConfirmPage extends BasePage {
mainAxisAlignment: MainAxisAlignment.center,
children: [
(trade.provider.image?.isNotEmpty ?? false)
? Image.asset(trade.provider.image, height: 50)
? ImageUtil.getImageFromPath(
imagePath: trade.provider.image, width: 50)
: const SizedBox(),
if (!trade.provider.horizontalLogo)
Padding(

View file

@ -40,13 +40,11 @@ class NanoChangeRepPage extends BasePage {
(node) => node.account == currentRepAccount,
orElse: () => N2Node(
account: currentRepAccount,
alias: currentRepAccount,
score: 0,
uptime: "???",
weight: 0,
),
);
return currentNode;
}
@ -57,9 +55,7 @@ class NanoChangeRepPage extends BasePage {
child: FutureBuilder(
future: nano!.getN2Reps(_wallet),
builder: (context, snapshot) {
if (snapshot.data == null) {
return SizedBox();
}
final reps = snapshot.data ?? [];
return Container(
padding: EdgeInsets.only(left: 24, right: 24),
@ -101,29 +97,35 @@ class NanoChangeRepPage extends BasePage {
),
_buildSingleRepresentative(
context,
getCurrentRepNode(snapshot.data as List<N2Node>),
getCurrentRepNode(reps),
isList: false,
divider: false,
),
Divider(height: 20),
Container(
margin: EdgeInsets.only(top: 12),
child: Text(
S.current.nano_pick_new_rep,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
if (reps.isNotEmpty) ...[
Divider(height: 20),
Container(
margin: EdgeInsets.only(top: 12),
child: Text(
S.current.nano_pick_new_rep,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
),
),
),
),
Divider(height: 20),
],
],
),
],
),
contentPadding: EdgeInsets.only(bottom: 24),
content: Container(
child: Column(
children: _getRepresentativeWidgets(context, snapshot.data as List<N2Node>),
),
child: reps.isNotEmpty
? Column(
children: _getRepresentativeWidgets(context, reps),
)
: SizedBox(),
),
bottomSectionPadding: EdgeInsets.only(bottom: 24),
bottomSection: Observer(
@ -207,19 +209,22 @@ class NanoChangeRepPage extends BasePage {
final List<Widget> ret = [];
for (final N2Node node in list) {
if (node.alias != null && node.alias!.trim().isNotEmpty) {
ret.add(_buildSingleRepresentative(context, node));
bool divider = node != list.first;
ret.add(_buildSingleRepresentative(context, node, divider: divider, isList: true));
}
}
return ret;
}
Widget _buildSingleRepresentative(BuildContext context, N2Node rep, {bool isList = true}) {
Widget _buildSingleRepresentative(
BuildContext context,
N2Node rep, {
bool isList = true,
bool divider = false,
}) {
return Column(
children: <Widget>[
if (isList)
Divider(
height: 2,
),
if (divider) Divider(height: 2),
TextButton(
style: TextButton.styleFrom(
padding: EdgeInsets.zero,
@ -244,11 +249,11 @@ class NanoChangeRepPage extends BasePage {
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
_sanitizeAlias(rep.alias),
rep.alias ?? rep.account!,
style: TextStyle(
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
fontWeight: FontWeight.w700,
fontSize: 18,
fontSize: rep.alias == null ? 14 : 18,
),
),
Container(
@ -337,11 +342,4 @@ class NanoChangeRepPage extends BasePage {
],
);
}
String _sanitizeAlias(String? alias) {
if (alias != null) {
return alias.replaceAll(RegExp(r'[^a-zA-Z_.!?_;:-]'), '');
}
return '';
}
}

View file

@ -3,52 +3,61 @@ import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart';
import 'package:cake_wallet/entities/seed_phrase_length.dart';
import 'package:cake_wallet/entities/seed_type.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/nodes/widgets/node_form.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_choices_cell.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
import 'package:cake_wallet/themes/extensions/new_wallet_theme.dart';
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart';
import 'package:cake_wallet/view_model/seed_type_view_model.dart';
import 'package:cake_wallet/view_model/settings/choices_list_item.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/themes/extensions/new_wallet_theme.dart';
import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart';
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
import 'package:cake_wallet/view_model/settings/choices_list_item.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class AdvancedPrivacySettingsPage extends BasePage {
AdvancedPrivacySettingsPage(this.useTestnet, this.toggleUseTestnet,
this.advancedPrivacySettingsViewModel, this.nodeViewModel, this.seedTypeViewModel);
AdvancedPrivacySettingsPage({
required this.isFromRestore,
required this.useTestnet,
required this.toggleUseTestnet,
required this.advancedPrivacySettingsViewModel,
required this.nodeViewModel,
required this.seedSettingsViewModel,
});
final AdvancedPrivacySettingsViewModel advancedPrivacySettingsViewModel;
final NodeCreateOrEditViewModel nodeViewModel;
final SeedTypeViewModel seedTypeViewModel;
final SeedSettingsViewModel seedSettingsViewModel;
@override
String get title => S.current.privacy_settings;
final bool isFromRestore;
final bool useTestnet;
final Function(bool? val) toggleUseTestnet;
@override
Widget body(BuildContext context) => AdvancedPrivacySettingsBody(useTestnet, toggleUseTestnet,
advancedPrivacySettingsViewModel, nodeViewModel, seedTypeViewModel);
Widget body(BuildContext context) => _AdvancedPrivacySettingsBody(isFromRestore, useTestnet,
toggleUseTestnet, advancedPrivacySettingsViewModel, nodeViewModel, seedSettingsViewModel);
}
class AdvancedPrivacySettingsBody extends StatefulWidget {
const AdvancedPrivacySettingsBody(this.useTestnet, this.toggleUseTestnet,
class _AdvancedPrivacySettingsBody extends StatefulWidget {
const _AdvancedPrivacySettingsBody(this.isFromRestore, this.useTestnet, this.toggleUseTestnet,
this.privacySettingsViewModel, this.nodeViewModel, this.seedTypeViewModel,
{Key? key})
: super(key: key);
final AdvancedPrivacySettingsViewModel privacySettingsViewModel;
final NodeCreateOrEditViewModel nodeViewModel;
final SeedTypeViewModel seedTypeViewModel;
final SeedSettingsViewModel seedTypeViewModel;
final bool isFromRestore;
final bool useTestnet;
final Function(bool? val) toggleUseTestnet;
@ -56,15 +65,25 @@ class AdvancedPrivacySettingsBody extends StatefulWidget {
_AdvancedPrivacySettingsBodyState createState() => _AdvancedPrivacySettingsBodyState();
}
class _AdvancedPrivacySettingsBodyState extends State<AdvancedPrivacySettingsBody> {
_AdvancedPrivacySettingsBodyState();
class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBody> {
final TextEditingController passphraseController = TextEditingController();
final _formKey = GlobalKey<FormState>();
bool? testnetValue;
bool obscurePassphrase = true;
@override
void initState() {
passphraseController.text = widget.seedTypeViewModel.passphrase ?? '';
passphraseController
.addListener(() => widget.seedTypeViewModel.setPassphrase(passphraseController.text));
super.initState();
}
@override
Widget build(BuildContext context) {
if (testnetValue == null && widget.useTestnet != null) {
if (testnetValue == null && widget.useTestnet) {
testnetValue = widget.useTestnet;
}
@ -97,6 +116,61 @@ class _AdvancedPrivacySettingsBodyState extends State<AdvancedPrivacySettingsBod
),
);
}),
if (widget.privacySettingsViewModel.hasSeedTypeOption)
Observer(builder: (_) {
return SettingsChoicesCell(
ChoicesListItem<MoneroSeedType>(
title: S.current.seedtype,
items: MoneroSeedType.all,
selectedItem: widget.seedTypeViewModel.moneroSeedType,
onItemSelected: widget.seedTypeViewModel.setMoneroSeedType,
),
);
}),
if ([WalletType.bitcoin, WalletType.litecoin]
.contains(widget.privacySettingsViewModel.type))
Observer(builder: (_) {
return SettingsChoicesCell(
ChoicesListItem<BitcoinSeedType>(
title: S.current.seedtype,
items: BitcoinSeedType.all,
selectedItem: widget.seedTypeViewModel.bitcoinSeedType,
onItemSelected: widget.seedTypeViewModel.setBitcoinSeedType,
),
);
}),
if (!widget.isFromRestore) ...[
Observer(builder: (_) {
if (widget.privacySettingsViewModel.hasSeedPhraseLengthOption)
return SettingsPickerCell<SeedPhraseLength>(
title: S.current.seed_phrase_length,
items: SeedPhraseLength.values,
selectedItem: widget.privacySettingsViewModel.seedPhraseLength,
onItemSelected: (SeedPhraseLength length) {
widget.privacySettingsViewModel.setSeedPhraseLength(length);
},
);
return Container();
}),
if (widget.privacySettingsViewModel.hasPassphraseOption)
Padding(
padding: EdgeInsets.all(24),
child: BaseTextFormField(
hintText: S.current.passphrase,
controller: passphraseController,
obscureText: obscurePassphrase,
suffixIcon: GestureDetector(
onTap: () => setState(() {
obscurePassphrase = !obscurePassphrase;
}),
child: Icon(
Icons.remove_red_eye,
color: obscurePassphrase ? Colors.black54 : Colors.black26,
),
),
),
),
],
Observer(builder: (_) {
return Column(
children: [
@ -122,31 +196,9 @@ class _AdvancedPrivacySettingsBodyState extends State<AdvancedPrivacySettingsBod
],
);
}),
if (widget.privacySettingsViewModel.hasSeedPhraseLengthOption)
Observer(builder: (_) {
return SettingsPickerCell<SeedPhraseLength>(
title: S.current.seed_phrase_length,
items: SeedPhraseLength.values,
selectedItem: widget.privacySettingsViewModel.seedPhraseLength,
onItemSelected: (SeedPhraseLength length) {
widget.privacySettingsViewModel.setSeedPhraseLength(length);
},
);
}),
if (widget.privacySettingsViewModel.hasSeedTypeOption)
Observer(builder: (_) {
return SettingsChoicesCell(
ChoicesListItem<SeedType>(
title: S.current.seedtype,
items: SeedType.all,
selectedItem: widget.seedTypeViewModel.moneroSeedType,
onItemSelected: widget.seedTypeViewModel.setMoneroSeedType,
),
);
}),
if (widget.privacySettingsViewModel.type == WalletType.bitcoin)
Builder(builder: (_) {
final val = testnetValue!;
final val = testnetValue ?? false;
return SettingsSwitcherCell(
title: S.current.use_testnet,
value: val,
@ -154,7 +206,7 @@ class _AdvancedPrivacySettingsBodyState extends State<AdvancedPrivacySettingsBod
setState(() {
testnetValue = !val;
});
widget.toggleUseTestnet!.call(testnetValue);
widget.toggleUseTestnet.call(testnetValue);
});
}),
],
@ -203,4 +255,11 @@ class _AdvancedPrivacySettingsBodyState extends State<AdvancedPrivacySettingsBod
),
);
}
@override
void dispose() {
passphraseController
.removeListener(() => widget.seedTypeViewModel.setPassphrase(passphraseController.text));
super.dispose();
}
}

View file

@ -1,36 +1,35 @@
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/core/wallet_name_validator.dart';
import 'package:cake_wallet/entities/generate_name.dart';
import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart';
import 'package:cake_wallet/src/widgets/picker.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/entities/seed_type.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/main.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/seed_type_view_model.dart';
import 'package:mobx/mobx.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/core/wallet_name_validator.dart';
import 'package:cake_wallet/src/widgets/seed_language_selector.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/picker.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/src/widgets/seed_language_picker.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/view_model/wallet_new_vm.dart';
import 'package:cake_wallet/src/widgets/seed_language_selector.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/themes/extensions/new_wallet_theme.dart';
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
import 'package:cake_wallet/entities/seed_type.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
import 'package:cake_wallet/view_model/wallet_new_vm.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
class NewWalletPage extends BasePage {
NewWalletPage(this._walletNewVM, this._seedTypeViewModel);
NewWalletPage(this._walletNewVM, this._seedSettingsViewModel);
final WalletNewVM _walletNewVM;
final SeedTypeViewModel _seedTypeViewModel;
final SeedSettingsViewModel _seedSettingsViewModel;
final walletNameImage = Image.asset('assets/images/wallet_name.png');
@ -51,15 +50,15 @@ class NewWalletPage extends BasePage {
Widget body(BuildContext context) => WalletNameForm(
_walletNewVM,
currentTheme.type == ThemeType.dark ? walletNameImage : walletNameLightImage,
_seedTypeViewModel);
_seedSettingsViewModel);
}
class WalletNameForm extends StatefulWidget {
WalletNameForm(this._walletNewVM, this.walletImage, this._seedTypeViewModel);
WalletNameForm(this._walletNewVM, this.walletImage, this._seedSettingsViewModel);
final WalletNewVM _walletNewVM;
final Image walletImage;
final SeedTypeViewModel _seedTypeViewModel;
final SeedSettingsViewModel _seedSettingsViewModel;
@override
_WalletNameFormState createState() => _WalletNameFormState(_walletNewVM);
@ -113,7 +112,7 @@ class _WalletNameFormState extends State<WalletNameForm> {
}
});
_setSeedType(SeedType.defaultSeedType);
_setSeedType(MoneroSeedType.defaultSeedType);
super.initState();
}
@ -291,12 +290,12 @@ class _WalletNameFormState extends State<WalletNameForm> {
padding: EdgeInsets.only(top: 24),
child: SelectButton(
key: ValueKey('new_wallet_page_monero_seed_type_button_key'),
text: widget._seedTypeViewModel.moneroSeedType.title,
text: widget._seedSettingsViewModel.moneroSeedType.title,
onTap: () async {
await showPopUp<void>(
context: context,
builder: (_) => Picker(
items: SeedType.all,
items: MoneroSeedType.all,
selectedAtIndex: isPolyseed ? 1 : 0,
onItemSelected: _setSeedType,
isSeparated: false,
@ -315,8 +314,8 @@ class _WalletNameFormState extends State<WalletNameForm> {
buttonKey: ValueKey('new_wallet_page_seed_language_selector_button_key'),
initialSelected: defaultSeedLanguage,
seedType: _walletNewVM.hasSeedType
? widget._seedTypeViewModel.moneroSeedType
: SeedType.legacy,
? widget._seedSettingsViewModel.moneroSeedType
: MoneroSeedType.legacy,
),
),
)
@ -389,10 +388,10 @@ class _WalletNameFormState extends State<WalletNameForm> {
_formProcessing = false;
}
bool get isPolyseed => widget._seedTypeViewModel.moneroSeedType == SeedType.polyseed;
bool get isPolyseed => widget._seedSettingsViewModel.moneroSeedType == MoneroSeedType.polyseed;
void _setSeedType(SeedType item) {
widget._seedTypeViewModel.setMoneroSeedType(item);
void _setSeedType(MoneroSeedType item) {
widget._seedSettingsViewModel.setMoneroSeedType(item);
_languageSelectorKey.currentState?.selected = defaultSeedLanguage; // Reset Seed language
}
}

View file

@ -9,35 +9,34 @@ import 'package:cake_wallet/src/widgets/seed_language_picker.dart';
import 'package:cake_wallet/src/widgets/seed_widget.dart';
import 'package:cake_wallet/themes/extensions/send_page_theme.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/seed_type_view_model.dart';
import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:mobx/mobx.dart';
import 'package:polyseed/polyseed.dart';
class WalletRestoreFromSeedForm extends StatefulWidget {
WalletRestoreFromSeedForm(
{Key? key,
required this.displayLanguageSelector,
required this.displayBlockHeightSelector,
required this.displayPassphrase,
required this.type,
required this.displayWalletPassword,
required this.seedTypeViewModel,
this.blockHeightFocusNode,
this.onHeightOrDateEntered,
this.onSeedChange,
this.onLanguageChange,
this.onPasswordChange,
this.onRepeatedPasswordChange})
: super(key: key);
WalletRestoreFromSeedForm({Key? key,
required this.displayLanguageSelector,
required this.displayBlockHeightSelector,
required this.displayPassphrase,
required this.type,
required this.displayWalletPassword,
required this.seedSettingsViewModel,
this.blockHeightFocusNode,
this.onHeightOrDateEntered,
this.onSeedChange,
this.onLanguageChange,
this.onPasswordChange,
this.onRepeatedPasswordChange,
}) : super(key: key);
final WalletType type;
final bool displayLanguageSelector;
final bool displayBlockHeightSelector;
final bool displayWalletPassword;
final bool displayPassphrase;
final SeedTypeViewModel seedTypeViewModel;
final SeedSettingsViewModel seedSettingsViewModel;
final FocusNode? blockHeightFocusNode;
final Function(bool)? onHeightOrDateEntered;
final void Function(String)? onSeedChange;
@ -58,7 +57,9 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
languageController = TextEditingController(),
nameTextEditingController = TextEditingController(),
passwordTextEditingController = displayWalletPassword ? TextEditingController() : null,
repeatedPasswordTextEditingController = displayWalletPassword ? TextEditingController() : null,
repeatedPasswordTextEditingController = displayWalletPassword
? TextEditingController()
: null,
passphraseController = TextEditingController(),
seedTypeController = TextEditingController();
@ -75,10 +76,11 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
String language;
void Function()? passwordListener;
void Function()? repeatedPasswordListener;
void Function()? passphraseListener;
@override
void initState() {
_setSeedType(widget.seedTypeViewModel.moneroSeedType);
_setSeedType(widget.seedSettingsViewModel.moneroSeedType);
_setLanguageLabel(language);
if (passwordTextEditingController != null) {
@ -87,14 +89,19 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
}
if (repeatedPasswordTextEditingController != null) {
repeatedPasswordListener = () => widget.onRepeatedPasswordChange?.call(repeatedPasswordTextEditingController!.text);
repeatedPasswordListener =
() => widget.onRepeatedPasswordChange?.call(repeatedPasswordTextEditingController!.text);
repeatedPasswordTextEditingController?.addListener(repeatedPasswordListener!);
}
passphraseListener = () => widget.seedSettingsViewModel.setPassphrase(passphraseController.text);
passphraseController.addListener(passphraseListener!);
moneroSeedTypeReaction =
reaction((_) => widget.seedTypeViewModel.moneroSeedType, (SeedType item) {
_setSeedType(item);
_changeLanguage('English');
});
reaction((_) => widget.seedSettingsViewModel.moneroSeedType, (MoneroSeedType item) {
_setSeedType(item);
_changeLanguage('English');
});
super.initState();
}
@ -110,6 +117,9 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
if (repeatedPasswordListener != null) {
repeatedPasswordTextEditingController?.removeListener(repeatedPasswordListener!);
}
passphraseController.removeListener(passphraseListener!);
super.dispose();
}
@ -118,11 +128,13 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
Polyseed.isValidSeed(seed)) {
final lang = PolyseedLang.getByPhrase(seed);
_changeSeedType(SeedType.polyseed);
_changeSeedType(MoneroSeedType.polyseed);
_changeLanguage(lang.nameEnglish);
}
if (widget.type == WalletType.wownero && seed.split(" ").length == 14) {
_changeSeedType(SeedType.wowneroSeed);
if (widget.type == WalletType.wownero && seed
.split(" ")
.length == 14) {
_changeSeedType(MoneroSeedType.wowneroSeed);
_changeLanguage("English");
}
widget.onSeedChange?.call(seed);
@ -141,7 +153,9 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
BaseTextFormField(
key: ValueKey('wallet_restore_from_seed_wallet_name_textfield_key'),
controller: nameTextEditingController,
hintText: S.of(context).wallet_name,
hintText: S
.of(context)
.wallet_name,
suffixIcon: IconButton(
key: ValueKey('wallet_restore_from_seed_wallet_name_refresh_button_key'),
onPressed: () async {
@ -158,7 +172,9 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(6.0),
color: Theme.of(context).hintColor,
color: Theme
.of(context)
.hintColor,
),
width: 34,
height: 34,
@ -188,13 +204,14 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
onTap: () async {
await showPopUp<void>(
context: context,
builder: (_) => Picker(
builder: (_) =>
Picker(
items: _getItems(),
selectedAtIndex: isPolyseed
? 1
: seedTypeController.value.text.contains("14")
? 2
: 0,
? 2
: 0,
mainAxisAlignment: MainAxisAlignment.start,
onItemSelected: _changeSeedType,
isSeparated: false,
@ -216,37 +233,43 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
if (widget.displayWalletPassword)
...[BaseTextFormField(
controller: passwordTextEditingController,
hintText: S.of(context).password,
hintText: S
.of(context)
.password,
obscureText: true),
BaseTextFormField(
controller: repeatedPasswordTextEditingController,
hintText: S.of(context).repeat_wallet_password,
obscureText: true)],
hintText: S
.of(context)
.repeat_wallet_password,
obscureText: true)
],
if (widget.displayLanguageSelector)
if (!seedTypeController.value.text.contains("14") && widget.displayLanguageSelector)
GestureDetector(
onTap: () async {
await showPopUp<void>(
context: context,
builder: (_) => SeedLanguagePicker(
selected: language,
onItemSelected: _changeLanguage,
seedType: isPolyseed ? SeedType.polyseed : SeedType.legacy,
));
},
child: Container(
color: Colors.transparent,
padding: EdgeInsets.only(top: 20.0),
child: IgnorePointer(
child: BaseTextFormField(
controller: languageController,
enableInteractiveSelection: false,
readOnly: true,
suffixIcon: expandIcon,
if (!seedTypeController.value.text.contains("14") && widget.displayLanguageSelector)
GestureDetector(
onTap: () async {
await showPopUp<void>(
context: context,
builder: (_) =>
SeedLanguagePicker(
selected: language,
onItemSelected: _changeLanguage,
seedType: isPolyseed ? MoneroSeedType.polyseed : MoneroSeedType.legacy,
));
},
child: Container(
color: Colors.transparent,
padding: EdgeInsets.only(top: 20.0),
child: IgnorePointer(
child: BaseTextFormField(
controller: languageController,
enableInteractiveSelection: false,
readOnly: true,
suffixIcon: expandIcon,
),
),
),
),
),
if ((!isPolyseed) && widget.displayBlockHeightSelector)
BlockchainHeightWidget(
focusNode: widget.blockHeightFocusNode,
@ -267,17 +290,20 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
}
bool get isPolyseed =>
widget.seedTypeViewModel.moneroSeedType == SeedType.polyseed &&
(widget.type == WalletType.monero || widget.type == WalletType.wownero);
widget.seedSettingsViewModel.moneroSeedType == MoneroSeedType.polyseed &&
(widget.type == WalletType.monero || widget.type == WalletType.wownero);
Widget get expandIcon => Container(
Widget get expandIcon =>
Container(
padding: EdgeInsets.all(18),
width: 24,
height: 24,
child: Image.asset(
'assets/images/arrow_bottom_purple_icon.png',
height: 8,
color: Theme.of(context).hintColor,
color: Theme
.of(context)
.hintColor,
),
);
@ -285,8 +311,8 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
final setLang = isPolyseed
? "POLYSEED_$language"
: seedTypeController.value.text.contains("14")
? "WOWSEED_" + language
: language;
? "WOWSEED_" + language
: language;
setState(() {
this.language = setLang;
seedWidgetStateKey.currentState!.changeSeedLanguage(setLang);
@ -298,24 +324,24 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
void _setLanguageLabel(String language) =>
languageController.text = '${language.replaceAll("POLYSEED_", "")} (Seed language)';
void _changeSeedType(SeedType item) {
void _changeSeedType(MoneroSeedType item) {
_setSeedType(item);
_changeLanguage('English');
widget.seedTypeViewModel.setMoneroSeedType(item);
widget.seedSettingsViewModel.setMoneroSeedType(item);
}
void _setSeedType(SeedType item) {
void _setSeedType(MoneroSeedType item) {
seedTypeController.text = item.toString();
}
List<SeedType> _getItems() {
List<MoneroSeedType> _getItems() {
switch (widget.type) {
case WalletType.monero:
return [SeedType.legacy, SeedType.polyseed];
return [MoneroSeedType.legacy, MoneroSeedType.polyseed];
case WalletType.wownero:
return [SeedType.legacy, SeedType.polyseed, SeedType.wowneroSeed];
return [MoneroSeedType.legacy, MoneroSeedType.polyseed, MoneroSeedType.wowneroSeed];
default:
return [SeedType.legacy];
return [MoneroSeedType.legacy];
}
}
}

View file

@ -12,7 +12,7 @@ import 'package:cake_wallet/themes/extensions/wallet_list_theme.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/restore/restore_mode.dart';
import 'package:cake_wallet/view_model/seed_type_view_model.dart';
import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
import 'package:cake_wallet/view_model/wallet_restore_view_model.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
@ -23,7 +23,7 @@ import 'package:mobx/mobx.dart';
import 'package:smooth_page_indicator/smooth_page_indicator.dart';
class WalletRestorePage extends BasePage {
WalletRestorePage(this.walletRestoreViewModel, this.seedTypeViewModel)
WalletRestorePage(this.walletRestoreViewModel, this.seedSettingsViewModel)
: walletRestoreFromSeedFormKey = GlobalKey<WalletRestoreFromSeedFormState>(),
walletRestoreFromKeysFormKey = GlobalKey<WalletRestoreFromKeysFromState>(),
_pages = [],
@ -33,7 +33,7 @@ class WalletRestorePage extends BasePage {
switch (mode) {
case WalletRestoreMode.seed:
_pages.add(WalletRestoreFromSeedForm(
seedTypeViewModel: seedTypeViewModel,
seedSettingsViewModel: seedSettingsViewModel,
displayBlockHeightSelector:
walletRestoreViewModel.hasBlockchainHeightLanguageSelector,
displayLanguageSelector: walletRestoreViewModel.hasSeedLanguageSelector,
@ -96,7 +96,7 @@ class WalletRestorePage extends BasePage {
));
final WalletRestoreViewModel walletRestoreViewModel;
final SeedTypeViewModel seedTypeViewModel;
final SeedSettingsViewModel seedSettingsViewModel;
final PageController _controller;
final List<Widget> _pages;
final GlobalKey<WalletRestoreFromSeedFormState> walletRestoreFromSeedFormKey;
@ -235,6 +235,7 @@ class WalletRestorePage extends BasePage {
onTap: () {
Navigator.of(context)
.pushNamed(Routes.advancedPrivacySettings, arguments: {
'isFromRestore': true,
'type': walletRestoreViewModel.type,
'useTestnet': walletRestoreViewModel.useTestnet,
'toggleTestnet': walletRestoreViewModel.toggleUseTestnet
@ -324,8 +325,7 @@ class WalletRestorePage extends BasePage {
}
if (walletRestoreViewModel.hasPassphrase) {
credentials['passphrase'] =
walletRestoreFromSeedFormKey.currentState!.passphraseController.text;
credentials['passphrase'] = seedSettingsViewModel.passphrase;
}
credentials['name'] =
@ -408,26 +408,19 @@ class WalletRestorePage extends BasePage {
) as DerivationInfo?;
} else if (derivationsWithHistory == 1) {
dInfo = derivations[derivationWithHistoryIndex];
}
// get the default derivation for this wallet type:
if (dInfo == null) {
} else if (derivations.length == 1) {
// we only return 1 derivation if we're pretty sure we know which one to use:
if (derivations.length == 1) {
dInfo = derivations.first;
} else {
// if we have multiple possible derivations, and none have histories
// we just default to the most common one:
dInfo = walletRestoreViewModel.getCommonRestoreDerivation();
}
dInfo = derivations.first;
} else {
// if we have multiple possible derivations, and none (or multiple) have histories
// we just default to the most common one:
dInfo = walletRestoreViewModel.getCommonRestoreDerivation();
}
this.derivationInfo = dInfo;
if (this.derivationInfo == null) {
this.derivationInfo = walletRestoreViewModel.getDefaultDerivation();
}
await walletRestoreViewModel.create(options: _credentials());
seedSettingsViewModel.setPassphrase(null);
} catch (e) {
_formProcessing = false;
rethrow;

View file

@ -274,6 +274,7 @@ class SendPage extends BasePage {
? template.cryptoCurrency
: template.fiatCurrency,
onTap: () async {
sendViewModel.state = IsExecutingState();
if (template.additionalRecipients?.isNotEmpty ?? false) {
sendViewModel.clearOutputs();
@ -302,6 +303,7 @@ class SendPage extends BasePage {
template: template,
);
}
sendViewModel.state = InitialExecutionState();
},
onRemove: () {
showPopUp<void>(
@ -373,6 +375,7 @@ class SendPage extends BasePage {
return LoadingPrimaryButton(
key: ValueKey('send_page_send_button_key'),
onPressed: () async {
if (sendViewModel.state is IsExecutingState) return;
if (_formKey.currentState != null && !_formKey.currentState!.validate()) {
if (sendViewModel.outputs.length > 1) {
showErrorValidationAlert(context);

View file

@ -6,7 +6,9 @@ import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_choices_cell.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/settings/choices_list_item.dart';
import 'package:cake_wallet/view_model/settings/privacy_settings_view_model.dart';
import 'package:flutter/material.dart';
@ -111,6 +113,33 @@ class PrivacyPage extends BasePage {
_privacySettingsViewModel.setUseTronGrid(value);
},
),
if (_privacySettingsViewModel.canUseMempoolFeeAPI)
SettingsSwitcherCell(
title: S.current.live_fee_rates,
value: _privacySettingsViewModel.useMempoolFeeAPI,
onValueChange: (BuildContext _, bool isEnabled) async {
if (!isEnabled) {
final bool confirmation = await showPopUp<bool>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).warning,
alertContent: S.of(context).disable_fee_api_warning,
rightButtonText: S.of(context).confirm,
leftButtonText: S.of(context).cancel,
actionRightButton: () => Navigator.of(context).pop(true),
actionLeftButton: () => Navigator.of(context).pop(false));
}) ??
false;
if (confirmation) {
_privacySettingsViewModel.setUseMempoolFeeAPI(isEnabled);
}
return;
}
_privacySettingsViewModel.setUseMempoolFeeAPI(isEnabled);
},
),
SettingsCellWithArrow(
title: S.current.domain_looks_up,
handler: (context) => Navigator.of(context).pushNamed(Routes.domainLookupsPage),

View file

@ -113,10 +113,6 @@ class CheckBoxPickerState extends State<CheckBoxPicker> {
return GestureDetector(
onTap: () {
if (item.isDisabled) {
return;
}
bool newValue = !item.value;
item.value = newValue;
widget.onChanged(index, newValue);
@ -134,7 +130,7 @@ class CheckBoxPickerState extends State<CheckBoxPicker> {
borderColor: Theme.of(context).dividerColor,
iconColor: Colors.white,
onChanged: (bool? value) {
if (value == null || item.isDisabled) {
if (value == null) {
return;
}

View file

@ -11,36 +11,36 @@ class SeedLanguagePickerOption {
final String name;
final String nameLocalized;
final Image image;
final List<SeedType> supportedSeedTypes;
final List<MoneroSeedType> supportedSeedTypes;
}
final List<SeedLanguagePickerOption> seedLanguages = [
SeedLanguagePickerOption('English', S.current.seed_language_english,
Image.asset('assets/images/flags/usa.png'), [SeedType.legacy, SeedType.polyseed]),
Image.asset('assets/images/flags/usa.png'), [MoneroSeedType.legacy, MoneroSeedType.polyseed]),
SeedLanguagePickerOption('Chinese (Simplified)', S.current.seed_language_chinese,
Image.asset('assets/images/flags/chn.png'), [SeedType.legacy, SeedType.polyseed]),
Image.asset('assets/images/flags/chn.png'), [MoneroSeedType.legacy, MoneroSeedType.polyseed]),
SeedLanguagePickerOption('Chinese (Traditional)', S.current.seed_language_chinese_traditional,
Image.asset('assets/images/flags/chn.png'), [SeedType.polyseed]),
Image.asset('assets/images/flags/chn.png'), [MoneroSeedType.polyseed]),
SeedLanguagePickerOption('Dutch', S.current.seed_language_dutch,
Image.asset('assets/images/flags/nld.png'), [SeedType.legacy]),
Image.asset('assets/images/flags/nld.png'), [MoneroSeedType.legacy]),
SeedLanguagePickerOption('German', S.current.seed_language_german,
Image.asset('assets/images/flags/deu.png'), [SeedType.legacy]),
Image.asset('assets/images/flags/deu.png'), [MoneroSeedType.legacy]),
SeedLanguagePickerOption('Japanese', S.current.seed_language_japanese,
Image.asset('assets/images/flags/jpn.png'), [SeedType.legacy, SeedType.polyseed]),
Image.asset('assets/images/flags/jpn.png'), [MoneroSeedType.legacy, MoneroSeedType.polyseed]),
SeedLanguagePickerOption('Korean', S.current.seed_language_korean,
Image.asset('assets/images/flags/kor.png'), [SeedType.polyseed]),
Image.asset('assets/images/flags/kor.png'), [MoneroSeedType.polyseed]),
SeedLanguagePickerOption('Portuguese', S.current.seed_language_portuguese,
Image.asset('assets/images/flags/prt.png'), [SeedType.legacy, SeedType.polyseed]),
Image.asset('assets/images/flags/prt.png'), [MoneroSeedType.legacy, MoneroSeedType.polyseed]),
SeedLanguagePickerOption('Russian', S.current.seed_language_russian,
Image.asset('assets/images/flags/rus.png'), [SeedType.legacy]),
Image.asset('assets/images/flags/rus.png'), [MoneroSeedType.legacy]),
SeedLanguagePickerOption('Czech', S.current.seed_language_czech,
Image.asset('assets/images/flags/czk.png'), [SeedType.polyseed]),
Image.asset('assets/images/flags/czk.png'), [MoneroSeedType.polyseed]),
SeedLanguagePickerOption('Spanish', S.current.seed_language_spanish,
Image.asset('assets/images/flags/esp.png'), [SeedType.legacy, SeedType.polyseed]),
Image.asset('assets/images/flags/esp.png'), [MoneroSeedType.legacy, MoneroSeedType.polyseed]),
SeedLanguagePickerOption('French', S.current.seed_language_french,
Image.asset('assets/images/flags/fra.png'), [SeedType.legacy, SeedType.polyseed]),
Image.asset('assets/images/flags/fra.png'), [MoneroSeedType.legacy, MoneroSeedType.polyseed]),
SeedLanguagePickerOption('Italian', S.current.seed_language_italian,
Image.asset('assets/images/flags/ita.png'), [SeedType.legacy, SeedType.polyseed]),
Image.asset('assets/images/flags/ita.png'), [MoneroSeedType.legacy, MoneroSeedType.polyseed]),
];
const defaultSeedLanguage = 'English';
@ -51,11 +51,11 @@ class SeedLanguagePicker extends StatefulWidget {
SeedLanguagePicker(
{Key? key,
this.selected = defaultSeedLanguage,
this.seedType = SeedType.defaultSeedType,
this.seedType = MoneroSeedType.defaultSeedType,
required this.onItemSelected})
: super(key: key);
final SeedType seedType;
final MoneroSeedType seedType;
final String selected;
final Function(String) onItemSelected;
@ -68,7 +68,7 @@ class SeedLanguagePickerState extends State<SeedLanguagePicker> {
SeedLanguagePickerState(
{required this.selected, required this.onItemSelected, required this.seedType});
final SeedType seedType;
final MoneroSeedType seedType;
final String selected;
final Function(String) onItemSelected;

View file

@ -8,13 +8,13 @@ import 'package:flutter/material.dart';
class SeedLanguageSelector extends StatefulWidget {
SeedLanguageSelector({
required this.initialSelected,
this.seedType = SeedType.defaultSeedType,
this.seedType = MoneroSeedType.defaultSeedType,
this.buttonKey,
Key? key,
}) : super(key: key);
final String initialSelected;
final SeedType seedType;
final MoneroSeedType seedType;
final Key? buttonKey;
@override

View file

@ -16,7 +16,9 @@ abstract class TradeFilterStoreBase with Store {
displaySimpleSwap = true,
displayTrocador = true,
displayExolix = true,
displayThorChain = true;
displayThorChain = true,
displayLetsExchange = true,
displayStealthEx = true;
@observable
bool displayXMRTO;
@ -42,6 +44,12 @@ abstract class TradeFilterStoreBase with Store {
@observable
bool displayThorChain;
@observable
bool displayLetsExchange;
@observable
bool displayStealthEx;
@computed
bool get displayAllTrades =>
displayChangeNow &&
@ -49,7 +57,9 @@ abstract class TradeFilterStoreBase with Store {
displaySimpleSwap &&
displayTrocador &&
displayExolix &&
displayThorChain;
displayThorChain &&
displayLetsExchange &&
displayStealthEx;
@action
void toggleDisplayExchange(ExchangeProviderDescription provider) {
@ -78,6 +88,11 @@ abstract class TradeFilterStoreBase with Store {
case ExchangeProviderDescription.thorChain:
displayThorChain = !displayThorChain;
break;
case ExchangeProviderDescription.letsExchange:
displayLetsExchange = !displayLetsExchange;
case ExchangeProviderDescription.stealthEx:
displayStealthEx = !displayStealthEx;
break;
case ExchangeProviderDescription.all:
if (displayAllTrades) {
displayChangeNow = false;
@ -88,6 +103,8 @@ abstract class TradeFilterStoreBase with Store {
displayTrocador = false;
displayExolix = false;
displayThorChain = false;
displayLetsExchange = false;
displayStealthEx = false;
} else {
displayChangeNow = true;
displaySideShift = true;
@ -97,6 +114,8 @@ abstract class TradeFilterStoreBase with Store {
displayTrocador = true;
displayExolix = true;
displayThorChain = true;
displayLetsExchange = true;
displayStealthEx = true;
}
break;
}
@ -112,13 +131,21 @@ abstract class TradeFilterStoreBase with Store {
? _trades
.where((item) =>
(displayXMRTO && item.trade.provider == ExchangeProviderDescription.xmrto) ||
(displaySideShift && item.trade.provider == ExchangeProviderDescription.sideShift) ||
(displayChangeNow && item.trade.provider == ExchangeProviderDescription.changeNow) ||
(displayMorphToken && item.trade.provider == ExchangeProviderDescription.morphToken) ||
(displaySimpleSwap && item.trade.provider == ExchangeProviderDescription.simpleSwap) ||
(displaySideShift &&
item.trade.provider == ExchangeProviderDescription.sideShift) ||
(displayChangeNow &&
item.trade.provider == ExchangeProviderDescription.changeNow) ||
(displayMorphToken &&
item.trade.provider == ExchangeProviderDescription.morphToken) ||
(displaySimpleSwap &&
item.trade.provider == ExchangeProviderDescription.simpleSwap) ||
(displayTrocador && item.trade.provider == ExchangeProviderDescription.trocador) ||
(displayExolix && item.trade.provider == ExchangeProviderDescription.exolix) ||
(displayThorChain && item.trade.provider == ExchangeProviderDescription.thorChain))
(displayThorChain &&
item.trade.provider == ExchangeProviderDescription.thorChain) ||
(displayLetsExchange &&
item.trade.provider == ExchangeProviderDescription.letsExchange) ||
(displayStealthEx && item.trade.provider == ExchangeProviderDescription.stealthEx))
.toList()
: _trades;
}

View file

@ -0,0 +1,11 @@
import 'package:mobx/mobx.dart';
part 'seed_settings_store.g.dart';
class SeedSettingsStore = SeedSettingsStoreBase with _$SeedSettingsStore;
abstract class SeedSettingsStoreBase with Store {
@observable
String? passphrase;
}

View file

@ -1,45 +1,46 @@
import 'dart:io';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/core/secure_storage.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/action_list_display_mode.dart';
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
import 'package:cake_wallet/entities/provider_types.dart';
import 'package:cake_wallet/entities/cake_2fa_preset_options.dart';
import 'package:cake_wallet/entities/background_tasks.dart';
import 'package:cake_wallet/entities/balance_display_mode.dart';
import 'package:cake_wallet/entities/cake_2fa_preset_options.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/entities/language_service.dart';
import 'package:cake_wallet/entities/pin_code_required_duration.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/entities/provider_types.dart';
import 'package:cake_wallet/entities/secret_store_key.dart';
import 'package:cake_wallet/entities/seed_phrase_length.dart';
import 'package:cake_wallet/entities/seed_type.dart';
import 'package:cake_wallet/entities/sort_balance_types.dart';
import 'package:cake_wallet/entities/wallet_list_order_types.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
import 'package:cake_wallet/view_model/settings/sync_mode.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/wallet_type_utils.dart';
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/themes/theme_list.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/package_info.dart';
import 'package:cake_wallet/view_model/settings/sync_mode.dart';
import 'package:cake_wallet/wallet_type_utils.dart';
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cw_core/node.dart';
import 'package:cw_core/set_app_secure_native.dart';
import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/material.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/utils/package_info.dart';
import 'package:cake_wallet/di.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cake_wallet/entities/language_service.dart';
import 'package:cake_wallet/entities/balance_display_mode.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cw_core/node.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/entities/action_list_display_mode.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart';
import 'package:cw_core/set_app_secure_native.dart';
part 'settings_store.g.dart';
@ -55,7 +56,8 @@ abstract class SettingsStoreBase with Store {
required BalanceDisplayMode initialBalanceDisplayMode,
required bool initialSaveRecipientAddress,
required AutoGenerateSubaddressStatus initialAutoGenerateSubaddressStatus,
required SeedType initialMoneroSeedType,
required MoneroSeedType initialMoneroSeedType,
required BitcoinSeedType initialBitcoinSeedType,
required bool initialAppSecure,
required bool initialDisableBuy,
required bool initialDisableSell,
@ -99,6 +101,7 @@ abstract class SettingsStoreBase with Store {
required this.useEtherscan,
required this.usePolygonScan,
required this.useTronGrid,
required this.useMempoolFeeAPI,
required this.defaultNanoRep,
required this.defaultBananoRep,
required this.lookupsTwitter,
@ -128,6 +131,7 @@ abstract class SettingsStoreBase with Store {
shouldSaveRecipientAddress = initialSaveRecipientAddress,
autoGenerateSubaddressStatus = initialAutoGenerateSubaddressStatus,
moneroSeedType = initialMoneroSeedType,
bitcoinSeedType = initialBitcoinSeedType,
fiatApiMode = initialFiatMode,
allowBiometricalAuthentication = initialAllowBiometricalAuthentication,
selectedCake2FAPreset = initialCake2FAPresetOptions,
@ -329,9 +333,14 @@ abstract class SettingsStoreBase with Store {
reaction(
(_) => moneroSeedType,
(SeedType moneroSeedType) =>
(MoneroSeedType moneroSeedType) =>
sharedPreferences.setInt(PreferencesKey.moneroSeedType, moneroSeedType.raw));
reaction(
(_) => bitcoinSeedType,
(BitcoinSeedType bitcoinSeedType) => sharedPreferences.setInt(
PreferencesKey.bitcoinSeedType, bitcoinSeedType.raw));
reaction(
(_) => fiatApiMode,
(FiatApiMode mode) =>
@ -408,6 +417,9 @@ abstract class SettingsStoreBase with Store {
reaction((_) => useTronGrid,
(bool useTronGrid) => _sharedPreferences.setBool(PreferencesKey.useTronGrid, useTronGrid));
reaction((_) => useMempoolFeeAPI,
(bool useMempoolFeeAPI) => _sharedPreferences.setBool(PreferencesKey.useMempoolFeeAPI, useMempoolFeeAPI));
reaction((_) => defaultNanoRep,
(String nanoRep) => _sharedPreferences.setString(PreferencesKey.defaultNanoRep, nanoRep));
@ -555,7 +567,8 @@ abstract class SettingsStoreBase with Store {
static const defaultAutoGenerateSubaddressStatus = AutoGenerateSubaddressStatus.initialized;
static final walletPasswordDirectInput = Platform.isLinux;
static const defaultSeedPhraseLength = SeedPhraseLength.twelveWords;
static const defaultMoneroSeedType = SeedType.defaultSeedType;
static const defaultMoneroSeedType = MoneroSeedType.defaultSeedType;
static const defaultBitcoinSeedType = BitcoinSeedType.defaultDerivationType;
@observable
FiatCurrency fiatCurrency;
@ -585,7 +598,10 @@ abstract class SettingsStoreBase with Store {
AutoGenerateSubaddressStatus autoGenerateSubaddressStatus;
@observable
SeedType moneroSeedType;
MoneroSeedType moneroSeedType;
@observable
BitcoinSeedType bitcoinSeedType;
@observable
bool isAppSecure;
@ -695,6 +711,9 @@ abstract class SettingsStoreBase with Store {
@observable
bool useTronGrid;
@observable
bool useMempoolFeeAPI;
@observable
String defaultNanoRep;
@ -880,6 +899,7 @@ abstract class SettingsStoreBase with Store {
final useEtherscan = sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true;
final usePolygonScan = sharedPreferences.getBool(PreferencesKey.usePolygonScan) ?? true;
final useTronGrid = sharedPreferences.getBool(PreferencesKey.useTronGrid) ?? true;
final useMempoolFeeAPI = sharedPreferences.getBool(PreferencesKey.useMempoolFeeAPI) ?? true;
final defaultNanoRep = sharedPreferences.getString(PreferencesKey.defaultNanoRep) ?? "";
final defaultBananoRep = sharedPreferences.getString(PreferencesKey.defaultBananoRep) ?? "";
final lookupsTwitter = sharedPreferences.getBool(PreferencesKey.lookupsTwitter) ?? true;
@ -945,9 +965,15 @@ abstract class SettingsStoreBase with Store {
final _moneroSeedType = sharedPreferences.getInt(PreferencesKey.moneroSeedType);
final moneroSeedType = _moneroSeedType != null
? SeedType.deserialize(raw: _moneroSeedType)
? MoneroSeedType.deserialize(raw: _moneroSeedType)
: defaultMoneroSeedType;
final _bitcoinSeedType = sharedPreferences.getInt(PreferencesKey.bitcoinSeedType);
final bitcoinSeedType = _bitcoinSeedType != null
? BitcoinSeedType.deserialize(raw: _bitcoinSeedType)
: defaultBitcoinSeedType;
final nodes = <WalletType, Node>{};
final powNodes = <WalletType, Node>{};
@ -1111,6 +1137,7 @@ abstract class SettingsStoreBase with Store {
initialSaveRecipientAddress: shouldSaveRecipientAddress,
initialAutoGenerateSubaddressStatus: autoGenerateSubaddressStatus,
initialMoneroSeedType: moneroSeedType,
initialBitcoinSeedType: bitcoinSeedType,
initialAppSecure: isAppSecure,
initialDisableBuy: disableBuy,
initialDisableSell: disableSell,
@ -1135,6 +1162,7 @@ abstract class SettingsStoreBase with Store {
useEtherscan: useEtherscan,
usePolygonScan: usePolygonScan,
useTronGrid: useTronGrid,
useMempoolFeeAPI: useMempoolFeeAPI,
defaultNanoRep: defaultNanoRep,
defaultBananoRep: defaultBananoRep,
lookupsTwitter: lookupsTwitter,
@ -1233,9 +1261,15 @@ abstract class SettingsStoreBase with Store {
final _moneroSeedType = sharedPreferences.getInt(PreferencesKey.moneroSeedType);
moneroSeedType = _moneroSeedType != null
? SeedType.deserialize(raw: _moneroSeedType)
? MoneroSeedType.deserialize(raw: _moneroSeedType)
: defaultMoneroSeedType;
final _bitcoinSeedType = sharedPreferences.getInt(PreferencesKey.bitcoinSeedType);
bitcoinSeedType = _bitcoinSeedType != null
? BitcoinSeedType.deserialize(raw: _bitcoinSeedType)
: defaultBitcoinSeedType;
balanceDisplayMode = BalanceDisplayMode.deserialize(
raw: sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey)!);
shouldSaveRecipientAddress =
@ -1282,6 +1316,7 @@ abstract class SettingsStoreBase with Store {
useEtherscan = sharedPreferences.getBool(PreferencesKey.useEtherscan) ?? true;
usePolygonScan = sharedPreferences.getBool(PreferencesKey.usePolygonScan) ?? true;
useTronGrid = sharedPreferences.getBool(PreferencesKey.useTronGrid) ?? true;
useMempoolFeeAPI = sharedPreferences.getBool(PreferencesKey.useMempoolFeeAPI) ?? true;
defaultNanoRep = sharedPreferences.getString(PreferencesKey.defaultNanoRep) ?? "";
defaultBananoRep = sharedPreferences.getString(PreferencesKey.defaultBananoRep) ?? "";
lookupsTwitter = sharedPreferences.getBool(PreferencesKey.lookupsTwitter) ?? true;

View file

@ -172,8 +172,14 @@ class ExceptionHandler {
"Error while launching http",
"OS Error: Network is unreachable",
"ClientException: Write failed, uri=http",
"Connection terminated during handshake",
"Corrupted wallets seeds",
"bad_alloc",
"does not correspond",
"basic_string",
"input_stream",
"input stream error",
"invalid signature",
"invalid password",
];
static Future<void> _addDeviceInfo(File file) async {

View file

@ -0,0 +1,60 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
class ImageUtil {
static Widget getImageFromPath({required String imagePath, double? height, double? width}) {
final bool isNetworkImage = imagePath.startsWith('http') || imagePath.startsWith('https');
final bool isSvg = imagePath.endsWith('.svg');
final double _height = height ?? 35;
final double _width = width ?? 35;
if (isNetworkImage) {
return isSvg
? SvgPicture.network(
imagePath,
height: _height,
width: _width,
placeholderBuilder: (BuildContext context) => Container(
height: _height,
width: _width,
child: Center(
child: CircularProgressIndicator(),
),
),
)
: Image.network(
imagePath,
height: _height,
width: _width,
loadingBuilder:
(BuildContext context, Widget child, ImageChunkEvent? loadingProgress) {
if (loadingProgress == null) {
return child;
}
return Container(
height: _height,
width: _width,
child: Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
),
),
);
},
errorBuilder: (BuildContext context, Object exception, StackTrace? stackTrace) {
return Container(
height: _height,
width: _width,
);
},
);
} else {
return isSvg
? SvgPicture.asset(imagePath, height: _height, width: _width)
: Image.asset(imagePath, height: _height, width: _width);
}
}
}

View file

@ -30,6 +30,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
final SettingsStore _settingsStore;
@computed
bool get hasSeedPhraseLengthOption {
// convert to switch case so that it give a syntax error when adding a new wallet type
// thus we don't forget about it
@ -40,11 +41,14 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
case WalletType.solana:
case WalletType.tron:
return true;
case WalletType.bitcoin:
case WalletType.litecoin:
return _settingsStore.bitcoinSeedType == BitcoinSeedType.bip39;
case WalletType.monero:
case WalletType.wownero:
case WalletType.none:
case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.haven:
case WalletType.nano:
case WalletType.banano:
@ -52,7 +56,13 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
}
}
bool get hasSeedTypeOption => type == WalletType.monero || type == WalletType.wownero;
bool get hasSeedTypeOption => [WalletType.monero, WalletType.wownero].contains(type);
bool get hasPassphraseOption => [
WalletType.bitcoin,
WalletType.litecoin,
WalletType.bitcoinCash,
].contains(type);
@computed
bool get addCustomNode => _addCustomNode;
@ -61,7 +71,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store {
SeedPhraseLength get seedPhraseLength => _settingsStore.seedPhraseLength;
@computed
bool get isPolySeed => _settingsStore.moneroSeedType == SeedType.polyseed;
bool get isPolySeed => _settingsStore.moneroSeedType == MoneroSeedType.polyseed;
@action
void setFiatApiMode(FiatApiMode fiatApiMode) => _settingsStore.fiatApiMode = fiatApiMode;

View file

@ -1,12 +1,14 @@
import 'dart:convert';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/buy/buy_provider.dart';
import 'package:cake_wallet/core/key_service.dart';
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
import 'package:cake_wallet/entities/balance_display_mode.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/entities/provider_types.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/entities/service_status.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/generated/i18n.dart';
@ -45,11 +47,9 @@ import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:eth_sig_util/util/utils.dart';
import 'package:flutter/services.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:http/http.dart' as http;
import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cake_wallet/.secrets.g.dart' as secrets;
part 'dashboard_view_model.g.dart';
@ -129,6 +129,16 @@ abstract class DashboardViewModelBase with Store {
caption: ExchangeProviderDescription.thorChain.title,
onChanged: () =>
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.thorChain)),
FilterItem(
value: () => tradeFilterStore.displayLetsExchange,
caption: ExchangeProviderDescription.letsExchange.title,
onChanged: () =>
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.letsExchange)),
FilterItem(
value: () => tradeFilterStore.displayStealthEx,
caption: ExchangeProviderDescription.stealthEx.title,
onChanged: () =>
tradeFilterStore.toggleDisplayExchange(ExchangeProviderDescription.stealthEx)),
]
},
subname = '',

View file

@ -69,12 +69,10 @@ class TransactionListItem extends ActionListItem with Keyable {
}
String get formattedStatus {
if (transaction.direction == TransactionDirection.incoming) {
if (balanceViewModel.wallet.type == WalletType.monero ||
balanceViewModel.wallet.type == WalletType.wownero ||
balanceViewModel.wallet.type == WalletType.haven) {
return formattedPendingStatus;
}
if (balanceViewModel.wallet.type == WalletType.monero ||
balanceViewModel.wallet.type == WalletType.wownero ||
balanceViewModel.wallet.type == WalletType.haven) {
return formattedPendingStatus;
}
return transaction.isPending ? S.current.pending : '';
}

View file

@ -7,6 +7,7 @@ import 'package:cake_wallet/exchange/provider/exolix_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/quantex_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/sideshift_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/simpleswap_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/stealth_ex_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart';
import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
import 'package:cake_wallet/exchange/trade.dart';
@ -52,6 +53,8 @@ abstract class ExchangeTradeViewModelBase with Store {
case ExchangeProviderDescription.quantex:
_provider = QuantexExchangeProvider();
break;
case ExchangeProviderDescription.stealthEx:
_provider = StealthExExchangeProvider();
case ExchangeProviderDescription.thorChain:
_provider = ThorChainExchangeProvider(tradesStore: trades);
break;

View file

@ -4,6 +4,8 @@ import 'dart:convert';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cake_wallet/core/create_trade_result.dart';
import 'package:cake_wallet/exchange/provider/letsexchange_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/stealth_ex_exchange_provider.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/sync_status.dart';
import 'package:cw_core/transaction_priority.dart';
@ -160,15 +162,17 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
final SharedPreferences sharedPreferences;
List<ExchangeProvider> get _allProviders => [
ChangeNowExchangeProvider(settingsStore: _settingsStore),
SideShiftExchangeProvider(),
SimpleSwapExchangeProvider(),
ThorChainExchangeProvider(tradesStore: trades),
if (FeatureFlag.isExolixEnabled) ExolixExchangeProvider(),
QuantexExchangeProvider(),
TrocadorExchangeProvider(
useTorOnly: _useTorOnly, providerStates: _settingsStore.trocadorProviderStates),
];
ChangeNowExchangeProvider(settingsStore: _settingsStore),
SideShiftExchangeProvider(),
SimpleSwapExchangeProvider(),
ThorChainExchangeProvider(tradesStore: trades),
if (FeatureFlag.isExolixEnabled) ExolixExchangeProvider(),
QuantexExchangeProvider(),
LetsExchangeExchangeProvider(),
StealthExExchangeProvider(),
TrocadorExchangeProvider(
useTorOnly: _useTorOnly, providerStates: _settingsStore.trocadorProviderStates),
];
@observable
ExchangeProvider? provider;

View file

@ -7,6 +7,7 @@ import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/view_model/restore/restore_mode.dart';
import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
@ -26,13 +27,13 @@ class WalletRestorationFromQRVM = WalletRestorationFromQRVMBase with _$WalletRes
abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store {
WalletRestorationFromQRVMBase(AppStore appStore, WalletCreationService walletCreationService,
Box<WalletInfo> walletInfoSource, WalletType type)
Box<WalletInfo> walletInfoSource, WalletType type, SeedSettingsViewModel seedSettingsViewModel)
: height = 0,
viewKey = '',
spendKey = '',
wif = '',
address = '',
super(appStore, walletInfoSource, walletCreationService, type: type, isRecovery: true);
super(appStore, walletInfoSource, walletCreationService, seedSettingsViewModel, type: type, isRecovery: true);
@observable
int height;
@ -61,7 +62,7 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
derivationInfo = options["derivationInfo"] as DerivationInfo?;
passphrase = options["passphrase"] as String?;
}
derivationInfo ??= getDefaultDerivation();
derivationInfo ??= getDefaultCreateDerivation();
switch (restoreWallet.restoreMode) {
case WalletRestoreMode.keys:

View file

@ -46,7 +46,7 @@ abstract class RestoreFromBackupViewModelBase with Store {
final data = await file.readAsBytes();
await backupService.importBackup(data, password);
await main();
await initializeAppAtRoot(reInitializing: true);
final store = getIt.get<AppStore>();
ReactionDisposer? reaction;

View file

@ -0,0 +1,34 @@
import 'package:cake_wallet/entities/seed_type.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/store/seed_settings_store.dart';
import 'package:mobx/mobx.dart';
part 'seed_settings_view_model.g.dart';
class SeedSettingsViewModel = SeedSettingsViewModelBase with _$SeedSettingsViewModel;
abstract class SeedSettingsViewModelBase with Store {
SeedSettingsViewModelBase(this._appStore, this._seedSettingsStore);
@computed
MoneroSeedType get moneroSeedType => _appStore.settingsStore.moneroSeedType;
@action
void setMoneroSeedType(MoneroSeedType seedType) => _appStore.settingsStore.moneroSeedType = seedType;
@computed
BitcoinSeedType get bitcoinSeedType => _appStore.settingsStore.bitcoinSeedType;
@action
void setBitcoinSeedType(BitcoinSeedType derivationType) =>
_appStore.settingsStore.bitcoinSeedType = derivationType;
@computed
String? get passphrase => this._seedSettingsStore.passphrase;
@action
void setPassphrase(String? passphrase) => this._seedSettingsStore.passphrase = passphrase;
final AppStore _appStore;
final SeedSettingsStore _seedSettingsStore;
}

View file

@ -1,19 +0,0 @@
import 'package:cake_wallet/entities/seed_type.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:mobx/mobx.dart';
part 'seed_type_view_model.g.dart';
class SeedTypeViewModel = SeedTypeViewModelBase with _$SeedTypeViewModel;
abstract class SeedTypeViewModelBase with Store {
SeedTypeViewModelBase(this._appStore);
@computed
SeedType get moneroSeedType => _appStore.settingsStore.moneroSeedType;
@action
void setMoneroSeedType(SeedType seedType) => _appStore.settingsStore.moneroSeedType = seedType;
final AppStore _appStore;
}

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
import 'package:cake_wallet/entities/exchange_api_mode.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
@ -75,6 +76,9 @@ abstract class PrivacySettingsViewModelBase with Store {
@computed
bool get useTronGrid => _settingsStore.useTronGrid;
@computed
bool get useMempoolFeeAPI => _settingsStore.useMempoolFeeAPI;
@computed
bool get lookupTwitter => _settingsStore.lookupsTwitter;
@ -99,6 +103,8 @@ abstract class PrivacySettingsViewModelBase with Store {
bool get canUseTronGrid => _wallet.type == WalletType.tron;
bool get canUseMempoolFeeAPI => _wallet.type == WalletType.bitcoin;
@action
void setShouldSaveRecipientAddress(bool value) =>
_settingsStore.shouldSaveRecipientAddress = value;
@ -156,4 +162,9 @@ abstract class PrivacySettingsViewModelBase with Store {
_settingsStore.useTronGrid = value;
tron!.updateTronGridUsageState(_wallet, value);
}
@action
void setUseMempoolFeeAPI(bool value) {
_settingsStore.useMempoolFeeAPI = value;
}
}

View file

@ -4,9 +4,11 @@ import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/exchange/provider/changenow_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/exolix_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/letsexchange_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/quantex_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/sideshift_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/simpleswap_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/stealth_ex_exchange_provider.dart';
import 'package:cake_wallet/exchange/provider/thorchain_exchange.provider.dart';
import 'package:cake_wallet/exchange/provider/trocador_exchange_provider.dart';
import 'package:cake_wallet/exchange/trade.dart';
@ -59,6 +61,11 @@ abstract class TradeDetailsViewModelBase with Store {
break;
case ExchangeProviderDescription.quantex:
_provider = QuantexExchangeProvider();
case ExchangeProviderDescription.letsExchange:
_provider = LetsExchangeExchangeProvider();
break;
case ExchangeProviderDescription.stealthEx:
_provider = StealthExExchangeProvider();
break;
}
@ -86,6 +93,10 @@ abstract class TradeDetailsViewModelBase with Store {
return 'https://track.ninerealms.com/${trade.id}';
case ExchangeProviderDescription.quantex:
return 'https://myquantex.com/send/${trade.id}';
case ExchangeProviderDescription.letsExchange:
return 'https://letsexchange.io/?transactionId=${trade.id}';
case ExchangeProviderDescription.stealthEx:
return 'https://stealthex.io/exchange/?id=${trade.id}';
}
return null;
}

View file

@ -1,20 +1,21 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/core/wallet_creation_service.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/background_tasks.dart';
import 'package:cake_wallet/entities/generate_name.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/view_model/restore/restore_wallet.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/entities/generate_name.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:polyseed/polyseed.dart';
part 'wallet_creation_vm.g.dart';
@ -23,6 +24,7 @@ class WalletCreationVM = WalletCreationVMBase with _$WalletCreationVM;
abstract class WalletCreationVMBase with Store {
WalletCreationVMBase(this._appStore, this._walletInfoSource, this.walletCreationService,
this.seedSettingsViewModel,
{required this.type, required this.isRecovery})
: state = InitialExecutionState(),
name = '';
@ -44,7 +46,6 @@ abstract class WalletCreationVMBase with Store {
@observable
String? repeatedWalletPassword;
bool get hasWalletPassword => SettingsStoreBase.walletPasswordDirectInput;
WalletType type;
@ -52,6 +53,7 @@ abstract class WalletCreationVMBase with Store {
final WalletCreationService walletCreationService;
final Box<WalletInfo> _walletInfoSource;
final AppStore _appStore;
final SeedSettingsViewModel seedSettingsViewModel;
bool isPolyseed(String seed) =>
(type == WalletType.monero || type == WalletType.wownero) &&
@ -95,7 +97,7 @@ abstract class WalletCreationVMBase with Store {
dirPath: dirPath,
address: '',
showIntroCakePayCard: (!walletCreationService.typeExists(type)) && type != WalletType.haven,
derivationInfo: credentials.derivationInfo ?? getDefaultDerivation(),
derivationInfo: credentials.derivationInfo ?? getDefaultCreateDerivation(),
hardwareWalletType: credentials.hardwareWalletType,
);
@ -109,17 +111,35 @@ abstract class WalletCreationVMBase with Store {
getIt.get<BackgroundTasks>().registerSyncTask();
_appStore.authenticationStore.allowed();
state = ExecutedSuccessfullyState();
} catch (e, s) {
} catch (e, _) {
state = FailureState(e.toString());
}
}
DerivationInfo? getDefaultDerivation() {
switch (this.type) {
DerivationInfo? getDefaultCreateDerivation() {
final useBip39 = seedSettingsViewModel.bitcoinSeedType.type == DerivationType.bip39;
switch (type) {
case WalletType.nano:
return DerivationInfo(derivationType: DerivationType.nano);
case WalletType.bitcoin:
if (useBip39) {
return DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/84'/0'/0'",
description: "Standard BIP84 native segwit",
scriptType: "p2wpkh",
);
}
return bitcoin!.getElectrumDerivations()[DerivationType.electrum]!.first;
case WalletType.litecoin:
if (useBip39) {
return DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/84'/2'/0'",
description: "Default Litecoin",
scriptType: "p2wpkh",
);
}
return bitcoin!.getElectrumDerivations()[DerivationType.electrum]!.first;
default:
return null;
@ -127,10 +147,14 @@ abstract class WalletCreationVMBase with Store {
}
DerivationInfo? getCommonRestoreDerivation() {
final useElectrum = seedSettingsViewModel.bitcoinSeedType.type == DerivationType.electrum;
switch (this.type) {
case WalletType.nano:
return DerivationInfo(derivationType: DerivationType.nano);
case WalletType.bitcoin:
if (useElectrum) {
return bitcoin!.getElectrumDerivations()[DerivationType.electrum]!.first;
}
return DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/84'/0'/0'/0",
@ -138,6 +162,9 @@ abstract class WalletCreationVMBase with Store {
scriptType: "p2wpkh",
);
case WalletType.litecoin:
if (useElectrum) {
return bitcoin!.getElectrumDerivations()[DerivationType.electrum]!.first;
}
return DerivationInfo(
derivationType: DerivationType.bip39,
derivationPath: "m/84'/2'/0'/0",

View file

@ -5,6 +5,7 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
import 'package:cake_wallet/view_model/wallet_creation_vm.dart';
import 'package:cw_core/hardware/hardware_account_data.dart';
import 'package:cw_core/wallet_base.dart';
@ -25,10 +26,15 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with
int _nextIndex = 0;
WalletHardwareRestoreViewModelBase(this.ledgerViewModel, AppStore appStore,
WalletCreationService walletCreationService, Box<WalletInfo> walletInfoSource,
WalletHardwareRestoreViewModelBase(
this.ledgerViewModel,
AppStore appStore,
WalletCreationService walletCreationService,
Box<WalletInfo> walletInfoSource,
SeedSettingsViewModel seedSettingsViewModel,
{required WalletType type})
: super(appStore, walletInfoSource, walletCreationService, type: type, isRecovery: true);
: super(appStore, walletInfoSource, walletCreationService, seedSettingsViewModel,
type: type, isRecovery: true);
@observable
String name = "";

View file

@ -342,7 +342,8 @@ abstract class WalletKeysViewModelBase with Store {
'hexSeed': _appStore.wallet!.hexSeed!,
if (_appStore.wallet!.seed == null && _appStore.wallet!.privateKey != null)
'private_key': _appStore.wallet!.privateKey!,
if (restoreHeightResult != null) ...{'height': restoreHeightResult}
if (restoreHeightResult != null) ...{'height': restoreHeightResult},
if (_appStore.wallet!.passphrase != null) 'passphrase': _appStore.wallet!.passphrase!
};
}

View file

@ -1,35 +1,42 @@
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/core/wallet_creation_service.dart';
import 'package:cake_wallet/entities/seed_type.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/haven/haven.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
import 'package:cake_wallet/view_model/wallet_creation_vm.dart';
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cake_wallet/core/wallet_creation_service.dart';
import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/view_model/wallet_creation_vm.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/haven/haven.dart';
import 'advanced_privacy_settings_view_model.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import '../polygon/polygon.dart';
import 'advanced_privacy_settings_view_model.dart';
part 'wallet_new_vm.g.dart';
class WalletNewVM = WalletNewVMBase with _$WalletNewVM;
abstract class WalletNewVMBase extends WalletCreationVM with Store {
WalletNewVMBase(AppStore appStore, WalletCreationService walletCreationService,
Box<WalletInfo> walletInfoSource, this.advancedPrivacySettingsViewModel,
WalletNewVMBase(
AppStore appStore,
WalletCreationService walletCreationService,
Box<WalletInfo> walletInfoSource,
this.advancedPrivacySettingsViewModel,
SeedSettingsViewModel seedSettingsViewModel,
{required WalletType type})
: selectedMnemonicLanguage = '',
super(appStore, walletInfoSource, walletCreationService, type: type, isRecovery: false);
super(appStore, walletInfoSource, walletCreationService, seedSettingsViewModel,
type: type, isRecovery: false);
final AdvancedPrivacySettingsViewModel advancedPrivacySettingsViewModel;
@ -37,47 +44,58 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
String selectedMnemonicLanguage;
bool get hasLanguageSelector =>
type == WalletType.monero || type == WalletType.haven || type == WalletType.wownero;
[WalletType.monero, WalletType.haven, WalletType.wownero].contains(type);
int get seedPhraseWordsLength {
switch (type) {
case WalletType.monero:
case WalletType.wownero:
if (advancedPrivacySettingsViewModel.isPolySeed) {
return 16;
}
return 25;
return advancedPrivacySettingsViewModel.isPolySeed ? 16 : 25;
case WalletType.tron:
case WalletType.solana:
case WalletType.polygon:
case WalletType.ethereum:
case WalletType.bitcoinCash:
return advancedPrivacySettingsViewModel.seedPhraseLength.value;
case WalletType.bitcoin:
case WalletType.litecoin:
return seedSettingsViewModel.bitcoinSeedType == BitcoinSeedType.bip39
? advancedPrivacySettingsViewModel.seedPhraseLength.value
: 24;
default:
return 24;
}
}
bool get hasSeedType => type == WalletType.monero || type == WalletType.wownero;
bool get hasSeedType => [WalletType.monero, WalletType.wownero].contains(type);
@override
WalletCredentials getCredentials(dynamic _options) {
final options = _options as List<dynamic>?;
final passphrase = seedSettingsViewModel.passphrase;
seedSettingsViewModel.setPassphrase(null);
switch (type) {
case WalletType.monero:
return monero!.createMoneroNewWalletCredentials(
name: name, language: options!.first as String, password: walletPassword, isPolyseed: options.last as bool);
name: name,
language: options!.first as String,
password: walletPassword,
isPolyseed: options.last as bool);
case WalletType.bitcoin:
return bitcoin!.createBitcoinNewWalletCredentials(name: name, password: walletPassword);
return bitcoin!.createBitcoinNewWalletCredentials(
name: name, password: walletPassword, passphrase: passphrase);
case WalletType.litecoin:
return bitcoin!.createBitcoinNewWalletCredentials(name: name, password: walletPassword);
return bitcoin!.createBitcoinNewWalletCredentials(
name: name, password: walletPassword, passphrase: passphrase);
case WalletType.haven:
return haven!.createHavenNewWalletCredentials(
name: name, language: options!.first as String, password: walletPassword);
case WalletType.ethereum:
return ethereum!.createEthereumNewWalletCredentials(name: name, password: walletPassword);
case WalletType.bitcoinCash:
return bitcoinCash!.createBitcoinCashNewWalletCredentials(name: name, password: walletPassword);
return bitcoinCash!.createBitcoinCashNewWalletCredentials(
name: name, password: walletPassword, passphrase: passphrase);
case WalletType.nano:
case WalletType.banano:
return nano!.createNanoNewWalletCredentials(name: name);

View file

@ -1,25 +1,26 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/tron/tron.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cake_wallet/core/generate_wallet_password.dart';
import 'package:cake_wallet/core/wallet_creation_service.dart';
import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cake_wallet/view_model/wallet_creation_vm.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/haven/haven.dart';
import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/nano/nano.dart';
import 'package:cake_wallet/polygon/polygon.dart';
import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/view_model/restore/restore_mode.dart';
import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
import 'package:cake_wallet/view_model/wallet_creation_vm.dart';
import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
part 'wallet_restore_view_model.g.dart';
@ -27,7 +28,7 @@ class WalletRestoreViewModel = WalletRestoreViewModelBase with _$WalletRestoreVi
abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
WalletRestoreViewModelBase(AppStore appStore, WalletCreationService walletCreationService,
Box<WalletInfo> walletInfoSource,
Box<WalletInfo> walletInfoSource, SeedSettingsViewModel seedSettingsViewModel,
{required WalletType type})
: hasSeedLanguageSelector =
type == WalletType.monero || type == WalletType.haven || type == WalletType.wownero,
@ -41,7 +42,8 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
type == WalletType.tron,
isButtonEnabled = false,
mode = WalletRestoreMode.seed,
super(appStore, walletInfoSource, walletCreationService, type: type, isRecovery: true) {
super(appStore, walletInfoSource, walletCreationService, seedSettingsViewModel,
type: type, isRecovery: true) {
switch (type) {
case WalletType.monero:
availableModes = WalletRestoreMode.values;
@ -76,7 +78,8 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
final bool hasBlockchainHeightLanguageSelector;
final bool hasRestoreFromPrivateKey;
bool get hasPassphrase => [WalletType.bitcoin, WalletType.litecoin].contains(type);
bool get hasPassphrase =>
[WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(type);
@observable
WalletRestoreMode mode;
@ -192,10 +195,11 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
case WalletType.nano:
return nano!.createNanoRestoreWalletFromKeysCredentials(
name: name,
password: password,
seedKey: options['private_key'] as String,
derivationType: options["derivationType"] as DerivationType);
name: name,
password: password,
seedKey: options['private_key'] as String,
derivationType: derivationInfo!.derivationType!,
);
case WalletType.polygon:
return polygon!.createPolygonRestoreWalletFromPrivateKey(
name: name,

View file

@ -195,6 +195,7 @@
"delete_wallet_confirm_message": "هل أنت متأكد أنك تريد حذف محفظة ${wallet_name}؟",
"deleteConnectionConfirmationPrompt": "ـﺑ ﻝﺎﺼﺗﻻﺍ ﻑﺬﺣ ﺪﻳﺮﺗ ﻚﻧﺃ ﺪﻛﺄﺘﻣ ﺖﻧﺃ ﻞﻫ",
"denominations": "الطوائف",
"derivationpath": "مسار الاشتقاق",
"descending": "النزول",
"description": "ﻒﺻﻭ",
"destination_tag": "علامة الوجهة:",
@ -207,6 +208,7 @@
"disable_buy": "تعطيل إجراء الشراء",
"disable_cake_2fa": "تعطيل 2 عامل المصادقة",
"disable_exchange": "تعطيل التبادل",
"disable_fee_api_warning": "من خلال إيقاف تشغيل هذا ، قد تكون معدلات الرسوم غير دقيقة في بعض الحالات ، لذلك قد ينتهي بك الأمر إلى دفع مبالغ زائدة أو دفع رسوم المعاملات الخاصة بك",
"disable_fiat": "تعطيل fiat",
"disable_sell": "قم بتعطيل إجراء البيع",
"disableBatteryOptimization": "تعطيل تحسين البطارية",
@ -354,6 +356,7 @@
"ledger_error_wrong_app": "يرجى التأكد",
"ledger_please_enable_bluetooth": "يرجى تمكين البلوتوث للكشف عن دفتر الأستاذ الخاص بك",
"light_theme": "فاتح",
"live_fee_rates": "أسعار الرسوم المباشرة عبر API",
"load_more": "تحميل المزيد",
"loading_your_wallet": "يتم تحميل محفظتك",
"login": "تسجيل الدخول",

View file

@ -195,6 +195,7 @@
"delete_wallet_confirm_message": "Сигурни ли сте, че искате да изтриете протфейла ${wallet_name}?",
"deleteConnectionConfirmationPrompt": "Сигурни ли сте, че искате да изтриете връзката към",
"denominations": "Деноминации",
"derivationpath": "Пътят на производно",
"descending": "Низходящ",
"description": "Описание",
"destination_tag": "Destination tag:",
@ -207,6 +208,7 @@
"disable_buy": "Деактивирайте действието за покупка",
"disable_cake_2fa": "Деактивирайте Cake 2FA",
"disable_exchange": "Деактивиране на борса",
"disable_fee_api_warning": "Като изключите това, таксите могат да бъдат неточни в някои случаи, така че може да се препланите или да не плащате таксите за вашите транзакции",
"disable_fiat": "Деактивиране на fiat",
"disable_sell": "Деактивирайте действието за продажба",
"disableBatteryOptimization": "Деактивирайте оптимизацията на батерията",
@ -354,6 +356,7 @@
"ledger_error_wrong_app": "Моля, уверете се, че сте отворили правилното приложение на вашата книга",
"ledger_please_enable_bluetooth": "Моля, активирайте Bluetooth да открие вашата книга",
"light_theme": "Светло",
"live_fee_rates": "Цени на таксите на живо чрез API",
"load_more": "Зареди още",
"loading_your_wallet": "Зареждане на портфейл",
"login": "Влизане",

View file

@ -195,6 +195,7 @@
"delete_wallet_confirm_message": "Opravdu chcete smazat ${wallet_name} peněženku?",
"deleteConnectionConfirmationPrompt": "Jste si jisti, že chcete smazat připojení k?",
"denominations": "Označení",
"derivationpath": "Derivační cesta",
"descending": "Klesající",
"description": "Popis",
"destination_tag": "Destination Tag:",
@ -207,6 +208,7 @@
"disable_buy": "Zakázat akci nákupu",
"disable_cake_2fa": "Zakázat Cake 2FA",
"disable_exchange": "Zakázat směnárny",
"disable_fee_api_warning": "Tímto vypnutím by sazby poplatků mohly být v některých případech nepřesné, takže byste mohli skončit přepláváním nebo nedoplatkem poplatků za vaše transakce",
"disable_fiat": "Zakázat fiat",
"disable_sell": "Zakázat akci prodeje",
"disableBatteryOptimization": "Zakázat optimalizaci baterie",
@ -354,6 +356,7 @@
"ledger_error_wrong_app": "Ujistěte se, že se na své knize otevřete správnou aplikaci",
"ledger_please_enable_bluetooth": "Umožněte prosím Bluetooth detekovat vaši knihu",
"light_theme": "Světlý",
"live_fee_rates": "Živé sazby poplatků prostřednictvím API",
"load_more": "Načíst další",
"loading_your_wallet": "Načítám peněženku",
"login": "Login",

View file

@ -195,6 +195,7 @@
"delete_wallet_confirm_message": "Sind Sie sicher, dass Sie das ${wallet_name} Wallet löschen möchten?",
"deleteConnectionConfirmationPrompt": "Sind Sie sicher, dass Sie die Verbindung zu löschen möchten?",
"denominations": "Konfessionen",
"derivationpath": "Ableitungspfad",
"descending": "Absteigend",
"description": "Beschreibung",
"destination_tag": "Ziel-Tag:",
@ -207,6 +208,7 @@
"disable_buy": "Kaufaktion deaktivieren",
"disable_cake_2fa": "Cake 2FA deaktivieren",
"disable_exchange": "Exchange deaktivieren",
"disable_fee_api_warning": "Wenn dies ausgeschaltet wird, sind die Gebührenquoten in einigen Fällen möglicherweise ungenau, sodass Sie die Gebühren für Ihre Transaktionen möglicherweise überbezahlt oder unterzahlt",
"disable_fiat": "Fiat deaktivieren",
"disable_sell": "Verkaufsaktion deaktivieren",
"disableBatteryOptimization": "Batterieoptimierung deaktivieren",
@ -354,6 +356,7 @@
"ledger_error_wrong_app": "Bitte stellen Sie sicher, dass Sie die richtige App auf Ihrem Ledger geöffnet haben",
"ledger_please_enable_bluetooth": "Bitte aktivieren Sie Bluetooth um sich mit Ihren Ledger zu verbinden.",
"light_theme": "Hell",
"live_fee_rates": "Live -Gebührenpreise über API",
"load_more": "Mehr laden",
"loading_your_wallet": "Wallet wird geladen",
"login": "Einloggen",

View file

@ -195,6 +195,7 @@
"delete_wallet_confirm_message": "Are you sure that you want to delete ${wallet_name} wallet?",
"deleteConnectionConfirmationPrompt": "Are you sure that you want to delete the connection to",
"denominations": "Denominations",
"derivationpath": "Derivation Path",
"descending": "Descending",
"description": "Description",
"destination_tag": "Destination tag:",
@ -207,6 +208,7 @@
"disable_buy": "Disable buy action",
"disable_cake_2fa": "Disable Cake 2FA",
"disable_exchange": "Disable exchange",
"disable_fee_api_warning": "By turning this off, the fee rates might be inaccurate in some cases, so you might end up overpaying or underpaying the fees for your transactions",
"disable_fiat": "Disable fiat",
"disable_sell": "Disable sell action",
"disableBatteryOptimization": "Disable Battery Optimization",
@ -354,6 +356,7 @@
"ledger_error_wrong_app": "Please make sure you opend the right app on your ledger",
"ledger_please_enable_bluetooth": "Please enable Bluetooth to detect your Ledger",
"light_theme": "Light",
"live_fee_rates": "Live fee rates via API",
"load_more": "Load more",
"loading_your_wallet": "Loading your wallet",
"login": "Login",

View file

@ -195,6 +195,7 @@
"delete_wallet_confirm_message": "¿Está seguro de que desea eliminar la billetera ${wallet_name}?",
"deleteConnectionConfirmationPrompt": "¿Está seguro de que desea eliminar la conexión a",
"denominations": "Denominaciones",
"derivationpath": "Ruta de derivación",
"descending": "Descendente",
"description": "Descripción",
"destination_tag": "Etiqueta de destino:",
@ -207,6 +208,7 @@
"disable_buy": "Desactivar acción de compra",
"disable_cake_2fa": "Desactivar pastel 2FA",
"disable_exchange": "Deshabilitar intercambio",
"disable_fee_api_warning": "Al apagar esto, las tasas de tarifas pueden ser inexactas en algunos casos, por lo que puede terminar pagando en exceso o pagando menos las tarifas por sus transacciones",
"disable_fiat": "Deshabilitar fiat",
"disable_sell": "Desactivar acción de venta",
"disableBatteryOptimization": "Deshabilitar la optimización de la batería",
@ -354,6 +356,7 @@
"ledger_error_wrong_app": "Por favor, asegúrese de abrir la aplicación correcta en su libro mayor.",
"ledger_please_enable_bluetooth": "Habilite Bluetooth para detectar su libro mayor",
"light_theme": "Ligera",
"live_fee_rates": "Tasas de tarifas en vivo a través de API",
"load_more": "Carga más",
"loading_your_wallet": "Cargando tu billetera",
"login": "Iniciar sesión",
@ -894,4 +897,4 @@
"you_will_get": "Convertir a",
"you_will_send": "Convertir de",
"yy": "YY"
}
}

View file

@ -195,6 +195,7 @@
"delete_wallet_confirm_message": "Êtes-vous sûr de vouloir supprimer le portefeuille (wallet) ${wallet_name}?",
"deleteConnectionConfirmationPrompt": "Êtes-vous sûr de vouloir supprimer la connexion à",
"denominations": "Dénominations",
"derivationpath": "Chemin de dérivation",
"descending": "Descendant",
"description": "Description",
"destination_tag": "Tag de destination :",
@ -207,6 +208,7 @@
"disable_buy": "Désactiver l'action d'achat",
"disable_cake_2fa": "Désactiver Cake 2FA",
"disable_exchange": "Désactiver l'échange",
"disable_fee_api_warning": "En désactivant cela, les taux de frais peuvent être inexacts dans certains cas, vous pourriez donc finir par payer trop ou sous-paiement les frais pour vos transactions",
"disable_fiat": "Désactiver les montants en fiat",
"disable_sell": "Désactiver l'action de vente",
"disableBatteryOptimization": "Désactiver l'optimisation de la batterie",
@ -354,6 +356,7 @@
"ledger_error_wrong_app": "Veuillez vous assurer d'ouvrir la bonne application sur votre grand livre",
"ledger_please_enable_bluetooth": "Veuillez activer Bluetooth pour détecter votre grand livre",
"light_theme": "Clair",
"live_fee_rates": "Taux de frais en direct via l'API",
"load_more": "Charger plus",
"loading_your_wallet": "Chargement de votre portefeuille (wallet)",
"login": "Utilisateur",

View file

@ -195,6 +195,7 @@
"delete_wallet_confirm_message": "Shin kun tabbata cewa kuna son share jakar ${wallet_name}?",
"deleteConnectionConfirmationPrompt": "Shin kun tabbata cewa kuna son share haɗin zuwa",
"denominations": "Denominations",
"derivationpath": "Hanyar Nasara",
"descending": "Saukowa",
"description": "Bayani",
"destination_tag": "Tambarin makoma:",
@ -207,6 +208,7 @@
"disable_buy": "Kashe alama",
"disable_cake_2fa": "Musaki Cake 2FA",
"disable_exchange": "Kashe musanya",
"disable_fee_api_warning": "Ta hanyar juya wannan kashe, kudaden da zai iya zama ba daidai ba a wasu halaye, saboda haka zaku iya ƙare da overpaying ko a ƙarƙashin kudaden don ma'amaloli",
"disable_fiat": "Dakatar da fiat",
"disable_sell": "Kashe karbuwa",
"disableBatteryOptimization": "Kashe ingantawa baturi",
@ -354,6 +356,7 @@
"ledger_error_wrong_app": "Da fatan za a tabbata kun yi amfani da app ɗin dama akan dillalarku",
"ledger_please_enable_bluetooth": "Da fatan za a kunna Bluetooth don gano Ledger ɗinku",
"light_theme": "Haske",
"live_fee_rates": "Kudin Kiɗa ta API",
"load_more": "Like more",
"loading_your_wallet": "Ana loda walat ɗin ku",
"login": "Shiga",

View file

@ -195,6 +195,7 @@
"delete_wallet_confirm_message": "क्या आप वाकई ${wallet_name} वॉलेट हटाना चाहते हैं?",
"deleteConnectionConfirmationPrompt": "क्या आप वाकई कनेक्शन हटाना चाहते हैं?",
"denominations": "मूल्यवर्ग",
"derivationpath": "व्युत्पत्ति पथ",
"descending": "अवरोही",
"description": "विवरण",
"destination_tag": "गंतव्य टैग:",
@ -207,6 +208,7 @@
"disable_buy": "खरीद कार्रवाई अक्षम करें",
"disable_cake_2fa": "केक 2FA अक्षम करें",
"disable_exchange": "एक्सचेंज अक्षम करें",
"disable_fee_api_warning": "इसे बंद करने से, कुछ मामलों में शुल्क दरें गलत हो सकती हैं, इसलिए आप अपने लेनदेन के लिए फीस को कम कर सकते हैं या कम कर सकते हैं",
"disable_fiat": "िएट को अक्षम करें",
"disable_sell": "बेचने की कार्रवाई अक्षम करें",
"disableBatteryOptimization": "बैटरी अनुकूलन अक्षम करें",
@ -354,6 +356,7 @@
"ledger_error_wrong_app": "कृपया सुनिश्चित करें कि आप अपने लेजर पर सही ऐप को खोलते हैं",
"ledger_please_enable_bluetooth": "कृपया अपने बहीखाने का पता लगाने के लिए ब्लूटूथ को सक्षम करें",
"light_theme": "रोशनी",
"live_fee_rates": "एपीआई के माध्यम से लाइव शुल्क दरें",
"load_more": "और लोड करें",
"loading_your_wallet": "अपना बटुआ लोड कर रहा है",
"login": "लॉग इन करें",

View file

@ -195,6 +195,7 @@
"delete_wallet_confirm_message": "Jeste li sigurni da želite izbrisati ${wallet_name} novčanik?",
"deleteConnectionConfirmationPrompt": "Jeste li sigurni da želite izbrisati vezu s",
"denominations": "Denominacije",
"derivationpath": "Put derivacije",
"descending": "Silazni",
"description": "Opis",
"destination_tag": "Odredišna oznaka:",
@ -207,6 +208,7 @@
"disable_buy": "Onemogući kupnju",
"disable_cake_2fa": "Onemogući Cake 2FA",
"disable_exchange": "Onemogući exchange",
"disable_fee_api_warning": "Isključivanjem ovoga, stope naknade u nekim bi slučajevima mogle biti netočne, tako da biste mogli preplatiti ili predati naknadu za vaše transakcije",
"disable_fiat": "Isključi, fiat",
"disable_sell": "Onemogući akciju prodaje",
"disableBatteryOptimization": "Onemogući optimizaciju baterije",
@ -354,6 +356,7 @@
"ledger_error_wrong_app": "Obavezno obavezno otvorite pravu aplikaciju na knjizi",
"ledger_please_enable_bluetooth": "Omogućite Bluetooth da otkrije svoju knjigu",
"light_theme": "Svijetla",
"live_fee_rates": "Stope naknada uživo putem API -ja",
"load_more": "Učitaj više",
"loading_your_wallet": "Novčanik se učitava",
"login": "Prijava",

View file

@ -207,6 +207,7 @@
"disable_buy": "Անջատել գնում գործողությունը",
"disable_cake_2fa": "Անջատել Cake 2FA",
"disable_exchange": "Անջատել փոխանակումը",
"disable_fee_api_warning": "Դրանից անջատելով, վճարների տեմպերը որոշ դեպքերում կարող են անճիշտ լինել, այնպես որ դուք կարող եք վերջ տալ ձեր գործարքների համար վճարների գերավճարների կամ գերավճարների վրա",
"disable_fiat": "Անջատել ֆիատ",
"disable_sell": "Անջատել վաճառք գործողությունը",
"disableBatteryOptimization": "Անջատել մարտկոցի օպտիմիզացիան",
@ -354,6 +355,7 @@
"ledger_error_wrong_app": "Խնդրում ենք համոզվել, որ դուք բացել եք ճիշտ ծրագիրը ձեր Ledger-ում",
"ledger_please_enable_bluetooth": "Խնդրում ենք միացնել Bluetooth-ը ձեր Ledger-ը հայտնաբերելու համար",
"light_theme": "Լուսավոր",
"live_fee_rates": "Ապակի վարձավճարներ API- ի միջոցով",
"load_more": "Բեռնել ավելին",
"loading_your_wallet": "Ձեր հաշվեհամարը բեռնում է",
"login": "Մուտք",

View file

@ -195,6 +195,7 @@
"delete_wallet_confirm_message": "Apakah Anda yakin ingin menghapus dompet ${wallet_name}?",
"deleteConnectionConfirmationPrompt": "Apakah Anda yakin ingin menghapus koneksi ke",
"denominations": "Denominasi",
"derivationpath": "Jalur derivasi",
"descending": "Menurun",
"description": "Keterangan",
"destination_tag": "Tag tujuan:",
@ -207,6 +208,7 @@
"disable_buy": "Nonaktifkan tindakan beli",
"disable_cake_2fa": "Nonaktifkan Kue 2FA",
"disable_exchange": "Nonaktifkan pertukaran",
"disable_fee_api_warning": "Dengan mematikan ini, tarif biaya mungkin tidak akurat dalam beberapa kasus, jadi Anda mungkin akan membayar lebih atau membayar biaya untuk transaksi Anda",
"disable_fiat": "Nonaktifkan fiat",
"disable_sell": "Nonaktifkan aksi jual",
"disableBatteryOptimization": "Nonaktifkan optimasi baterai",
@ -354,6 +356,7 @@
"ledger_error_wrong_app": "Pastikan Anda membuka aplikasi yang tepat di buku besar Anda",
"ledger_please_enable_bluetooth": "Harap aktifkan Bluetooth untuk mendeteksi buku besar Anda",
"light_theme": "Terang",
"live_fee_rates": "Tarif biaya langsung melalui API",
"load_more": "Muat lebih banyak",
"loading_your_wallet": "Memuat dompet Anda",
"login": "Masuk",

View file

@ -196,6 +196,7 @@
"delete_wallet_confirm_message": "Sei sicuro di voler eliminare il portafoglio ${wallet_name}?",
"deleteConnectionConfirmationPrompt": "Sei sicuro di voler eliminare la connessione a",
"denominations": "Denominazioni",
"derivationpath": "Percorso di derivazione",
"descending": "Discendente",
"description": "Descrizione",
"destination_tag": "Tag destinazione:",
@ -208,6 +209,7 @@
"disable_buy": "Disabilita l'azione di acquisto",
"disable_cake_2fa": "Disabilita Cake 2FA",
"disable_exchange": "Disabilita scambio",
"disable_fee_api_warning": "Disattivando questo, i tassi delle commissioni potrebbero essere inaccurati in alcuni casi, quindi potresti finire in eccesso o sostenere le commissioni per le transazioni",
"disable_fiat": "Disabilita fiat",
"disable_sell": "Disabilita l'azione di vendita",
"disableBatteryOptimization": "Disabilita l'ottimizzazione della batteria",
@ -355,6 +357,7 @@
"ledger_error_wrong_app": "Assicurati di aprire l'app giusta sul libro mastro",
"ledger_please_enable_bluetooth": "Si prega di consentire al Bluetooth di rilevare il libro mastro",
"light_theme": "Bianco",
"live_fee_rates": "Tariffe delle commissioni dal vivo tramite API",
"load_more": "Carica di più",
"loading_your_wallet": "Caricamento portafoglio",
"login": "Accedi",

View file

@ -195,6 +195,7 @@
"delete_wallet_confirm_message": "${wallet_name} ウォレットを削除してもよろしいですか?",
"deleteConnectionConfirmationPrompt": "への接続を削除してもよろしいですか?",
"denominations": "宗派",
"derivationpath": "派生パス",
"descending": "下降",
"description": "説明",
"destination_tag": "宛先タグ:",
@ -207,6 +208,7 @@
"disable_buy": "購入アクションを無効にする",
"disable_cake_2fa": "Cake 2FA を無効にする",
"disable_exchange": "交換を無効にする",
"disable_fee_api_warning": "これをオフにすることで、料金金利は場合によっては不正確になる可能性があるため、取引の費用が過払いまたは不足している可能性があります",
"disable_fiat": "フィアットを無効にする",
"disable_sell": "販売アクションを無効にする",
"disableBatteryOptimization": "バッテリーの最適化を無効にします",
@ -355,6 +357,7 @@
"ledger_error_wrong_app": "元帳に適切なアプリを開始するようにしてください",
"ledger_please_enable_bluetooth": "Bluetoothが元帳を検出できるようにしてください",
"light_theme": "光",
"live_fee_rates": "API経由のライブ料金",
"load_more": "もっと読み込む",
"loading_your_wallet": "ウォレットをロードしています",
"login": "ログイン",

View file

@ -195,6 +195,7 @@
"delete_wallet_confirm_message": "${wallet_name} 지갑을 삭제하시겠습니까?",
"deleteConnectionConfirmationPrompt": "다음 연결을 삭제하시겠습니까?",
"denominations": "교파",
"derivationpath": "파생 경로",
"descending": "내림차순",
"description": "설명",
"destination_tag": "목적지 태그:",
@ -207,6 +208,7 @@
"disable_buy": "구매 행동 비활성화",
"disable_cake_2fa": "케이크 2FA 비활성화",
"disable_exchange": "교환 비활성화",
"disable_fee_api_warning": "이것을 끄면 경우에 따라 수수료가 부정확 할 수 있으므로 거래 수수료를 초과 지불하거나 지불 할 수 있습니다.",
"disable_fiat": "법정화폐 비활성화",
"disable_sell": "판매 조치 비활성화",
"disableBatteryOptimization": "배터리 최적화를 비활성화합니다",
@ -354,6 +356,7 @@
"ledger_error_wrong_app": "원장에서 올바른 앱을 반대하는지 확인하십시오.",
"ledger_please_enable_bluetooth": "Bluetooth가 원장을 감지 할 수 있도록하십시오",
"light_theme": "빛",
"live_fee_rates": "API를 통한 라이브 요금 요금",
"load_more": "더로드하십시오",
"loading_your_wallet": "지갑 넣기",
"login": "로그인",

View file

@ -195,6 +195,7 @@
"delete_wallet_confirm_message": "${wallet_name} ပိုက်ဆံအိတ်ကို ဖျက်လိုသည်မှာ သေချာပါသလား။",
"deleteConnectionConfirmationPrompt": "ချိတ်ဆက်မှုကို ဖျက်လိုသည်မှာ သေချာပါသလား။",
"denominations": "ဂိုဏ်းချုပ်ပစ္စည်းများ",
"derivationpath": "derivation လမ်းကြောင်း",
"descending": "ဆင်း",
"description": "ဖော်ပြချက်",
"destination_tag": "ခရီးဆုံးအမှတ်-",
@ -207,6 +208,7 @@
"disable_buy": "ဝယ်ယူမှု လုပ်ဆောင်ချက်ကို ပိတ်ပါ။",
"disable_cake_2fa": "ကိတ်မုန့် 2FA ကို ပိတ်ပါ။",
"disable_exchange": "လဲလှယ်မှုကို ပိတ်ပါ။",
"disable_fee_api_warning": "ဤအရာကိုဖွင့်ခြင်းအားဖြင့်အချို့သောကိစ္စရပ်များတွင်အခကြေးငွေနှုန်းထားများသည်တိကျမှုရှိနိုင်သည်,",
"disable_fiat": "Fiat ကိုပိတ်ပါ။",
"disable_sell": "ရောင်းချခြင်းလုပ်ဆောင်ချက်ကို ပိတ်ပါ။",
"disableBatteryOptimization": "ဘက်ထရီ optimization ကိုပိတ်ပါ",
@ -354,6 +356,7 @@
"ledger_error_wrong_app": "ကျေးဇူးပြု. သင့်လက်ျာအက်ပ်ကိုသင်၏ Ledger တွင်ဖွင့်ရန်သေချာစေပါ",
"ledger_please_enable_bluetooth": "သင်၏ Ledger ကိုရှာဖွေရန် Bluetooth ကိုဖွင့်ပါ",
"light_theme": "အလင်း",
"live_fee_rates": "API မှတစ်ဆင့် Live အခကြေးငွေနှုန်းထားများ",
"load_more": "ပိုပြီး load",
"loading_your_wallet": "သင့်ပိုက်ဆံအိတ်ကို ဖွင့်နေသည်။",
"login": "လော့ဂ်အင်",

View file

@ -195,6 +195,7 @@
"delete_wallet_confirm_message": "Weet u zeker dat u de portemonnee van ${wallet_name} wilt verwijderen?",
"deleteConnectionConfirmationPrompt": "Weet u zeker dat u de verbinding met",
"denominations": "Denominaties",
"derivationpath": "Afleidingspad",
"descending": "Aflopend",
"description": "Beschrijving",
"destination_tag": "Bestemmingstag:",
@ -207,6 +208,7 @@
"disable_buy": "Koopactie uitschakelen",
"disable_cake_2fa": "Taart 2FA uitschakelen",
"disable_exchange": "Uitwisseling uitschakelen",
"disable_fee_api_warning": "Door dit uit te schakelen, kunnen de tarieven in sommige gevallen onnauwkeurig zijn, dus u kunt de vergoedingen voor uw transacties te veel betalen of te weinig betalen",
"disable_fiat": "Schakel Fiat uit",
"disable_sell": "Verkoopactie uitschakelen",
"disableBatteryOptimization": "Schakel de batterijoptimalisatie uit",
@ -354,6 +356,7 @@
"ledger_error_wrong_app": "Zorg ervoor dat u de juiste app op uw grootboek opent",
"ledger_please_enable_bluetooth": "Schakel Bluetooth in staat om uw grootboek te detecteren",
"light_theme": "Licht",
"live_fee_rates": "Live -tarieven via API",
"load_more": "Meer laden",
"loading_your_wallet": "Uw portemonnee laden",
"login": "Log in",

View file

@ -195,6 +195,7 @@
"delete_wallet_confirm_message": "Czy na pewno chcesz usunąć portfel ${wallet_name}?",
"deleteConnectionConfirmationPrompt": "Czy na pewno chcesz usunąć połączenie z",
"denominations": "Wyznaczenia",
"derivationpath": "Ścieżka pochodna",
"descending": "Schodzenie",
"description": "Opis",
"destination_tag": "Tag docelowy:",
@ -207,6 +208,7 @@
"disable_buy": "Wyłącz akcję kupna",
"disable_cake_2fa": "Wyłącz Cake 2FA",
"disable_exchange": "Wyłącz wymianę",
"disable_fee_api_warning": "Wyłączając to, stawki opłaty mogą być w niektórych przypadkach niedokładne, więc możesz skończyć się przepłaceniem lub wynagrodzeniem opłat za transakcje",
"disable_fiat": "Wyłącz waluty FIAT",
"disable_sell": "Wyłącz akcję sprzedaży",
"disableBatteryOptimization": "Wyłącz optymalizację baterii",
@ -354,6 +356,7 @@
"ledger_error_wrong_app": "Upewnij się, że opisz odpowiednią aplikację na swojej księdze",
"ledger_please_enable_bluetooth": "Włącz Bluetooth wykrywanie księgi",
"light_theme": "Jasny",
"live_fee_rates": "Stawki opłaty na żywo za pośrednictwem API",
"load_more": "Załaduj więcej",
"loading_your_wallet": "Ładowanie portfela",
"login": "Login",

Some files were not shown because too many files have changed in this diff Show more