Merge branch 'main' into CW-685-passphrase-support-for-monero-wownero-wallets

This commit is contained in:
cyan 2024-10-24 12:17:43 +02:00 committed by GitHub
commit d361b180c5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
111 changed files with 1960 additions and 1055 deletions

View file

@ -224,6 +224,7 @@ jobs:
cd /opt/android/cake_wallet/build/app/outputs/flutter-apk cd /opt/android/cake_wallet/build/app/outputs/flutter-apk
mkdir test-apk mkdir test-apk
cp app-arm64-v8a-release.apk test-apk/${{env.BRANCH_NAME}}.apk cp app-arm64-v8a-release.apk test-apk/${{env.BRANCH_NAME}}.apk
cp app-x86_64-release.apk test-apk/${{env.BRANCH_NAME}}_x86.apk
- name: Upload Artifact - name: Upload Artifact
uses: kittaakos/upload-artifact-as-is@v0 uses: kittaakos/upload-artifact-as-is@v0

View file

@ -1,35 +1,30 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="__APP_PACKAGE__"> package="__APP_PACKAGE__">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.USE_FINGERPRINT"/> <uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_INTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/> <uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<!--bibo01 : hardware option--> <!-- bibo01 : hardware option-->
<uses-feature android:name="android.hardware.bluetooth" android:required="false"/> <uses-feature android:name="android.hardware.bluetooth" android:required="false" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="false"/> <uses-feature android:name="android.hardware.bluetooth_le" android:required="false" />
<!-- required for API 18 - 30 --> <!-- required for API 18 - 30 -->
<uses-permission <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
android:name="android.permission.BLUETOOTH" <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
android:maxSdkVersion="30" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission
android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<!-- API 31+ --> <!-- required for API <= 29 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" />
<!-- API 31+ -->
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission <uses-permission android:name="android.permission.BLUETOOTH_SCAN" android:usesPermissionFlags="neverForLocation" />
android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" /> <uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<application <application
android:name=".Application" android:name=".Application"
android:label="${APP_NAME}" android:label="${APP_NAME}"

View file

@ -3,6 +3,7 @@
useSSL: true useSSL: true
- -
uri: btc-electrum.cakewallet.com:50002 uri: btc-electrum.cakewallet.com:50002
useSSL: true
isDefault: true isDefault: true
- -
uri: electrs.cakewallet.com:50001 uri: electrs.cakewallet.com:50001

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -1,4 +1,3 @@
Monero enhancements for sending and address generation Monero enhancements
StealthEx Introducing StealthEx and LetxExchange
LetsExchange Bug fixes
Visual enhancements and bug fixes

View file

@ -1,9 +1,7 @@
Add Litecoin MWEB Added Litecoin MWEB
Wallet groups (same seed, multiple wallets) Added wallet groups
Silent Payments enhancements Silent Payment enhancements for speed & reliability
Monero enhancements for sending and address generation Monero enhancements
StealthEx Introducing StealthEx and LetxExchange
LetsExchange Additional ERC20 tokens scam detection
Replace-By-Fee improvements Bug fixes
ERC20 tokens potential scam detection
Visual enhancements and bug fixes

View file

@ -15,7 +15,7 @@ These steps will help you configure and execute a build of CakeWallet from its s
### 1. Installing Package Dependencies ### 1. Installing Package Dependencies
CakeWallet requires some packages to be install on your build system. You may easily install them on your build system with the following command: CakeWallet requires some packages to be installed on your build system. You may easily install them on your build system with the following command:
`$ sudo apt install build-essential cmake pkg-config git curl autoconf libtool` `$ sudo apt install build-essential cmake pkg-config git curl autoconf libtool`
@ -145,7 +145,7 @@ Path to executable file will be:
# Flatpak # Flatpak
For package the built application into flatpak you need fistly to install `flatpak` and `flatpak-builder`: For package the built application into flatpak you need firstly to install `flatpak` and `flatpak-builder`:
`$ sudo apt install flatpak flatpak-builder` `$ sudo apt install flatpak flatpak-builder`

View file

@ -5,30 +5,31 @@ import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:cw_bitcoin/utils.dart'; import 'package:cw_bitcoin/utils.dart';
import 'package:cw_core/hardware/hardware_account_data.dart'; import 'package:cw_core/hardware/hardware_account_data.dart';
import 'package:ledger_bitcoin/ledger_bitcoin.dart'; import 'package:ledger_bitcoin/ledger_bitcoin.dart';
import 'package:ledger_flutter/ledger_flutter.dart'; import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
class BitcoinHardwareWalletService { class BitcoinHardwareWalletService {
BitcoinHardwareWalletService(this.ledger, this.device); BitcoinHardwareWalletService(this.ledgerConnection);
final Ledger ledger; final LedgerConnection ledgerConnection;
final LedgerDevice device;
Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async { Future<List<HardwareAccountData>> getAvailableAccounts(
final bitcoinLedgerApp = BitcoinLedgerApp(ledger); {int index = 0, int limit = 5}) async {
final bitcoinLedgerApp = BitcoinLedgerApp(ledgerConnection);
final masterFp = await bitcoinLedgerApp.getMasterFingerprint(device); final masterFp = await bitcoinLedgerApp.getMasterFingerprint();
print(masterFp);
final accounts = <HardwareAccountData>[]; final accounts = <HardwareAccountData>[];
final indexRange = List.generate(limit, (i) => i + index); final indexRange = List.generate(limit, (i) => i + index);
for (final i in indexRange) { for (final i in indexRange) {
final derivationPath = "m/84'/0'/$i'"; final derivationPath = "m/84'/0'/$i'";
final xpub = await bitcoinLedgerApp.getXPubKey(device, derivationPath: derivationPath); final xpub =
await bitcoinLedgerApp.getXPubKey(derivationPath: derivationPath);
Bip32Slip10Secp256k1 hd = Bip32Slip10Secp256k1 hd =
Bip32Slip10Secp256k1.fromExtendedKey(xpub).childKey(Bip32KeyIndex(0)); Bip32Slip10Secp256k1.fromExtendedKey(xpub).childKey(Bip32KeyIndex(0));
final address = generateP2WPKHAddress(hd: hd, index: 0, network: BitcoinNetwork.mainnet); final address = generateP2WPKHAddress(
hd: hd, index: 0, network: BitcoinNetwork.mainnet);
accounts.add(HardwareAccountData( accounts.add(HardwareAccountData(
address: address, address: address,

View file

@ -1,11 +1,13 @@
import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; import 'package:cw_bitcoin/bitcoin_transaction_priority.dart';
import 'package:cw_core/output_info.dart'; import 'package:cw_core/output_info.dart';
import 'package:cw_core/unspent_coin_type.dart';
class BitcoinTransactionCredentials { class BitcoinTransactionCredentials {
BitcoinTransactionCredentials(this.outputs, BitcoinTransactionCredentials(this.outputs,
{required this.priority, this.feeRate}); {required this.priority, this.feeRate, this.coinTypeToSpendFrom = UnspentCoinType.any});
final List<OutputInfo> outputs; final List<OutputInfo> outputs;
final BitcoinTransactionPriority? priority; final BitcoinTransactionPriority? priority;
final int? feeRate; final int? feeRate;
final UnspentCoinType coinTypeToSpendFrom;
} }

View file

@ -5,13 +5,13 @@ import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/bitcoin_mnemonic.dart'; import 'package:cw_bitcoin/bitcoin_mnemonic.dart';
import 'package:cw_bitcoin/psbt_transaction_builder.dart';
import 'package:cw_core/encryption_file_utils.dart'; import 'package:cw_core/encryption_file_utils.dart';
import 'package:cw_bitcoin/electrum_derivations.dart'; import 'package:cw_bitcoin/electrum_derivations.dart';
import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart'; import 'package:cw_bitcoin/bitcoin_wallet_addresses.dart';
import 'package:cw_bitcoin/electrum_balance.dart'; import 'package:cw_bitcoin/electrum_balance.dart';
import 'package:cw_bitcoin/electrum_wallet.dart'; import 'package:cw_bitcoin/electrum_wallet.dart';
import 'package:cw_bitcoin/electrum_wallet_snapshot.dart'; import 'package:cw_bitcoin/electrum_wallet_snapshot.dart';
import 'package:cw_bitcoin/psbt_transaction_builder.dart';
import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/unspent_coins_info.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
@ -19,7 +19,7 @@ import 'package:cw_core/wallet_keys_file.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:ledger_bitcoin/ledger_bitcoin.dart'; import 'package:ledger_bitcoin/ledger_bitcoin.dart';
import 'package:ledger_flutter/ledger_flutter.dart'; import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
part 'bitcoin_wallet.g.dart'; part 'bitcoin_wallet.g.dart';
@ -61,8 +61,9 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
initialBalance: initialBalance, initialBalance: initialBalance,
seedBytes: seedBytes, seedBytes: seedBytes,
encryptionFileUtils: encryptionFileUtils, encryptionFileUtils: encryptionFileUtils,
currency: currency: networkParam == BitcoinNetwork.testnet
networkParam == BitcoinNetwork.testnet ? CryptoCurrency.tbtc : CryptoCurrency.btc, ? CryptoCurrency.tbtc
: CryptoCurrency.btc,
alwaysScan: alwaysScan, alwaysScan: alwaysScan,
) { ) {
// in a standard BIP44 wallet, mainHd derivation path = m/84'/0'/0'/0 (account 0, index unspecified here) // in a standard BIP44 wallet, mainHd derivation path = m/84'/0'/0'/0 (account 0, index unspecified here)
@ -80,11 +81,14 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
mainHd: hd, mainHd: hd,
sideHd: accountHD.childKey(Bip32KeyIndex(1)), sideHd: accountHD.childKey(Bip32KeyIndex(1)),
network: networkParam ?? network, network: networkParam ?? network,
masterHd: seedBytes != null ? Bip32Slip10Secp256k1.fromSeed(seedBytes) : null, masterHd:
seedBytes != null ? Bip32Slip10Secp256k1.fromSeed(seedBytes) : null,
isHardwareWallet: walletInfo.isHardwareWallet,
); );
autorun((_) { autorun((_) {
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress; this.walletAddresses.isEnabledAutoGenerateSubaddress =
this.isEnabledAutoGenerateSubaddress;
}); });
} }
@ -185,8 +189,10 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
walletInfo.derivationInfo ??= DerivationInfo(); walletInfo.derivationInfo ??= DerivationInfo();
// set the default if not present: // set the default if not present:
walletInfo.derivationInfo!.derivationPath ??= snp?.derivationPath ?? electrum_path; walletInfo.derivationInfo!.derivationPath ??=
walletInfo.derivationInfo!.derivationType ??= snp?.derivationType ?? DerivationType.electrum; snp?.derivationPath ?? electrum_path;
walletInfo.derivationInfo!.derivationType ??=
snp?.derivationType ?? DerivationType.electrum;
Uint8List? seedBytes = null; Uint8List? seedBytes = null;
final mnemonic = keysData.mnemonic; final mnemonic = keysData.mnemonic;
@ -228,15 +234,14 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
); );
} }
Ledger? _ledger; LedgerConnection? _ledgerConnection;
LedgerDevice? _ledgerDevice;
BitcoinLedgerApp? _bitcoinLedgerApp; BitcoinLedgerApp? _bitcoinLedgerApp;
void setLedger(Ledger setLedger, LedgerDevice setLedgerDevice) { @override
_ledger = setLedger; void setLedgerConnection(LedgerConnection connection) {
_ledgerDevice = setLedgerDevice; _ledgerConnection = connection;
_bitcoinLedgerApp = _bitcoinLedgerApp = BitcoinLedgerApp(_ledgerConnection!,
BitcoinLedgerApp(_ledger!, derivationPath: walletInfo.derivationInfo!.derivationPath!); derivationPath: walletInfo.derivationInfo!.derivationPath!);
} }
@override @override
@ -251,12 +256,14 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
BitcoinOrdering inputOrdering = BitcoinOrdering.bip69, BitcoinOrdering inputOrdering = BitcoinOrdering.bip69,
BitcoinOrdering outputOrdering = BitcoinOrdering.bip69, BitcoinOrdering outputOrdering = BitcoinOrdering.bip69,
}) async { }) async {
final masterFingerprint = await _bitcoinLedgerApp!.getMasterFingerprint(_ledgerDevice!); final masterFingerprint = await _bitcoinLedgerApp!.getMasterFingerprint();
final psbtReadyInputs = <PSBTReadyUtxoWithAddress>[]; final psbtReadyInputs = <PSBTReadyUtxoWithAddress>[];
for (final utxo in utxos) { for (final utxo in utxos) {
final rawTx = await electrumClient.getTransactionHex(hash: utxo.utxo.txHash); final rawTx =
final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!; await electrumClient.getTransactionHex(hash: utxo.utxo.txHash);
final publicKeyAndDerivationPath =
publicKeys[utxo.ownerDetails.address.pubKeyHash()]!;
psbtReadyInputs.add(PSBTReadyUtxoWithAddress( psbtReadyInputs.add(PSBTReadyUtxoWithAddress(
utxo: utxo.utxo, utxo: utxo.utxo,
@ -268,10 +275,10 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
)); ));
} }
final psbt = final psbt = PSBTTransactionBuild(
PSBTTransactionBuild(inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF); inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF);
final rawHex = await _bitcoinLedgerApp!.signPsbt(_ledgerDevice!, psbt: psbt.psbt); final rawHex = await _bitcoinLedgerApp!.signPsbt(psbt: psbt.psbt);
return BtcTransaction.fromRaw(BytesUtils.toHexString(rawHex)); return BtcTransaction.fromRaw(BytesUtils.toHexString(rawHex));
} }
@ -279,14 +286,16 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
Future<String> signMessage(String message, {String? address = null}) async { Future<String> signMessage(String message, {String? address = null}) async {
if (walletInfo.isHardwareWallet) { if (walletInfo.isHardwareWallet) {
final addressEntry = address != null final addressEntry = address != null
? walletAddresses.allAddresses.firstWhere((element) => element.address == address) ? walletAddresses.allAddresses
.firstWhere((element) => element.address == address)
: null; : null;
final index = addressEntry?.index ?? 0; final index = addressEntry?.index ?? 0;
final isChange = addressEntry?.isHidden == true ? 1 : 0; final isChange = addressEntry?.isHidden == true ? 1 : 0;
final accountPath = walletInfo.derivationInfo?.derivationPath; final accountPath = walletInfo.derivationInfo?.derivationPath;
final derivationPath = accountPath != null ? "$accountPath/$isChange/$index" : null; final derivationPath =
accountPath != null ? "$accountPath/$isChange/$index" : null;
final signature = await _bitcoinLedgerApp!.signMessage(_ledgerDevice!, final signature = await _bitcoinLedgerApp!.signMessage(
message: ascii.encode(message), signDerivationPath: derivationPath); message: ascii.encode(message), signDerivationPath: derivationPath);
return base64Encode(signature); return base64Encode(signature);
} }

View file

@ -15,6 +15,7 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
required super.mainHd, required super.mainHd,
required super.sideHd, required super.sideHd,
required super.network, required super.network,
required super.isHardwareWallet,
super.initialAddresses, super.initialAddresses,
super.initialRegularAddressIndex, super.initialRegularAddressIndex,
super.initialChangeAddressIndex, super.initialChangeAddressIndex,

View file

@ -68,8 +68,8 @@ class ElectrumClient {
try { try {
await socket?.close(); await socket?.close();
socket = null;
} catch (_) {} } catch (_) {}
socket = null;
try { try {
if (useSSL == false || (useSSL == null && uri.toString().contains("btc-electrum"))) { if (useSSL == false || (useSSL == null && uri.toString().contains("btc-electrum"))) {
@ -102,7 +102,8 @@ class ElectrumClient {
return; return;
} }
_setConnectionStatus(ConnectionStatus.connected); // use ping to determine actual connection status since we could've just not timed out yet:
// _setConnectionStatus(ConnectionStatus.connected);
socket!.listen( socket!.listen(
(Uint8List event) { (Uint8List event) {
@ -128,7 +129,7 @@ class ElectrumClient {
print("SOCKET CLOSED!!!!!"); print("SOCKET CLOSED!!!!!");
unterminatedString = ''; unterminatedString = '';
try { try {
if (host == socket?.address.host) { if (host == socket?.address.host || socket == null) {
_setConnectionStatus(ConnectionStatus.disconnected); _setConnectionStatus(ConnectionStatus.disconnected);
socket?.destroy(); socket?.destroy();
} }
@ -178,7 +179,7 @@ class ElectrumClient {
unterminatedString = ''; unterminatedString = '';
} }
} catch (e) { } catch (e) {
print(e.toString()); print("parse $e");
} }
} }
@ -191,7 +192,7 @@ class ElectrumClient {
try { try {
await callWithTimeout(method: 'server.ping'); await callWithTimeout(method: 'server.ping');
_setConnectionStatus(ConnectionStatus.connected); _setConnectionStatus(ConnectionStatus.connected);
} on RequestFailedTimeoutException catch (_) { } catch (_) {
_setConnectionStatus(ConnectionStatus.disconnected); _setConnectionStatus(ConnectionStatus.disconnected);
} }
} }
@ -422,7 +423,7 @@ class ElectrumClient {
BehaviorSubject<T>? subscribe<T>( BehaviorSubject<T>? subscribe<T>(
{required String id, required String method, List<Object> params = const []}) { {required String id, required String method, List<Object> params = const []}) {
try { try {
if (socket == null || !isConnected) { if (socket == null) {
return null; return null;
} }
final subscription = BehaviorSubject<T>(); final subscription = BehaviorSubject<T>();
@ -431,14 +432,14 @@ class ElectrumClient {
return subscription; return subscription;
} catch (e) { } catch (e) {
print(e.toString()); print("subscribe $e");
return null; return null;
} }
} }
Future<dynamic> call( Future<dynamic> call(
{required String method, List<Object> params = const [], Function(int)? idCallback}) async { {required String method, List<Object> params = const [], Function(int)? idCallback}) async {
if (socket == null || !isConnected) { if (socket == null) {
return null; return null;
} }
final completer = Completer<dynamic>(); final completer = Completer<dynamic>();
@ -454,7 +455,7 @@ class ElectrumClient {
Future<dynamic> callWithTimeout( Future<dynamic> callWithTimeout(
{required String method, List<Object> params = const [], int timeout = 5000}) async { {required String method, List<Object> params = const [], int timeout = 5000}) async {
try { try {
if (socket == null || !isConnected) { if (socket == null) {
return null; return null;
} }
final completer = Completer<dynamic>(); final completer = Completer<dynamic>();
@ -470,7 +471,8 @@ class ElectrumClient {
return completer.future; return completer.future;
} catch (e) { } catch (e) {
print(e.toString()); print("callWithTimeout $e");
rethrow;
} }
} }
@ -537,6 +539,12 @@ class ElectrumClient {
onConnectionStatusChange?.call(status); onConnectionStatusChange?.call(status);
_connectionStatus = status; _connectionStatus = status;
_isConnected = status == ConnectionStatus.connected; _isConnected = status == ConnectionStatus.connected;
if (!_isConnected) {
try {
socket?.destroy();
} catch (_) {}
socket = null;
}
} }
void _handleResponse(Map<String, dynamic> response) { void _handleResponse(Map<String, dynamic> response) {

View file

@ -4,8 +4,8 @@ import 'dart:io';
import 'dart:isolate'; import 'dart:isolate';
import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_wallet.dart';
import 'package:shared_preferences/shared_preferences.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:blockchain_utils/blockchain_utils.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:cw_bitcoin/address_from_output.dart'; import 'package:cw_bitcoin/address_from_output.dart';
@ -25,6 +25,8 @@ import 'package:cw_bitcoin/exceptions.dart';
import 'package:cw_bitcoin/pending_bitcoin_transaction.dart'; import 'package:cw_bitcoin/pending_bitcoin_transaction.dart';
import 'package:cw_bitcoin/utils.dart'; import 'package:cw_bitcoin/utils.dart';
import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/encryption_file_utils.dart';
import 'package:cw_core/get_height_by_date.dart';
import 'package:cw_core/node.dart'; import 'package:cw_core/node.dart';
import 'package:cw_core/pathForWallet.dart'; import 'package:cw_core/pathForWallet.dart';
import 'package:cw_core/pending_transaction.dart'; import 'package:cw_core/pending_transaction.dart';
@ -36,9 +38,10 @@ import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_keys_file.dart'; import 'package:cw_core/wallet_keys_file.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
import 'package:cw_core/get_height_by_date.dart'; import 'package:cw_core/unspent_coin_type.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:rxdart/subjects.dart'; import 'package:rxdart/subjects.dart';
import 'package:sp_scanner/sp_scanner.dart'; import 'package:sp_scanner/sp_scanner.dart';
@ -49,9 +52,10 @@ part 'electrum_wallet.g.dart';
class ElectrumWallet = ElectrumWalletBase with _$ElectrumWallet; class ElectrumWallet = ElectrumWalletBase with _$ElectrumWallet;
abstract class ElectrumWalletBase abstract class ElectrumWalletBase extends WalletBase<
extends WalletBase<ElectrumBalance, ElectrumTransactionHistory, ElectrumTransactionInfo> ElectrumBalance,
with Store, WalletKeysFile { ElectrumTransactionHistory,
ElectrumTransactionInfo> with Store, WalletKeysFile {
ElectrumWalletBase({ ElectrumWalletBase({
required String password, required String password,
required WalletInfo walletInfo, required WalletInfo walletInfo,
@ -67,8 +71,8 @@ abstract class ElectrumWalletBase
ElectrumBalance? initialBalance, ElectrumBalance? initialBalance,
CryptoCurrency? currency, CryptoCurrency? currency,
this.alwaysScan, this.alwaysScan,
}) : accountHD = }) : accountHD = getAccountHDWallet(
getAccountHDWallet(currency, network, seedBytes, xpub, walletInfo.derivationInfo), currency, network, seedBytes, xpub, walletInfo.derivationInfo),
syncStatus = NotConnectedSyncStatus(), syncStatus = NotConnectedSyncStatus(),
_password = password, _password = password,
_feeRates = <int>[], _feeRates = <int>[],
@ -103,8 +107,12 @@ abstract class ElectrumWalletBase
sharedPrefs.complete(SharedPreferences.getInstance()); sharedPrefs.complete(SharedPreferences.getInstance());
} }
static Bip32Slip10Secp256k1 getAccountHDWallet(CryptoCurrency? currency, BasedUtxoNetwork network, static Bip32Slip10Secp256k1 getAccountHDWallet(
Uint8List? seedBytes, String? xpub, DerivationInfo? derivationInfo) { CryptoCurrency? currency,
BasedUtxoNetwork network,
Uint8List? seedBytes,
String? xpub,
DerivationInfo? derivationInfo) {
if (seedBytes == null && xpub == null) { if (seedBytes == null && xpub == null) {
throw Exception( throw Exception(
"To create a Wallet you need either a seed or an xpub. This should not happen"); "To create a Wallet you need either a seed or an xpub. This should not happen");
@ -115,8 +123,9 @@ abstract class ElectrumWalletBase
case CryptoCurrency.btc: case CryptoCurrency.btc:
case CryptoCurrency.ltc: case CryptoCurrency.ltc:
case CryptoCurrency.tbtc: case CryptoCurrency.tbtc:
return Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath( return Bip32Slip10Secp256k1.fromSeed(seedBytes, getKeyNetVersion(network))
_hardenedDerivationPath(derivationInfo?.derivationPath ?? electrum_path)) .derivePath(_hardenedDerivationPath(
derivationInfo?.derivationPath ?? electrum_path))
as Bip32Slip10Secp256k1; as Bip32Slip10Secp256k1;
case CryptoCurrency.bch: case CryptoCurrency.bch:
return bitcoinCashHDWallet(seedBytes); return bitcoinCashHDWallet(seedBytes);
@ -125,15 +134,26 @@ abstract class ElectrumWalletBase
} }
} }
return Bip32Slip10Secp256k1.fromExtendedKey(xpub!); return Bip32Slip10Secp256k1.fromExtendedKey(
xpub!, getKeyNetVersion(network));
} }
static Bip32Slip10Secp256k1 bitcoinCashHDWallet(Uint8List seedBytes) => static Bip32Slip10Secp256k1 bitcoinCashHDWallet(Uint8List seedBytes) =>
Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/44'/145'/0'") as Bip32Slip10Secp256k1; Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/44'/145'/0'")
as Bip32Slip10Secp256k1;
static int estimatedTransactionSize(int inputsCount, int outputsCounts) => static int estimatedTransactionSize(int inputsCount, int outputsCounts) =>
inputsCount * 68 + outputsCounts * 34 + 10; inputsCount * 68 + outputsCounts * 34 + 10;
static Bip32KeyNetVersions? getKeyNetVersion(BasedUtxoNetwork network) {
switch (network) {
case LitecoinNetwork.mainnet:
return Bip44Conf.litecoinMainNet.altKeyNetVer;
default:
return null;
}
}
bool? alwaysScan; bool? alwaysScan;
final Bip32Slip10Secp256k1 accountHD; final Bip32Slip10Secp256k1 accountHD;
@ -248,7 +268,7 @@ abstract class ElectrumWalletBase
int? _currentChainTip; int? _currentChainTip;
Future<int> getCurrentChainTip() async { Future<int> getCurrentChainTip() async {
if (_currentChainTip != null) { if ((_currentChainTip ?? 0) > 0) {
return _currentChainTip!; return _currentChainTip!;
} }
_currentChainTip = await electrumClient.getCurrentBlockChainTip() ?? 0; _currentChainTip = await electrumClient.getCurrentBlockChainTip() ?? 0;
@ -300,6 +320,7 @@ abstract class ElectrumWalletBase
@action @action
Future<void> _setListeners(int height, {int? chainTipParam, bool? doSingleScan}) async { Future<void> _setListeners(int height, {int? chainTipParam, bool? doSingleScan}) async {
if (this is! BitcoinWallet) return;
final chainTip = chainTipParam ?? await getUpdatedChainTip(); final chainTip = chainTipParam ?? await getUpdatedChainTip();
if (chainTip == height) { if (chainTip == height) {
@ -466,7 +487,7 @@ abstract class ElectrumWalletBase
} }
} catch (e, stacktrace) { } catch (e, stacktrace) {
print(stacktrace); print(stacktrace);
print(e.toString()); print("startSync $e");
syncStatus = FailedSyncStatus(); syncStatus = FailedSyncStatus();
} }
} }
@ -478,10 +499,10 @@ abstract class ElectrumWalletBase
final response = final response =
await http.get(Uri.parse("http://mempool.cakewallet.com:8999/api/v1/fees/recommended")); 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 result = json.decode(response.body) as Map<String, dynamic>;
final slowFee = result['economyFee']?.toInt() ?? 0; final slowFee = (result['economyFee'] as num?)?.toInt() ?? 0;
int mediumFee = result['hourFee']?.toInt() ?? 0; int mediumFee = (result['hourFee'] as num?)?.toInt() ?? 0;
int fastFee = result['fastestFee']?.toInt() ?? 0; int fastFee = (result['fastestFee'] as num?)?.toInt() ?? 0;
if (slowFee == mediumFee) { if (slowFee == mediumFee) {
mediumFee++; mediumFee++;
} }
@ -490,7 +511,9 @@ abstract class ElectrumWalletBase
} }
_feeRates = [slowFee, mediumFee, fastFee]; _feeRates = [slowFee, mediumFee, fastFee];
return; return;
} catch (_) {} } catch (e) {
print(e);
}
} }
final feeRates = await electrumClient.feeRates(network: network); final feeRates = await electrumClient.feeRates(network: network);
@ -570,7 +593,7 @@ abstract class ElectrumWalletBase
await electrumClient.connectToUri(node.uri, useSSL: node.useSSL); await electrumClient.connectToUri(node.uri, useSSL: node.useSSL);
} catch (e, stacktrace) { } catch (e, stacktrace) {
print(stacktrace); print(stacktrace);
print(e.toString()); print("connectToNode $e");
syncStatus = FailedSyncStatus(); syncStatus = FailedSyncStatus();
} }
} }
@ -584,6 +607,7 @@ abstract class ElectrumWalletBase
required int credentialsAmount, required int credentialsAmount,
required bool paysToSilentPayment, required bool paysToSilentPayment,
int? inputsCount, int? inputsCount,
UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any,
}) { }) {
List<UtxoWithAddress> utxos = []; List<UtxoWithAddress> utxos = [];
List<Outpoint> vinOutpoints = []; List<Outpoint> vinOutpoints = [];
@ -594,7 +618,20 @@ abstract class ElectrumWalletBase
bool spendsUnconfirmedTX = false; bool spendsUnconfirmedTX = false;
int leftAmount = credentialsAmount; int leftAmount = credentialsAmount;
final availableInputs = unspentCoins.where((utx) => utx.isSending && !utx.isFrozen).toList(); final availableInputs = unspentCoins.where((utx) {
if (!utx.isSending || utx.isFrozen) {
return false;
}
switch (coinTypeToSpendFrom) {
case UnspentCoinType.mweb:
return utx.bitcoinAddressRecord.type == SegwitAddresType.mweb;
case UnspentCoinType.nonMweb:
return utx.bitcoinAddressRecord.type != SegwitAddresType.mweb;
case UnspentCoinType.any:
return true;
}
}).toList();
final unconfirmedCoins = availableInputs.where((utx) => utx.confirmations == 0).toList(); final unconfirmedCoins = availableInputs.where((utx) => utx.confirmations == 0).toList();
for (int i = 0; i < availableInputs.length; i++) { for (int i = 0; i < availableInputs.length; i++) {
@ -615,8 +652,9 @@ abstract class ElectrumWalletBase
ECPrivate? privkey; ECPrivate? privkey;
bool? isSilentPayment = false; bool? isSilentPayment = false;
final hd = final hd = utx.bitcoinAddressRecord.isHidden
utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd; ? walletAddresses.sideHd
: walletAddresses.mainHd;
if (utx.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) { if (utx.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) {
final unspentAddress = utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord; final unspentAddress = utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord;
@ -701,11 +739,13 @@ abstract class ElectrumWalletBase
String? memo, String? memo,
int credentialsAmount = 0, int credentialsAmount = 0,
bool hasSilentPayment = false, bool hasSilentPayment = false,
UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any,
}) async { }) async {
final utxoDetails = _createUTXOS( final utxoDetails = _createUTXOS(
sendAll: true, sendAll: true,
credentialsAmount: credentialsAmount, credentialsAmount: credentialsAmount,
paysToSilentPayment: hasSilentPayment, paysToSilentPayment: hasSilentPayment,
coinTypeToSpendFrom: coinTypeToSpendFrom,
); );
int fee = await calcFee( int fee = await calcFee(
@ -767,17 +807,20 @@ abstract class ElectrumWalletBase
Future<EstimatedTxResult> estimateTxForAmount( Future<EstimatedTxResult> estimateTxForAmount(
int credentialsAmount, int credentialsAmount,
List<BitcoinOutput> outputs, List<BitcoinOutput> outputs,
List<BitcoinOutput> updatedOutputs,
int feeRate, { int feeRate, {
int? inputsCount, int? inputsCount,
String? memo, String? memo,
bool? useUnconfirmed, bool? useUnconfirmed,
bool hasSilentPayment = false, bool hasSilentPayment = false,
UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any,
}) async { }) async {
final utxoDetails = _createUTXOS( final utxoDetails = _createUTXOS(
sendAll: false, sendAll: false,
credentialsAmount: credentialsAmount, credentialsAmount: credentialsAmount,
inputsCount: inputsCount, inputsCount: inputsCount,
paysToSilentPayment: hasSilentPayment, paysToSilentPayment: hasSilentPayment,
coinTypeToSpendFrom: coinTypeToSpendFrom,
); );
final spendingAllCoins = utxoDetails.availableInputs.length == utxoDetails.utxos.length; final spendingAllCoins = utxoDetails.availableInputs.length == utxoDetails.utxos.length;
@ -793,10 +836,12 @@ abstract class ElectrumWalletBase
return estimateTxForAmount( return estimateTxForAmount(
credentialsAmount, credentialsAmount,
outputs, outputs,
updatedOutputs,
feeRate, feeRate,
inputsCount: utxoDetails.utxos.length + 1, inputsCount: utxoDetails.utxos.length + 1,
memo: memo, memo: memo,
hasSilentPayment: hasSilentPayment, hasSilentPayment: hasSilentPayment,
coinTypeToSpendFrom: coinTypeToSpendFrom,
); );
} }
@ -804,19 +849,38 @@ abstract class ElectrumWalletBase
} }
final changeAddress = await walletAddresses.getChangeAddress( final changeAddress = await walletAddresses.getChangeAddress(
outputs: outputs, inputs: utxoDetails.availableInputs,
utxoDetails: utxoDetails, outputs: updatedOutputs,
); );
final address = RegexUtils.addressTypeFromStr(changeAddress, network); final address = RegexUtils.addressTypeFromStr(changeAddress.address, network);
updatedOutputs.add(BitcoinOutput(
address: address,
value: BigInt.from(amountLeftForChangeAndFee),
isChange: true,
));
outputs.add(BitcoinOutput( outputs.add(BitcoinOutput(
address: address, address: address,
value: BigInt.from(amountLeftForChangeAndFee), value: BigInt.from(amountLeftForChangeAndFee),
isChange: true, isChange: true,
)); ));
// Get Derivation path for change Address since it is needed in Litecoin and BitcoinCash hardware Wallets
final changeDerivationPath =
"${_hardenedDerivationPath(walletInfo.derivationInfo?.derivationPath ?? "m/0'")}"
"/${changeAddress.isHidden ? "1" : "0"}"
"/${changeAddress.index}";
utxoDetails.publicKeys[address.pubKeyHash()] =
PublicKeyWithDerivationPath('', changeDerivationPath);
// calcFee updates the silent payment outputs to calculate the tx size accounting
// for taproot addresses, but if more inputs are needed to make up for fees,
// the silent payment outputs need to be recalculated for the new inputs
var temp = outputs.map((output) => output).toList();
int fee = await calcFee( int fee = await calcFee(
utxos: utxoDetails.utxos, utxos: utxoDetails.utxos,
outputs: outputs, // Always take only not updated bitcoin outputs here so for every estimation
// the SP outputs are re-generated to the proper taproot addresses
outputs: temp,
network: network, network: network,
memo: memo, memo: memo,
feeRate: feeRate, feeRate: feeRate,
@ -824,18 +888,25 @@ abstract class ElectrumWalletBase
vinOutpoints: utxoDetails.vinOutpoints, vinOutpoints: utxoDetails.vinOutpoints,
); );
updatedOutputs.clear();
updatedOutputs.addAll(temp);
if (fee == 0) { if (fee == 0) {
throw BitcoinTransactionNoFeeException(); throw BitcoinTransactionNoFeeException();
} }
int amount = credentialsAmount; int amount = credentialsAmount;
final lastOutput = outputs.last; final lastOutput = updatedOutputs.last;
final amountLeftForChange = amountLeftForChangeAndFee - fee; final amountLeftForChange = amountLeftForChangeAndFee - fee;
print(amountLeftForChangeAndFee);
if (!_isBelowDust(amountLeftForChange)) { if (!_isBelowDust(amountLeftForChange)) {
// Here, lastOutput already is change, return the amount left without the fee to the user's address. // Here, lastOutput already is change, return the amount left without the fee to the user's address.
updatedOutputs[updatedOutputs.length - 1] = BitcoinOutput(
address: lastOutput.address,
value: BigInt.from(amountLeftForChange),
isSilentPayment: lastOutput.isSilentPayment,
isChange: true,
);
outputs[outputs.length - 1] = BitcoinOutput( outputs[outputs.length - 1] = BitcoinOutput(
address: lastOutput.address, address: lastOutput.address,
value: BigInt.from(amountLeftForChange), value: BigInt.from(amountLeftForChange),
@ -844,6 +915,7 @@ abstract class ElectrumWalletBase
); );
} else { } else {
// If has change that is lower than dust, will end up with tx rejected by network rules, so estimate again without the added change // If has change that is lower than dust, will end up with tx rejected by network rules, so estimate again without the added change
updatedOutputs.removeLast();
outputs.removeLast(); outputs.removeLast();
// Still has inputs to spend before failing // Still has inputs to spend before failing
@ -851,17 +923,21 @@ abstract class ElectrumWalletBase
return estimateTxForAmount( return estimateTxForAmount(
credentialsAmount, credentialsAmount,
outputs, outputs,
updatedOutputs,
feeRate, feeRate,
inputsCount: utxoDetails.utxos.length + 1, inputsCount: utxoDetails.utxos.length + 1,
memo: memo, memo: memo,
hasSilentPayment: hasSilentPayment,
useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins, useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins,
coinTypeToSpendFrom: coinTypeToSpendFrom,
); );
} }
final estimatedSendAll = await estimateSendAllTx( final estimatedSendAll = await estimateSendAllTx(
outputs, updatedOutputs,
feeRate, feeRate,
memo: memo, memo: memo,
coinTypeToSpendFrom: coinTypeToSpendFrom,
); );
if (estimatedSendAll.amount == credentialsAmount) { if (estimatedSendAll.amount == credentialsAmount) {
@ -891,15 +967,18 @@ abstract class ElectrumWalletBase
if (spendingAllCoins) { if (spendingAllCoins) {
throw BitcoinTransactionWrongBalanceException(); throw BitcoinTransactionWrongBalanceException();
} else { } else {
updatedOutputs.removeLast();
outputs.removeLast(); outputs.removeLast();
return estimateTxForAmount( return estimateTxForAmount(
credentialsAmount, credentialsAmount,
outputs, outputs,
updatedOutputs,
feeRate, feeRate,
inputsCount: utxoDetails.utxos.length + 1, inputsCount: utxoDetails.utxos.length + 1,
memo: memo, memo: memo,
useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins, useUnconfirmed: useUnconfirmed ?? spendingAllConfirmedCoins,
hasSilentPayment: hasSilentPayment, hasSilentPayment: hasSilentPayment,
coinTypeToSpendFrom: coinTypeToSpendFrom,
); );
} }
} }
@ -957,6 +1036,7 @@ abstract class ElectrumWalletBase
final hasMultiDestination = transactionCredentials.outputs.length > 1; final hasMultiDestination = transactionCredentials.outputs.length > 1;
final sendAll = !hasMultiDestination && transactionCredentials.outputs.first.sendAll; final sendAll = !hasMultiDestination && transactionCredentials.outputs.first.sendAll;
final memo = transactionCredentials.outputs.first.memo; final memo = transactionCredentials.outputs.first.memo;
final coinTypeToSpendFrom = transactionCredentials.coinTypeToSpendFrom;
int credentialsAmount = 0; int credentialsAmount = 0;
bool hasSilentPayment = false; bool hasSilentPayment = false;
@ -1005,28 +1085,34 @@ abstract class ElectrumWalletBase
: feeRate(transactionCredentials.priority!); : feeRate(transactionCredentials.priority!);
EstimatedTxResult estimatedTx; EstimatedTxResult estimatedTx;
final updatedOutputs =
outputs.map((e) => BitcoinOutput(address: e.address, value: e.value)).toList();
if (sendAll) { if (sendAll) {
estimatedTx = await estimateSendAllTx( estimatedTx = await estimateSendAllTx(
outputs, updatedOutputs,
feeRateInt, feeRateInt,
memo: memo, memo: memo,
credentialsAmount: credentialsAmount, credentialsAmount: credentialsAmount,
hasSilentPayment: hasSilentPayment, hasSilentPayment: hasSilentPayment,
coinTypeToSpendFrom: coinTypeToSpendFrom,
); );
} else { } else {
estimatedTx = await estimateTxForAmount( estimatedTx = await estimateTxForAmount(
credentialsAmount, credentialsAmount,
outputs, outputs,
updatedOutputs,
feeRateInt, feeRateInt,
memo: memo, memo: memo,
hasSilentPayment: hasSilentPayment, hasSilentPayment: hasSilentPayment,
coinTypeToSpendFrom: coinTypeToSpendFrom,
); );
} }
if (walletInfo.isHardwareWallet) { if (walletInfo.isHardwareWallet) {
final transaction = await buildHardwareWalletTransaction( final transaction = await buildHardwareWalletTransaction(
utxos: estimatedTx.utxos, utxos: estimatedTx.utxos,
outputs: outputs, outputs: updatedOutputs,
publicKeys: estimatedTx.publicKeys, publicKeys: estimatedTx.publicKeys,
fee: BigInt.from(estimatedTx.fee), fee: BigInt.from(estimatedTx.fee),
network: network, network: network,
@ -1056,7 +1142,7 @@ abstract class ElectrumWalletBase
if (network is BitcoinCashNetwork) { if (network is BitcoinCashNetwork) {
txb = ForkedTransactionBuilder( txb = ForkedTransactionBuilder(
utxos: estimatedTx.utxos, utxos: estimatedTx.utxos,
outputs: outputs, outputs: updatedOutputs,
fee: BigInt.from(estimatedTx.fee), fee: BigInt.from(estimatedTx.fee),
network: network, network: network,
memo: estimatedTx.memo, memo: estimatedTx.memo,
@ -1066,7 +1152,7 @@ abstract class ElectrumWalletBase
} else { } else {
txb = BitcoinTransactionBuilder( txb = BitcoinTransactionBuilder(
utxos: estimatedTx.utxos, utxos: estimatedTx.utxos,
outputs: outputs, outputs: updatedOutputs,
fee: BigInt.from(estimatedTx.fee), fee: BigInt.from(estimatedTx.fee),
network: network, network: network,
memo: estimatedTx.memo, memo: estimatedTx.memo,
@ -1126,6 +1212,7 @@ abstract class ElectrumWalletBase
hasChange: estimatedTx.hasChange, hasChange: estimatedTx.hasChange,
isSendAll: estimatedTx.isSendAll, isSendAll: estimatedTx.isSendAll,
hasTaprootInputs: hasTaprootInputs, hasTaprootInputs: hasTaprootInputs,
utxos: estimatedTx.utxos,
)..addListener((transaction) async { )..addListener((transaction) async {
transactionHistory.addOne(transaction); transactionHistory.addOne(transaction);
if (estimatedTx.spendsSilentPayment) { if (estimatedTx.spendsSilentPayment) {
@ -1146,6 +1233,9 @@ abstract class ElectrumWalletBase
} }
} }
void setLedgerConnection(ledger.LedgerConnection connection) =>
throw UnimplementedError();
Future<BtcTransaction> buildHardwareWalletTransaction({ Future<BtcTransaction> buildHardwareWalletTransaction({
required List<BitcoinBaseOutput> outputs, required List<BitcoinBaseOutput> outputs,
required BigInt fee, required BigInt fee,
@ -1315,7 +1405,7 @@ abstract class ElectrumWalletBase
}); });
} }
// Set the balance of all non-silent payment addresses to 0 before updating // Set the balance of all non-silent payment and non-mweb addresses to 0 before updating
walletAddresses.allAddresses walletAddresses.allAddresses
.where((element) => element.type != SegwitAddresType.mweb) .where((element) => element.type != SegwitAddresType.mweb)
.forEach((addr) { .forEach((addr) {
@ -1432,7 +1522,7 @@ abstract class ElectrumWalletBase
await unspentCoinsInfo.deleteAll(keys); await unspentCoinsInfo.deleteAll(keys);
} }
} catch (e) { } catch (e) {
print(e.toString()); print("refreshUnspentCoinsInfo $e");
} }
} }
@ -1503,7 +1593,9 @@ abstract class ElectrumWalletBase
final btcAddress = RegexUtils.addressTypeFromStr(addressRecord.address, network); final btcAddress = RegexUtils.addressTypeFromStr(addressRecord.address, network);
final privkey = generateECPrivate( final privkey = generateECPrivate(
hd: addressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd, hd: addressRecord.isHidden
? walletAddresses.sideHd
: walletAddresses.mainHd,
index: addressRecord.index, index: addressRecord.index,
network: network); network: network);
@ -1685,7 +1777,8 @@ abstract class ElectrumWalletBase
if (height != null) { if (height != null) {
if (time == null && height > 0) { if (time == null && height > 0) {
time = (getDateByBitcoinHeight(height).millisecondsSinceEpoch / 1000).round(); time = (getDateByBitcoinHeight(height).millisecondsSinceEpoch / 1000)
.round();
} }
if (confirmations == null) { if (confirmations == null) {
@ -1776,7 +1869,7 @@ abstract class ElectrumWalletBase
return historiesWithDetails; return historiesWithDetails;
} catch (e) { } catch (e) {
print(e.toString()); print("fetchTransactions $e");
return {}; return {};
} }
} }
@ -1850,7 +1943,9 @@ abstract class ElectrumWalletBase
if (height > 0) { if (height > 0) {
storedTx.height = height; storedTx.height = height;
// the tx's block itself is the first confirmation so add 1 // the tx's block itself is the first confirmation so add 1
if (currentHeight != null) storedTx.confirmations = currentHeight - height + 1; if ((currentHeight ?? 0) > 0) {
storedTx.confirmations = currentHeight! - height + 1;
}
storedTx.isPending = storedTx.confirmations == 0; storedTx.isPending = storedTx.confirmations == 0;
} }
@ -1891,9 +1986,13 @@ abstract class ElectrumWalletBase
} }
await getCurrentChainTip(); await getCurrentChainTip();
transactionHistory.transactions.values.forEach((tx) async { transactionHistory.transactions.values.forEach((tx) {
if (tx.unspents != null && tx.unspents!.isNotEmpty && tx.height != null && tx.height! > 0) { if (tx.unspents != null &&
tx.confirmations = await getCurrentChainTip() - tx.height! + 1; tx.unspents!.isNotEmpty &&
tx.height != null &&
tx.height! > 0 &&
(_currentChainTip ?? 0) > 0) {
tx.confirmations = _currentChainTip! - tx.height! + 1;
} }
}); });
@ -1918,9 +2017,17 @@ abstract class ElectrumWalletBase
await Future.wait(unsubscribedScriptHashes.map((address) async { await Future.wait(unsubscribedScriptHashes.map((address) async {
final sh = address.getScriptHash(network); final sh = address.getScriptHash(network);
if (!(_scripthashesUpdateSubject[sh]?.isClosed ?? true)) { if (!(_scripthashesUpdateSubject[sh]?.isClosed ?? true)) {
await _scripthashesUpdateSubject[sh]?.close(); try {
await _scripthashesUpdateSubject[sh]?.close();
} catch (e) {
print("failed to close: $e");
}
}
try {
_scripthashesUpdateSubject[sh] = await electrumClient.scripthashUpdate(sh);
} catch (e) {
print("failed scripthashUpdate: $e");
} }
_scripthashesUpdateSubject[sh] = await electrumClient.scripthashUpdate(sh);
_scripthashesUpdateSubject[sh]?.listen((event) async { _scripthashesUpdateSubject[sh]?.listen((event) async {
try { try {
await updateUnspentsForAddress(address); await updateUnspentsForAddress(address);
@ -2116,6 +2223,7 @@ abstract class ElectrumWalletBase
@action @action
void _onConnectionStatusChange(ConnectionStatus status) { void _onConnectionStatusChange(ConnectionStatus status) {
switch (status) { switch (status) {
case ConnectionStatus.connected: case ConnectionStatus.connected:
if (syncStatus is NotConnectedSyncStatus || if (syncStatus is NotConnectedSyncStatus ||
@ -2127,19 +2235,26 @@ abstract class ElectrumWalletBase
break; break;
case ConnectionStatus.disconnected: case ConnectionStatus.disconnected:
syncStatus = NotConnectedSyncStatus(); if (syncStatus is! NotConnectedSyncStatus) {
syncStatus = NotConnectedSyncStatus();
}
break; break;
case ConnectionStatus.failed: case ConnectionStatus.failed:
syncStatus = LostConnectionSyncStatus(); if (syncStatus is! LostConnectionSyncStatus) {
syncStatus = LostConnectionSyncStatus();
}
break; break;
case ConnectionStatus.connecting: case ConnectionStatus.connecting:
syncStatus = ConnectingSyncStatus(); if (syncStatus is! ConnectingSyncStatus) {
syncStatus = ConnectingSyncStatus();
}
break; break;
default: default:
} }
} }
void _syncStatusReaction(SyncStatus syncStatus) async { void _syncStatusReaction(SyncStatus syncStatus) async {
print("SYNC_STATUS_CHANGE: ${syncStatus}");
if (syncStatus is SyncingSyncStatus) { if (syncStatus is SyncingSyncStatus) {
return; return;
} }

View file

@ -3,7 +3,7 @@ import 'dart:io' show Platform;
import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/electrum_wallet.dart'; import 'package:cw_bitcoin/bitcoin_unspent.dart';
import 'package:cw_core/wallet_addresses.dart'; import 'package:cw_core/wallet_addresses.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
@ -36,6 +36,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
required this.mainHd, required this.mainHd,
required this.sideHd, required this.sideHd,
required this.network, required this.network,
required this.isHardwareWallet,
List<BitcoinAddressRecord>? initialAddresses, List<BitcoinAddressRecord>? initialAddresses,
Map<String, int>? initialRegularAddressIndex, Map<String, int>? initialRegularAddressIndex,
Map<String, int>? initialChangeAddressIndex, Map<String, int>? initialChangeAddressIndex,
@ -44,6 +45,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
List<BitcoinAddressRecord>? initialMwebAddresses, List<BitcoinAddressRecord>? initialMwebAddresses,
Bip32Slip10Secp256k1? masterHd, Bip32Slip10Secp256k1? masterHd,
BitcoinAddressType? initialAddressPageType, BitcoinAddressType? initialAddressPageType,
}) : _addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()), }) : _addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()),
addressesByReceiveType = addressesByReceiveType =
ObservableList<BaseBitcoinAddressRecord>.of((<BitcoinAddressRecord>[]).toSet()), ObservableList<BaseBitcoinAddressRecord>.of((<BitcoinAddressRecord>[]).toSet()),
@ -112,6 +114,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
final BasedUtxoNetwork network; final BasedUtxoNetwork network;
final Bip32Slip10Secp256k1 mainHd; final Bip32Slip10Secp256k1 mainHd;
final Bip32Slip10Secp256k1 sideHd; final Bip32Slip10Secp256k1 sideHd;
final bool isHardwareWallet;
@observable @observable
SilentPaymentOwner? silentAddress; SilentPaymentOwner? silentAddress;
@ -240,15 +243,17 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
await _generateInitialAddresses(type: P2pkhAddressType.p2pkh); await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
} else if (walletInfo.type == WalletType.litecoin) { } else if (walletInfo.type == WalletType.litecoin) {
await _generateInitialAddresses(type: SegwitAddresType.p2wpkh); await _generateInitialAddresses(type: SegwitAddresType.p2wpkh);
if (Platform.isAndroid || Platform.isIOS) { if ((Platform.isAndroid || Platform.isIOS) && !isHardwareWallet) {
await _generateInitialAddresses(type: SegwitAddresType.mweb); await _generateInitialAddresses(type: SegwitAddresType.mweb);
} }
} else if (walletInfo.type == WalletType.bitcoin) { } else if (walletInfo.type == WalletType.bitcoin) {
await _generateInitialAddresses(); await _generateInitialAddresses();
await _generateInitialAddresses(type: P2pkhAddressType.p2pkh); if (!isHardwareWallet) {
await _generateInitialAddresses(type: P2shAddressType.p2wpkhInP2sh); await _generateInitialAddresses(type: P2pkhAddressType.p2pkh);
await _generateInitialAddresses(type: SegwitAddresType.p2tr); await _generateInitialAddresses(type: P2shAddressType.p2wpkhInP2sh);
await _generateInitialAddresses(type: SegwitAddresType.p2wsh); await _generateInitialAddresses(type: SegwitAddresType.p2tr);
await _generateInitialAddresses(type: SegwitAddresType.p2wsh);
}
} }
updateAddressesByMatch(); updateAddressesByMatch();
@ -267,7 +272,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
} }
@action @action
Future<String> getChangeAddress({List<BitcoinOutput>? outputs, UtxoDetails? utxoDetails}) async { Future<BitcoinAddressRecord> getChangeAddress({List<BitcoinUnspent>? inputs, List<BitcoinOutput>? outputs, bool isPegIn = false}) async {
updateChangeAddresses(); updateChangeAddresses();
if (changeAddresses.isEmpty) { if (changeAddresses.isEmpty) {
@ -282,7 +287,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
} }
updateChangeAddresses(); updateChangeAddresses();
final address = changeAddresses[currentChangeAddressIndex].address; final address = changeAddresses[currentChangeAddressIndex];
currentChangeAddressIndex += 1; currentChangeAddressIndex += 1;
return address; return address;
} }
@ -478,7 +483,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
await saveAddressesInBox(); await saveAddressesInBox();
} catch (e) { } catch (e) {
print(e.toString()); print("updateAddresses $e");
} }
} }
@ -670,7 +675,9 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
} }
Bip32Slip10Secp256k1 _getHd(bool isHidden) => isHidden ? sideHd : mainHd; Bip32Slip10Secp256k1 _getHd(bool isHidden) => isHidden ? sideHd : mainHd;
bool _isAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => addr.type == type; bool _isAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => addr.type == type;
bool _isUnusedReceiveAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) => bool _isUnusedReceiveAddressByType(BitcoinAddressRecord addr, BitcoinAddressType type) =>
!addr.isHidden && !addr.isUsed && addr.type == type; !addr.isHidden && !addr.isUsed && addr.type == type;

View file

@ -0,0 +1,46 @@
import 'dart:async';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:cw_bitcoin/utils.dart';
import 'package:cw_core/hardware/hardware_account_data.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
import 'package:ledger_litecoin/ledger_litecoin.dart';
class LitecoinHardwareWalletService {
LitecoinHardwareWalletService(this.ledgerConnection);
final LedgerConnection ledgerConnection;
Future<List<HardwareAccountData>> getAvailableAccounts(
{int index = 0, int limit = 5}) async {
final litecoinLedgerApp = LitecoinLedgerApp(ledgerConnection);
await litecoinLedgerApp.getVersion();
final accounts = <HardwareAccountData>[];
final indexRange = List.generate(limit, (i) => i + index);
final xpubVersion = Bip44Conf.litecoinMainNet.altKeyNetVer;
for (final i in indexRange) {
final derivationPath = "m/84'/2'/$i'";
final xpub = await litecoinLedgerApp.getXPubKey(
accountsDerivationPath: derivationPath,
xPubVersion: int.parse(hex.encode(xpubVersion.public), radix: 16));
final hd = Bip32Slip10Secp256k1.fromExtendedKey(xpub, xpubVersion)
.childKey(Bip32KeyIndex(0));
final address = generateP2WPKHAddress(
hd: hd, index: 0, network: LitecoinNetwork.mainnet);
accounts.add(HardwareAccountData(
address: address,
accountIndex: i,
derivationPath: derivationPath,
xpub: xpub,
));
}
return accounts;
}
}

View file

@ -1,5 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:typed_data';
import 'package:convert/convert.dart' as convert; import 'package:convert/convert.dart' as convert;
import 'dart:math'; import 'dart:math';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
@ -37,6 +39,8 @@ import 'package:cw_core/wallet_keys_file.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:grpc/grpc.dart'; import 'package:grpc/grpc.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
import 'package:ledger_litecoin/ledger_litecoin.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
import 'package:cw_mweb/cw_mweb.dart'; import 'package:cw_mweb/cw_mweb.dart';
@ -50,12 +54,13 @@ class LitecoinWallet = LitecoinWalletBase with _$LitecoinWallet;
abstract class LitecoinWalletBase extends ElectrumWallet with Store { abstract class LitecoinWalletBase extends ElectrumWallet with Store {
LitecoinWalletBase({ LitecoinWalletBase({
required String mnemonic,
required String password, required String password,
required WalletInfo walletInfo, required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo, required Box<UnspentCoinsInfo> unspentCoinsInfo,
required Uint8List seedBytes,
required EncryptionFileUtils encryptionFileUtils, required EncryptionFileUtils encryptionFileUtils,
Uint8List? seedBytes,
String? mnemonic,
String? xpub,
String? passphrase, String? passphrase,
String? addressPageType, String? addressPageType,
List<BitcoinAddressRecord>? initialAddresses, List<BitcoinAddressRecord>? initialAddresses,
@ -68,6 +73,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
}) : super( }) : super(
mnemonic: mnemonic, mnemonic: mnemonic,
password: password, password: password,
xpub: xpub,
walletInfo: walletInfo, walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo, unspentCoinsInfo: unspentCoinsInfo,
network: LitecoinNetwork.mainnet, network: LitecoinNetwork.mainnet,
@ -78,8 +84,14 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
currency: CryptoCurrency.ltc, currency: CryptoCurrency.ltc,
alwaysScan: alwaysScan, alwaysScan: alwaysScan,
) { ) {
mwebHd = Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/1000'") as Bip32Slip10Secp256k1; if (seedBytes != null) {
mwebEnabled = alwaysScan ?? false; mwebHd = Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath(
"m/1000'") as Bip32Slip10Secp256k1;
mwebEnabled = alwaysScan ?? false;
} else {
mwebHd = null;
mwebEnabled = false;
}
walletAddresses = LitecoinWalletAddresses( walletAddresses = LitecoinWalletAddresses(
walletInfo, walletInfo,
initialAddresses: initialAddresses, initialAddresses: initialAddresses,
@ -91,12 +103,43 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
network: network, network: network,
mwebHd: mwebHd, mwebHd: mwebHd,
mwebEnabled: mwebEnabled, mwebEnabled: mwebEnabled,
isHardwareWallet: walletInfo.isHardwareWallet,
); );
autorun((_) { autorun((_) {
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress; this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
}); });
reaction((_) => mwebSyncStatus, (status) async {
if (mwebSyncStatus is FailedSyncStatus) {
// we failed to connect to mweb, check if we are connected to the litecoin node:
late int nodeHeight;
try {
nodeHeight = await electrumClient.getCurrentBlockChainTip() ?? 0;
} catch (_) {
nodeHeight = 0;
}
if (nodeHeight == 0) {
// we aren't connected to the litecoin node, so the current electrum_wallet reactions will take care of this case for us
} else {
// we're connected to the litecoin node, but we failed to connect to mweb, try again after a few seconds:
await CwMweb.stop();
await Future.delayed(const Duration(seconds: 5));
startSync();
}
} else if (mwebSyncStatus is SyncingSyncStatus) {
syncStatus = mwebSyncStatus;
} else if (mwebSyncStatus is SyncronizingSyncStatus) {
if (syncStatus is! SyncronizingSyncStatus) {
syncStatus = mwebSyncStatus;
}
} else if (mwebSyncStatus is SyncedSyncStatus) {
if (syncStatus is! SyncedSyncStatus) {
syncStatus = mwebSyncStatus;
}
}
});
} }
late final Bip32Slip10Secp256k1 mwebHd; late final Bip32Slip10Secp256k1? mwebHd;
late final Box<MwebUtxo> mwebUtxosBox; late final Box<MwebUtxo> mwebUtxosBox;
Timer? _syncTimer; Timer? _syncTimer;
Timer? _feeRatesTimer; Timer? _feeRatesTimer;
@ -105,8 +148,11 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
late bool mwebEnabled; late bool mwebEnabled;
bool processingUtxos = false; bool processingUtxos = false;
List<int> get scanSecret => mwebHd.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw; @observable
List<int> get spendSecret => mwebHd.childKey(Bip32KeyIndex(0x80000001)).privateKey.privKey.raw; SyncStatus mwebSyncStatus = NotConnectedSyncStatus();
List<int> get scanSecret => mwebHd!.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw;
List<int> get spendSecret => mwebHd!.childKey(Bip32KeyIndex(0x80000001)).privateKey.privKey.raw;
static Future<LitecoinWallet> create( static Future<LitecoinWallet> create(
{required String mnemonic, {required String mnemonic,
@ -216,14 +262,15 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
} }
return LitecoinWallet( return LitecoinWallet(
mnemonic: keysData.mnemonic!, mnemonic: keysData.mnemonic,
xpub: keysData.xPub,
password: password, password: password,
walletInfo: walletInfo, walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo, unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: snp?.addresses, initialAddresses: snp?.addresses,
initialMwebAddresses: snp?.mwebAddresses, initialMwebAddresses: snp?.mwebAddresses,
initialBalance: snp?.balance, initialBalance: snp?.balance,
seedBytes: seedBytes!, seedBytes: seedBytes,
passphrase: passphrase, passphrase: passphrase,
encryptionFileUtils: encryptionFileUtils, encryptionFileUtils: encryptionFileUtils,
initialRegularAddressIndex: snp?.regularAddressIndex, initialRegularAddressIndex: snp?.regularAddressIndex,
@ -244,58 +291,60 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
@override @override
Future<void> startSync() async { Future<void> startSync() async {
print("startSync() called!"); print("startSync() called!");
if (syncStatus is SyncronizingSyncStatus) { print("STARTING SYNC - MWEB ENABLED: $mwebEnabled");
if (!mwebEnabled) {
try {
// in case we're switching from a litecoin wallet that had mweb enabled
CwMweb.stop();
} catch (_) {}
super.startSync();
return; return;
} }
if (mwebSyncStatus is SyncronizingSyncStatus) {
return;
}
print("STARTING SYNC - MWEB ENABLED: $mwebEnabled"); print("STARTING SYNC - MWEB ENABLED: $mwebEnabled");
_syncTimer?.cancel(); _syncTimer?.cancel();
try { try {
syncStatus = SyncronizingSyncStatus(); mwebSyncStatus = SyncronizingSyncStatus();
await subscribeForUpdates(); try {
await subscribeForUpdates();
} catch (e) {
print("failed to subcribe for updates: $e");
}
updateFeeRates(); updateFeeRates();
_feeRatesTimer?.cancel(); _feeRatesTimer?.cancel();
_feeRatesTimer = _feeRatesTimer =
Timer.periodic(const Duration(minutes: 1), (timer) async => await updateFeeRates()); Timer.periodic(const Duration(minutes: 1), (timer) async => await updateFeeRates());
if (!mwebEnabled) { print("START SYNC FUNCS");
try {
// in case we're switching from a litecoin wallet that had mweb enabled
CwMweb.stop();
} catch (_) {}
try {
await updateAllUnspents();
await updateTransactions();
await updateBalance();
syncStatus = SyncedSyncStatus();
} catch (e, s) {
print(e);
print(s);
syncStatus = FailedSyncStatus();
}
return;
}
await waitForMwebAddresses(); await waitForMwebAddresses();
await processMwebUtxos(); await processMwebUtxos();
await updateTransactions(); await updateTransactions();
await updateUnspent(); await updateUnspent();
await updateBalance(); await updateBalance();
} catch (e) { print("DONE SYNC FUNCS");
print("failed to start mweb sync: $e"); } catch (e, s) {
syncStatus = FailedSyncStatus(error: "failed to start"); print("mweb sync failed: $e $s");
mwebSyncStatus = FailedSyncStatus(error: "mweb sync failed: $e");
return; return;
} }
_syncTimer = Timer.periodic(const Duration(milliseconds: 3000), (timer) async { _syncTimer = Timer.periodic(const Duration(milliseconds: 3000), (timer) async {
if (syncStatus is FailedSyncStatus) return; if (mwebSyncStatus is FailedSyncStatus) {
_syncTimer?.cancel();
return;
}
final nodeHeight = final nodeHeight =
await electrumClient.getCurrentBlockChainTip() ?? 0; // current block height of our node await electrumClient.getCurrentBlockChainTip() ?? 0; // current block height of our node
if (nodeHeight == 0) { if (nodeHeight == 0) {
// we aren't connected to the ltc node yet // we aren't connected to the ltc node yet
if (syncStatus is! NotConnectedSyncStatus) { if (mwebSyncStatus is! NotConnectedSyncStatus) {
syncStatus = FailedSyncStatus(error: "Failed to connect to Litecoin node"); mwebSyncStatus = FailedSyncStatus(error: "litecoin node isn't connected");
} }
return; return;
} }
@ -305,12 +354,12 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
try { try {
if (resp.blockHeaderHeight < nodeHeight) { if (resp.blockHeaderHeight < nodeHeight) {
int h = resp.blockHeaderHeight; int h = resp.blockHeaderHeight;
syncStatus = SyncingSyncStatus(nodeHeight - h, h / nodeHeight); mwebSyncStatus = SyncingSyncStatus(nodeHeight - h, h / nodeHeight);
} else if (resp.mwebHeaderHeight < nodeHeight) { } else if (resp.mwebHeaderHeight < nodeHeight) {
int h = resp.mwebHeaderHeight; int h = resp.mwebHeaderHeight;
syncStatus = SyncingSyncStatus(nodeHeight - h, h / nodeHeight); mwebSyncStatus = SyncingSyncStatus(nodeHeight - h, h / nodeHeight);
} else if (resp.mwebUtxosHeight < nodeHeight) { } else if (resp.mwebUtxosHeight < nodeHeight) {
syncStatus = SyncingSyncStatus(1, 0.999); mwebSyncStatus = SyncingSyncStatus(1, 0.999);
} else { } else {
if (resp.mwebUtxosHeight > walletInfo.restoreHeight) { if (resp.mwebUtxosHeight > walletInfo.restoreHeight) {
await walletInfo.updateRestoreHeight(resp.mwebUtxosHeight); await walletInfo.updateRestoreHeight(resp.mwebUtxosHeight);
@ -321,6 +370,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
int txHeight = transaction.height ?? resp.mwebUtxosHeight; int txHeight = transaction.height ?? resp.mwebUtxosHeight;
final confirmations = (resp.mwebUtxosHeight - txHeight) + 1; final confirmations = (resp.mwebUtxosHeight - txHeight) + 1;
if (transaction.confirmations == confirmations) continue; if (transaction.confirmations == confirmations) continue;
if (transaction.confirmations == 0) {
updateBalance();
}
transaction.confirmations = confirmations; transaction.confirmations = confirmations;
transactionHistory.addOne(transaction); transactionHistory.addOne(transaction);
} }
@ -328,17 +380,17 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
} }
// prevent unnecessary reaction triggers: // prevent unnecessary reaction triggers:
if (syncStatus is! SyncedSyncStatus) { if (mwebSyncStatus is! SyncedSyncStatus) {
// mwebd is synced, but we could still be processing incoming utxos: // mwebd is synced, but we could still be processing incoming utxos:
if (!processingUtxos) { if (!processingUtxos) {
syncStatus = SyncedSyncStatus(); mwebSyncStatus = SyncedSyncStatus();
} }
} }
return; return;
} }
} catch (e) { } catch (e) {
print("error syncing: $e"); print("error syncing: $e");
syncStatus = FailedSyncStatus(error: e.toString()); mwebSyncStatus = FailedSyncStatus(error: e.toString());
} }
}); });
} }
@ -386,7 +438,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
bool? usingElectrs, bool? usingElectrs,
}) async { }) async {
_syncTimer?.cancel(); _syncTimer?.cancel();
int oldHeight = walletInfo.restoreHeight;
await walletInfo.updateRestoreHeight(height); await walletInfo.updateRestoreHeight(height);
// go through mwebUtxos and clear any that are above the new restore height: // go through mwebUtxos and clear any that are above the new restore height:
@ -508,8 +559,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
} }
_utxoStream = responseStream.listen((Utxo sUtxo) async { _utxoStream = responseStream.listen((Utxo sUtxo) async {
// we're processing utxos, so our balance could still be innacurate: // we're processing utxos, so our balance could still be innacurate:
if (syncStatus is! SyncronizingSyncStatus && syncStatus is! SyncingSyncStatus) { if (mwebSyncStatus is! SyncronizingSyncStatus && mwebSyncStatus is! SyncingSyncStatus) {
syncStatus = SyncronizingSyncStatus(); mwebSyncStatus = SyncronizingSyncStatus();
processingUtxos = true; processingUtxos = true;
_processingTimer?.cancel(); _processingTimer?.cancel();
_processingTimer = Timer.periodic(const Duration(seconds: 2), (timer) async { _processingTimer = Timer.periodic(const Duration(seconds: 2), (timer) async {
@ -526,10 +577,18 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
value: sUtxo.value.toInt(), value: sUtxo.value.toInt(),
); );
// if (mwebUtxosBox.containsKey(utxo.outputId)) { if (mwebUtxosBox.containsKey(utxo.outputId)) {
// // we've already stored this utxo, skip it: // we've already stored this utxo, skip it:
// return; // but do update the utxo height if it's somehow different:
// } final existingUtxo = mwebUtxosBox.get(utxo.outputId);
if (existingUtxo!.height != utxo.height) {
print(
"updating utxo height for $utxo.outputId: ${existingUtxo.height} -> ${utxo.height}");
existingUtxo.height = utxo.height;
await mwebUtxosBox.put(utxo.outputId, existingUtxo);
}
return;
}
await updateUnspent(); await updateUnspent();
await updateBalance(); await updateBalance();
@ -575,7 +634,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
final height = await electrumClient.getCurrentBlockChainTip(); final height = await electrumClient.getCurrentBlockChainTip();
if (height == null || status.blockHeaderHeight != height) return; if (height == null || status.blockHeaderHeight != height) return;
if (status.mwebUtxosHeight != height) return; // we aren't synced if (status.mwebUtxosHeight != height) return; // we aren't synced
int amount = 0; int amount = 0;
Set<String> inputAddresses = {}; Set<String> inputAddresses = {};
var output = convert.AccumulatorSink<Digest>(); var output = convert.AccumulatorSink<Digest>();
@ -669,10 +727,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
@override @override
@action @action
Future<void> updateAllUnspents() async { Future<void> updateAllUnspents() async {
// get ltc unspents:
await super.updateAllUnspents();
if (!mwebEnabled) { if (!mwebEnabled) {
await super.updateAllUnspents();
return; return;
} }
@ -708,6 +764,12 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
} }
mwebUnspentCoins.add(unspent); mwebUnspentCoins.add(unspent);
}); });
// copy coin control attributes to mwebCoins:
await updateCoins(mwebUnspentCoins);
// get regular ltc unspents (this resets unspentCoins):
await super.updateAllUnspents();
// add the mwebCoins:
unspentCoins.addAll(mwebUnspentCoins); unspentCoins.addAll(mwebUnspentCoins);
} }
@ -886,6 +948,10 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
tx.isMweb = mwebEnabled; tx.isMweb = mwebEnabled;
if (!mwebEnabled) { if (!mwebEnabled) {
tx.changeAddressOverride =
(await (walletAddresses as LitecoinWalletAddresses)
.getChangeAddress(isPegIn: false))
.address;
return tx; return tx;
} }
await waitForMwebAddresses(); await waitForMwebAddresses();
@ -909,13 +975,27 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
hasMwebOutput = true; hasMwebOutput = true;
break; break;
} }
if (output.address.toLowerCase().contains("mweb")) {
hasMwebOutput = true;
break;
}
} }
if (tx2.mwebBytes != null && tx2.mwebBytes!.isNotEmpty) { // check if mweb inputs are used:
hasMwebInput = true; for (final utxo in tx.utxos) {
if (utxo.utxo.scriptType == SegwitAddresType.mweb) {
hasMwebInput = true;
}
} }
bool isPegIn = !hasMwebInput && hasMwebOutput;
bool isRegular = !hasMwebInput && !hasMwebOutput;
tx.changeAddressOverride =
(await (walletAddresses as LitecoinWalletAddresses)
.getChangeAddress(isPegIn: isPegIn || isRegular))
.address;
if (!hasMwebInput && !hasMwebOutput) { if (!hasMwebInput && !hasMwebOutput) {
tx.isMweb = false;
return tx; return tx;
} }
@ -966,7 +1046,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
final addresses = <String>{}; final addresses = <String>{};
transaction.inputAddresses?.forEach((id) async { transaction.inputAddresses?.forEach((id) async {
final utxo = mwebUtxosBox.get(id); final utxo = mwebUtxosBox.get(id);
// await mwebUtxosBox.delete(id);// gets deleted in checkMwebUtxosSpent await mwebUtxosBox.delete(id); // gets deleted in checkMwebUtxosSpent
if (utxo == null) return; if (utxo == null) return;
final addressRecord = walletAddresses.allAddresses final addressRecord = walletAddresses.allAddresses
.firstWhere((addressRecord) => addressRecord.address == utxo.address); .firstWhere((addressRecord) => addressRecord.address == utxo.address);
@ -985,6 +1065,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
print(e); print(e);
print(s); print(s);
if (e.toString().contains("commit failed")) { if (e.toString().contains("commit failed")) {
print(e);
throw Exception("Transaction commit failed (no peers responded), please try again."); throw Exception("Transaction commit failed (no peers responded), please try again.");
} }
rethrow; rethrow;
@ -1152,4 +1233,64 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
return false; return false;
} }
LedgerConnection? _ledgerConnection;
LitecoinLedgerApp? _litecoinLedgerApp;
@override
void setLedgerConnection(LedgerConnection connection) {
_ledgerConnection = connection;
_litecoinLedgerApp =
LitecoinLedgerApp(_ledgerConnection!, derivationPath: walletInfo.derivationInfo!.derivationPath!);
}
@override
Future<BtcTransaction> buildHardwareWalletTransaction({
required List<BitcoinBaseOutput> outputs,
required BigInt fee,
required BasedUtxoNetwork network,
required List<UtxoWithAddress> utxos,
required Map<String, PublicKeyWithDerivationPath> publicKeys,
String? memo,
bool enableRBF = false,
BitcoinOrdering inputOrdering = BitcoinOrdering.bip69,
BitcoinOrdering outputOrdering = BitcoinOrdering.bip69,
}) async {
final readyInputs = <LedgerTransaction>[];
for (final utxo in utxos) {
final rawTx = await electrumClient.getTransactionHex(hash: utxo.utxo.txHash);
final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!;
readyInputs.add(LedgerTransaction(
rawTx: rawTx,
outputIndex: utxo.utxo.vout,
ownerPublicKey: Uint8List.fromList(hex.decode(publicKeyAndDerivationPath.publicKey)),
ownerDerivationPath: publicKeyAndDerivationPath.derivationPath,
// sequence: enableRBF ? 0x1 : 0xffffffff,
sequence: 0xffffffff,
));
}
String? changePath;
for (final output in outputs) {
final maybeChangePath = publicKeys[(output as BitcoinOutput).address.pubKeyHash()];
if (maybeChangePath != null) changePath ??= maybeChangePath.derivationPath;
}
final rawHex = await _litecoinLedgerApp!.createTransaction(
inputs: readyInputs,
outputs: outputs
.map((e) => TransactionOutput.fromBigInt(
(e as BitcoinOutput).value, Uint8List.fromList(e.address.toScriptPubKey().toBytes())))
.toList(),
changePath: changePath,
sigHashType: 0x01,
additionals: ["bech32"],
isSegWit: true,
useTrustedInputForSegwit: true
);
return BtcTransaction.fromRaw(rawHex);
}
} }

View file

@ -5,6 +5,7 @@ import 'dart:typed_data';
import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/bitcoin_unspent.dart';
import 'package:cw_bitcoin/electrum_wallet.dart'; import 'package:cw_bitcoin/electrum_wallet.dart';
import 'package:cw_bitcoin/utils.dart'; import 'package:cw_bitcoin/utils.dart';
import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; import 'package:cw_bitcoin/electrum_wallet_addresses.dart';
@ -15,14 +16,17 @@ import 'package:mobx/mobx.dart';
part 'litecoin_wallet_addresses.g.dart'; part 'litecoin_wallet_addresses.g.dart';
class LitecoinWalletAddresses = LitecoinWalletAddressesBase with _$LitecoinWalletAddresses; class LitecoinWalletAddresses = LitecoinWalletAddressesBase
with _$LitecoinWalletAddresses;
abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with Store { abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses
with Store {
LitecoinWalletAddressesBase( LitecoinWalletAddressesBase(
WalletInfo walletInfo, { WalletInfo walletInfo, {
required super.mainHd, required super.mainHd,
required super.sideHd, required super.sideHd,
required super.network, required super.network,
required super.isHardwareWallet,
required this.mwebHd, required this.mwebHd,
required this.mwebEnabled, required this.mwebEnabled,
super.initialAddresses, super.initialAddresses,
@ -36,20 +40,20 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
print("initialized with ${mwebAddrs.length} mweb addresses"); print("initialized with ${mwebAddrs.length} mweb addresses");
} }
final Bip32Slip10Secp256k1 mwebHd; final Bip32Slip10Secp256k1? mwebHd;
bool mwebEnabled; bool mwebEnabled;
int mwebTopUpIndex = 1000; int mwebTopUpIndex = 1000;
List<String> mwebAddrs = []; List<String> mwebAddrs = [];
bool generating = false; bool generating = false;
List<int> get scanSecret => mwebHd.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw; List<int> get scanSecret =>
mwebHd!.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw;
List<int> get spendPubkey => List<int> get spendPubkey =>
mwebHd.childKey(Bip32KeyIndex(0x80000001)).publicKey.pubKey.compressed; mwebHd!.childKey(Bip32KeyIndex(0x80000001)).publicKey.pubKey.compressed;
@override @override
Future<void> init() async { Future<void> init() async {
await initMwebAddresses(); if (!isHardwareWallet) await initMwebAddresses();
await super.init(); await super.init();
} }
@ -93,6 +97,9 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
print("Done generating MWEB addresses len: ${mwebAddrs.length}"); print("Done generating MWEB addresses len: ${mwebAddrs.length}");
// ensure mweb addresses are up to date: // ensure mweb addresses are up to date:
// This is the Case if the Litecoin Wallet is a hardware Wallet
if (mwebHd == null) return;
if (mwebAddresses.length < mwebAddrs.length) { if (mwebAddresses.length < mwebAddrs.length) {
List<BitcoinAddressRecord> addressRecords = mwebAddrs List<BitcoinAddressRecord> addressRecords = mwebAddrs
.asMap() .asMap()
@ -142,14 +149,15 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
@action @action
@override @override
Future<String> getChangeAddress({List<BitcoinOutput>? outputs, UtxoDetails? utxoDetails}) async { Future<BitcoinAddressRecord> getChangeAddress(
{List<BitcoinUnspent>? inputs, List<BitcoinOutput>? outputs, bool isPegIn = false}) async {
// use regular change address on peg in, otherwise use mweb for change address: // use regular change address on peg in, otherwise use mweb for change address:
if (!mwebEnabled) { if (!mwebEnabled || isPegIn) {
return super.getChangeAddress(); return super.getChangeAddress();
} }
if (outputs != null && utxoDetails != null) { if (inputs != null && outputs != null) {
// check if this is a PEGIN: // check if this is a PEGIN:
bool outputsToMweb = false; bool outputsToMweb = false;
bool comesFromMweb = false; bool comesFromMweb = false;
@ -161,14 +169,18 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
outputsToMweb = true; outputsToMweb = true;
} }
} }
// TODO: this doesn't respect coin control because it doesn't know which available inputs are selected
utxoDetails.availableInputs.forEach((element) { inputs.forEach((element) {
if (!element.isSending || element.isFrozen) {
return;
}
if (element.address.contains("mweb")) { if (element.address.contains("mweb")) {
comesFromMweb = true; comesFromMweb = true;
} }
}); });
bool isPegIn = !comesFromMweb && outputsToMweb; bool isPegIn = !comesFromMweb && outputsToMweb;
if (isPegIn && mwebEnabled) { if (isPegIn && mwebEnabled) {
return super.getChangeAddress(); return super.getChangeAddress();
} }
@ -181,7 +193,12 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
if (mwebEnabled) { if (mwebEnabled) {
await ensureMwebAddressUpToIndexExists(1); await ensureMwebAddressUpToIndexExists(1);
return mwebAddrs[0]; return BitcoinAddressRecord(
mwebAddrs[0],
index: 0,
type: SegwitAddresType.mweb,
network: network,
);
} }
return super.getChangeAddress(); return super.getChangeAddress();

View file

@ -1,4 +1,5 @@
import 'dart:io'; import 'dart:io';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_mnemonics_bip39.dart'; import 'package:cw_bitcoin/bitcoin_mnemonics_bip39.dart';
import 'package:cw_bitcoin/mnemonic_is_incorrect_exception.dart'; import 'package:cw_bitcoin/mnemonic_is_incorrect_exception.dart';
import 'package:cw_core/encryption_file_utils.dart'; import 'package:cw_core/encryption_file_utils.dart';
@ -20,7 +21,7 @@ class LitecoinWalletService extends WalletService<
BitcoinNewWalletCredentials, BitcoinNewWalletCredentials,
BitcoinRestoreWalletFromSeedCredentials, BitcoinRestoreWalletFromSeedCredentials,
BitcoinRestoreWalletFromWIFCredentials, BitcoinRestoreWalletFromWIFCredentials,
BitcoinNewWalletCredentials> { BitcoinRestoreWalletFromHardware> {
LitecoinWalletService( LitecoinWalletService(
this.walletInfoSource, this.unspentCoinsInfoSource, this.alwaysScan, this.isDirect); this.walletInfoSource, this.unspentCoinsInfoSource, this.alwaysScan, this.isDirect);
@ -147,9 +148,23 @@ class LitecoinWalletService extends WalletService<
} }
@override @override
Future<LitecoinWallet> restoreFromHardwareWallet(BitcoinNewWalletCredentials credentials) { Future<LitecoinWallet> restoreFromHardwareWallet(BitcoinRestoreWalletFromHardware credentials,
throw UnimplementedError( {bool? isTestnet}) async {
"Restoring a Litecoin wallet from a hardware wallet is not yet supported!"); final network = isTestnet == true ? LitecoinNetwork.testnet : LitecoinNetwork.mainnet;
credentials.walletInfo?.network = network.value;
credentials.walletInfo?.derivationInfo?.derivationPath =
credentials.hwAccountData.derivationPath;
final wallet = await LitecoinWallet(
password: credentials.password!,
xpub: credentials.hwAccountData.xpub,
walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource,
encryptionFileUtils: encryptionFileUtilsFor(isDirect),
);
await wallet.save();
await wallet.init();
return wallet;
} }
@override @override

View file

@ -24,6 +24,7 @@ class PendingBitcoinTransaction with PendingTransaction {
this.isSendAll = false, this.isSendAll = false,
this.hasTaprootInputs = false, this.hasTaprootInputs = false,
this.isMweb = false, this.isMweb = false,
this.utxos = const [],
}) : _listeners = <void Function(ElectrumTransactionInfo transaction)>[]; }) : _listeners = <void Function(ElectrumTransactionInfo transaction)>[];
final WalletType type; final WalletType type;
@ -36,7 +37,9 @@ class PendingBitcoinTransaction with PendingTransaction {
final bool isSendAll; final bool isSendAll;
final bool hasChange; final bool hasChange;
final bool hasTaprootInputs; final bool hasTaprootInputs;
List<UtxoWithAddress> utxos;
bool isMweb; bool isMweb;
String? changeAddressOverride;
String? idOverride; String? idOverride;
String? hexOverride; String? hexOverride;
List<String>? outputAddresses; List<String>? outputAddresses;
@ -63,6 +66,9 @@ class PendingBitcoinTransaction with PendingTransaction {
PendingChange? get change { PendingChange? get change {
try { try {
final change = _tx.outputs.firstWhere((out) => out.isChange); final change = _tx.outputs.firstWhere((out) => out.isChange);
if (changeAddressOverride != null) {
return PendingChange(changeAddressOverride!, BtcUtils.fromSatoshi(change.amount));
}
return PendingChange(change.scriptPubKey.toAddress(), BtcUtils.fromSatoshi(change.amount)); return PendingChange(change.scriptPubKey.toAddress(), BtcUtils.fromSatoshi(change.amount));
} catch (_) { } catch (_) {
return null; return null;

View file

@ -16,10 +16,6 @@ class PSBTTransactionBuild {
for (var i = 0; i < inputs.length; i++) { for (var i = 0; i < inputs.length; i++) {
final input = inputs[i]; final input = inputs[i];
print(input.utxo.isP2tr());
print(input.utxo.isSegwit());
print(input.utxo.isP2shSegwit());
psbt.setInputPreviousTxId(i, Uint8List.fromList(hex.decode(input.utxo.txHash).reversed.toList())); psbt.setInputPreviousTxId(i, Uint8List.fromList(hex.decode(input.utxo.txHash).reversed.toList()));
psbt.setInputOutputIndex(i, input.utxo.vout); psbt.setInputOutputIndex(i, input.utxo.vout);
psbt.setInputSequence(i, enableRBF ? 0x1 : 0xffffffff); psbt.setInputSequence(i, enableRBF ? 0x1 : 0xffffffff);

View file

@ -21,10 +21,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: archive name: archive
sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d" sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.4.10" version: "3.6.1"
args: args:
dependency: transitive dependency: transitive
description: description:
@ -101,6 +101,14 @@ packages:
url: "https://github.com/cake-tech/blockchain_utils" url: "https://github.com/cake-tech/blockchain_utils"
source: git source: git
version: "3.3.0" version: "3.3.0"
bluez:
dependency: transitive
description:
name: bluez
sha256: "203a1924e818a9dd74af2b2c7a8f375ab8e5edf0e486bba8f90a0d8a17ed9fce"
url: "https://pub.dev"
source: hosted
version: "0.8.2"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -300,6 +308,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.3" version: "1.0.3"
dbus:
dependency: transitive
description:
name: dbus
sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac"
url: "https://pub.dev"
source: hosted
version: "0.7.10"
encrypt: encrypt:
dependency: transitive dependency: transitive
description: description:
@ -336,10 +352,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: file name: file
sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.0.0" version: "7.0.1"
fixnum: fixnum:
dependency: transitive dependency: transitive
description: description:
@ -361,19 +377,19 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.1+1" version: "2.2.1+1"
flutter_reactive_ble:
dependency: transitive
description:
name: flutter_reactive_ble
sha256: "247e2efa76de203d1ba11335c13754b5b9d0504b5423e5b0c93a600f016b24e0"
url: "https://pub.dev"
source: hosted
version: "5.3.1"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_web_bluetooth:
dependency: transitive
description:
name: flutter_web_bluetooth
sha256: "52ce64f65d7321c4bf6abfe9dac02fb888731339a5e0ad6de59fb916c20c9f02"
url: "https://pub.dev"
source: hosted
version: "0.2.3"
flutter_web_plugins: flutter_web_plugins:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -387,14 +403,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.0" version: "4.0.0"
functional_data:
dependency: transitive
description:
name: functional_data
sha256: "76d17dc707c40e552014f5a49c0afcc3f1e3f05e800cd6b7872940bfe41a5039"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
glob: glob:
dependency: transitive dependency: transitive
description: description:
@ -403,14 +411,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.2" version: "2.1.2"
google_identity_services_web:
dependency: transitive
description:
name: google_identity_services_web
sha256: "5be191523702ba8d7a01ca97c17fca096822ccf246b0a9f11923a6ded06199b6"
url: "https://pub.dev"
source: hosted
version: "0.3.1+4"
googleapis_auth: googleapis_auth:
dependency: transitive dependency: transitive
description: description:
name: googleapis_auth name: googleapis_auth
sha256: af7c3a3edf9d0de2e1e0a77e994fae0a581c525fa7012af4fa0d4a52ed9484da sha256: befd71383a955535060acde8792e7efc11d2fccd03dd1d3ec434e85b68775938
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.1" version: "1.6.0"
graphs: graphs:
dependency: transitive dependency: transitive
description: description:
@ -542,29 +558,37 @@ packages:
ledger_bitcoin: ledger_bitcoin:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." path: "packages/ledger-bitcoin"
ref: HEAD ref: HEAD
resolved-ref: f819d37e235e239c315e93856abbf5e5d3b71dab resolved-ref: dbb5c4956949dc734af3fc8febdbabed89da72aa
url: "https://github.com/cake-tech/ledger-bitcoin" url: "https://github.com/cake-tech/ledger-flutter-plus-plugins"
source: git source: git
version: "0.0.2" version: "0.0.3"
ledger_flutter: ledger_flutter_plus:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." name: ledger_flutter_plus
ref: cake-v3 sha256: ea3ed586e1697776dacf42ac979095f1ca3bd143bf007cbe5c78e09cb6943f42
resolved-ref: "66469ff9dffe2417c70ae7287c9d76d2fe7157a4"
url: "https://github.com/cake-tech/ledger-flutter.git"
source: git
version: "1.0.2"
ledger_usb:
dependency: transitive
description:
name: ledger_usb
sha256: "52c92d03a4cffe06c82921c8e2f79f3cdad6e1cf78e1e9ca35444196ff8f14c2"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.0" version: "1.2.5"
ledger_litecoin:
dependency: "direct main"
description:
path: "packages/ledger-litecoin"
ref: HEAD
resolved-ref: dbb5c4956949dc734af3fc8febdbabed89da72aa
url: "https://github.com/cake-tech/ledger-flutter-plus-plugins"
source: git
version: "0.0.2"
ledger_usb_plus:
dependency: transitive
description:
name: ledger_usb_plus
sha256: "21cc5d976cf7edb3518bd2a0c4164139cbb0817d2e4f2054707fc4edfdf9ce87"
url: "https://pub.dev"
source: hosted
version: "1.0.4"
logging: logging:
dependency: transitive dependency: transitive
description: description:
@ -693,6 +717,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" version: "2.3.0"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
url: "https://pub.dev"
source: hosted
version: "6.0.2"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@ -765,30 +797,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.2.2" version: "3.2.2"
reactive_ble_mobile:
dependency: transitive
description:
name: reactive_ble_mobile
sha256: "9ec2b4c9c725e439950838d551579750060258fbccd5536d0543b4d07d225798"
url: "https://pub.dev"
source: hosted
version: "5.3.1"
reactive_ble_platform_interface:
dependency: transitive
description:
name: reactive_ble_platform_interface
sha256: "632c92401a2d69c9b94bd48f8fd47488a7013f3d1f9b291884350291a4a81813"
url: "https://pub.dev"
source: hosted
version: "5.3.1"
rxdart: rxdart:
dependency: "direct main" dependency: "direct main"
description: description:
name: rxdart name: rxdart
sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.27.7" version: "0.28.0"
shared_preferences: shared_preferences:
dependency: "direct main" dependency: "direct main"
description: description:
@ -809,10 +825,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_foundation name: shared_preferences_foundation
sha256: c4b35f6cb8f63c147312c054ce7c2254c8066745125264f0c88739c417fc9d9f sha256: "07e050c7cd39bad516f8d64c455f04508d09df104be326d8c02551590a0d513d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.2" version: "2.5.3"
shared_preferences_linux: shared_preferences_linux:
dependency: transitive dependency: transitive
description: description:
@ -979,6 +995,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.2" version: "1.3.2"
universal_ble:
dependency: transitive
description:
name: universal_ble
sha256: "0dfbd6b64bff3ad61ed7a895c232530d9614e9b01ab261a74433a43267edb7f3"
url: "https://pub.dev"
source: hosted
version: "0.12.0"
universal_platform:
dependency: transitive
description:
name: universal_platform
sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
unorm_dart: unorm_dart:
dependency: transitive dependency: transitive
description: description:
@ -1031,10 +1063,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: xdg_directories name: xdg_directories
sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.4" version: "1.1.0"
xml:
dependency: transitive
description:
name: xml
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
url: "https://pub.dev"
source: hosted
version: "6.5.0"
yaml: yaml:
dependency: transitive dependency: transitive
description: description:

View file

@ -24,16 +24,12 @@ dependencies:
git: git:
url: https://github.com/cake-tech/bitbox-flutter.git url: https://github.com/cake-tech/bitbox-flutter.git
ref: Add-Support-For-OP-Return-data ref: Add-Support-For-OP-Return-data
rxdart: ^0.27.5 rxdart: ^0.28.0
cryptography: ^2.0.5 cryptography: ^2.0.5
blockchain_utils: blockchain_utils:
git: git:
url: https://github.com/cake-tech/blockchain_utils url: https://github.com/cake-tech/blockchain_utils
ref: cake-update-v2 ref: cake-update-v2
ledger_flutter: ^1.0.1
ledger_bitcoin:
git:
url: https://github.com/cake-tech/ledger-bitcoin
cw_mweb: cw_mweb:
path: ../cw_mweb path: ../cw_mweb
grpc: ^3.2.4 grpc: ^3.2.4
@ -44,6 +40,15 @@ dependencies:
bech32: bech32:
git: git:
url: https://github.com/cake-tech/bech32.git url: https://github.com/cake-tech/bech32.git
ledger_flutter_plus: ^1.4.1
ledger_bitcoin:
git:
url: https://github.com/cake-tech/ledger-flutter-plus-plugins
path: packages/ledger-bitcoin
ledger_litecoin:
git:
url: https://github.com/cake-tech/ledger-flutter-plus-plugins
path: packages/ledger-litecoin
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@ -54,10 +59,6 @@ dev_dependencies:
hive_generator: ^1.1.3 hive_generator: ^1.1.3
dependency_overrides: dependency_overrides:
ledger_flutter:
git:
url: https://github.com/cake-tech/ledger-flutter.git
ref: cake-v3
watcher: ^1.1.0 watcher: ^1.1.0
protobuf: ^3.1.0 protobuf: ^3.1.0
bitcoin_base: bitcoin_base:

View file

@ -58,6 +58,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
sideHd: accountHD.childKey(Bip32KeyIndex(1)), sideHd: accountHD.childKey(Bip32KeyIndex(1)),
network: network, network: network,
initialAddressPageType: addressPageType, initialAddressPageType: addressPageType,
isHardwareWallet: walletInfo.isHardwareWallet,
); );
autorun((_) { autorun((_) {
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress; this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;

View file

@ -15,6 +15,7 @@ abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses wi
required super.mainHd, required super.mainHd,
required super.sideHd, required super.sideHd,
required super.network, required super.network,
required super.isHardwareWallet,
super.initialAddresses, super.initialAddresses,
super.initialRegularAddressIndex, super.initialRegularAddressIndex,
super.initialChangeAddressIndex, super.initialChangeAddressIndex,

View file

@ -8,6 +8,7 @@ enum DeviceConnectionType {
[bool isIOS = false]) { [bool isIOS = false]) {
switch (walletType) { switch (walletType) {
case WalletType.bitcoin: case WalletType.bitcoin:
case WalletType.litecoin:
case WalletType.ethereum: case WalletType.ethereum:
case WalletType.polygon: case WalletType.polygon:
if (isIOS) return [DeviceConnectionType.ble]; if (isIOS) return [DeviceConnectionType.ble];

View file

@ -0,0 +1 @@
enum UnspentCoinType { mweb, nonMweb, any }

View file

@ -23,7 +23,7 @@ abstract class WalletAddresses {
return _localAddress ?? address; return _localAddress ?? address;
} }
String? get primaryAddress => null; String get primaryAddress => address;
String? _localAddress; String? _localAddress;

View file

@ -2,26 +2,26 @@ import 'dart:async';
import 'package:cw_core/hardware/hardware_account_data.dart'; import 'package:cw_core/hardware/hardware_account_data.dart';
import 'package:ledger_ethereum/ledger_ethereum.dart'; import 'package:ledger_ethereum/ledger_ethereum.dart';
import 'package:ledger_flutter/ledger_flutter.dart'; import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
class EVMChainHardwareWalletService { class EVMChainHardwareWalletService {
EVMChainHardwareWalletService(this.ledger, this.device); EVMChainHardwareWalletService(this.ledgerConnection);
final Ledger ledger; final LedgerConnection ledgerConnection;
final LedgerDevice device;
Future<List<HardwareAccountData>> getAvailableAccounts({int index = 0, int limit = 5}) async { Future<List<HardwareAccountData>> getAvailableAccounts(
final ethereumLedgerApp = EthereumLedgerApp(ledger); {int index = 0, int limit = 5}) async {
final ethereumLedgerApp = EthereumLedgerApp(ledgerConnection);
final version = await ethereumLedgerApp.getVersion(device); await ethereumLedgerApp.getVersion();
final accounts = <HardwareAccountData>[]; final accounts = <HardwareAccountData>[];
final indexRange = List.generate(limit, (i) => i + index); final indexRange = List.generate(limit, (i) => i + index);
for (final i in indexRange) { for (final i in indexRange) {
final derivationPath = "m/44'/60'/$i'/0/0"; final derivationPath = "m/44'/60'/$i'/0/0";
final address = final address = await ethereumLedgerApp.getAccounts(
await ethereumLedgerApp.getAccounts(device, accountsDerivationPath: derivationPath); accountsDerivationPath: derivationPath);
accounts.add(HardwareAccountData( accounts.add(HardwareAccountData(
address: address.first, address: address.first,

View file

@ -1,7 +1,6 @@
import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/output_info.dart'; import 'package:cw_core/output_info.dart';
import 'package:cw_evm/evm_chain_transaction_priority.dart'; import 'package:cw_evm/evm_chain_transaction_priority.dart';
import 'package:ledger_flutter/ledger_flutter.dart';
class EVMChainTransactionCredentials { class EVMChainTransactionCredentials {
EVMChainTransactionCredentials( EVMChainTransactionCredentials(

View file

@ -17,6 +17,9 @@ abstract class EVMChainWalletAddressesBase extends WalletAddresses with Store {
@observable @observable
String address; String address;
@override
String get primaryAddress => address;
@override @override
Future<void> init() async { Future<void> init() async {
address = walletInfo.address; address = walletInfo.address;

View file

@ -1,17 +1,16 @@
import 'dart:async'; import 'dart:async';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:cw_core/hardware/device_not_connected_exception.dart'; import 'package:cw_core/hardware/device_not_connected_exception.dart'
as exception;
import 'package:ledger_ethereum/ledger_ethereum.dart'; import 'package:ledger_ethereum/ledger_ethereum.dart';
import 'package:ledger_flutter/ledger_flutter.dart'; import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
import 'package:web3dart/crypto.dart'; import 'package:web3dart/crypto.dart';
import 'package:web3dart/web3dart.dart'; import 'package:web3dart/web3dart.dart';
class EvmLedgerCredentials extends CredentialsWithKnownAddress { class EvmLedgerCredentials extends CredentialsWithKnownAddress {
final String _address; final String _address;
Ledger? ledger;
LedgerDevice? ledgerDevice;
EthereumLedgerApp? ethereumLedgerApp; EthereumLedgerApp? ethereumLedgerApp;
EvmLedgerCredentials(this._address); EvmLedgerCredentials(this._address);
@ -19,25 +18,25 @@ class EvmLedgerCredentials extends CredentialsWithKnownAddress {
@override @override
EthereumAddress get address => EthereumAddress.fromHex(_address); EthereumAddress get address => EthereumAddress.fromHex(_address);
void setLedger(Ledger setLedger, [LedgerDevice? setLedgerDevice, String? derivationPath]) { void setLedgerConnection(LedgerConnection connection,
ledger = setLedger; [String? derivationPath]) {
ledgerDevice = setLedgerDevice; ethereumLedgerApp = EthereumLedgerApp(connection,
ethereumLedgerApp = derivationPath: derivationPath ?? "m/44'/60'/0'/0/0");
EthereumLedgerApp(ledger!, derivationPath: derivationPath ?? "m/44'/60'/0'/0/0");
} }
@override @override
MsgSignature signToEcSignature(Uint8List payload, {int? chainId, bool isEIP1559 = false}) => MsgSignature signToEcSignature(Uint8List payload,
throw UnimplementedError("EvmLedgerCredentials.signToEcSignature"); {int? chainId, bool isEIP1559 = false}) =>
throw UnimplementedError("EvmLedgerCredentials.signToEcSignature");
@override @override
Future<MsgSignature> signToSignature(Uint8List payload, Future<MsgSignature> signToSignature(Uint8List payload,
{int? chainId, bool isEIP1559 = false}) async { {int? chainId, bool isEIP1559 = false}) async {
if (ledgerDevice == null && ledger?.devices.isNotEmpty != true) { if (ethereumLedgerApp == null) {
throw DeviceNotConnectedException(); throw exception.DeviceNotConnectedException();
} }
final sig = await ethereumLedgerApp!.signTransaction(device, payload); final sig = await ethereumLedgerApp!.signTransaction(payload);
final v = sig[0].toInt(); final v = sig[0].toInt();
final r = bytesToHex(sig.sublist(1, 1 + 32)); final r = bytesToHex(sig.sublist(1, 1 + 32));
@ -65,14 +64,16 @@ class EvmLedgerCredentials extends CredentialsWithKnownAddress {
chainIdV = chainId != null ? (parity + (chainId * 2 + 35)) : parity; chainIdV = chainId != null ? (parity + (chainId * 2 + 35)) : parity;
} }
return MsgSignature(BigInt.parse(r, radix: 16), BigInt.parse(s, radix: 16), chainIdV); return MsgSignature(
BigInt.parse(r, radix: 16), BigInt.parse(s, radix: 16), chainIdV);
} }
@override @override
Future<Uint8List> signPersonalMessage(Uint8List payload, {int? chainId}) async { Future<Uint8List> signPersonalMessage(Uint8List payload,
if (isNotConnected) throw DeviceNotConnectedException(); {int? chainId}) async {
if (isNotConnected) throw exception.DeviceNotConnectedException();
final sig = await ethereumLedgerApp!.signMessage(device, payload); final sig = await ethereumLedgerApp!.signMessage(payload);
final r = sig.sublist(1, 1 + 32); final r = sig.sublist(1, 1 + 32);
final s = sig.sublist(1 + 32, 1 + 32 + 32); final s = sig.sublist(1 + 32, 1 + 32 + 32);
@ -84,20 +85,22 @@ class EvmLedgerCredentials extends CredentialsWithKnownAddress {
@override @override
Uint8List signPersonalMessageToUint8List(Uint8List payload, {int? chainId}) => Uint8List signPersonalMessageToUint8List(Uint8List payload, {int? chainId}) =>
throw UnimplementedError("EvmLedgerCredentials.signPersonalMessageToUint8List"); throw UnimplementedError(
"EvmLedgerCredentials.signPersonalMessageToUint8List");
Future<void> provideERC20Info(String erc20ContractAddress, int chainId) async { Future<void> provideERC20Info(
if (isNotConnected) throw DeviceNotConnectedException(); String erc20ContractAddress, int chainId) async {
if (isNotConnected) throw exception.DeviceNotConnectedException();
try { try {
await ethereumLedgerApp!.getAndProvideERC20TokenInformation(device, await ethereumLedgerApp!.getAndProvideERC20TokenInformation(
erc20ContractAddress: erc20ContractAddress, chainId: chainId); erc20ContractAddress: erc20ContractAddress, chainId: chainId);
} on LedgerException catch (e) { } catch (e) {
if (e.errorCode != -28672) rethrow; print(e);
rethrow;
// if (e.errorCode != -28672) rethrow;
} }
} }
bool get isNotConnected => (ledgerDevice ?? ledger?.devices.firstOrNull) == null; bool get isNotConnected => ethereumLedgerApp == null || ethereumLedgerApp!.connection.isDisconnected;
LedgerDevice get device => ledgerDevice ?? ledger!.devices.first;
} }

View file

@ -25,20 +25,17 @@ dependencies:
mobx: ^2.0.7+4 mobx: ^2.0.7+4
cw_core: cw_core:
path: ../cw_core path: ../cw_core
ledger_flutter: ^1.0.1 ledger_flutter_plus: ^1.4.1
ledger_ethereum: ledger_ethereum:
git: git:
url: https://github.com/cake-tech/ledger-ethereum.git url: https://github.com/cake-tech/ledger-flutter-plus-plugins
path: packages/ledger-ethereum
dependency_overrides: dependency_overrides:
web3dart: web3dart:
git: git:
url: https://github.com/cake-tech/web3dart.git url: https://github.com/cake-tech/web3dart.git
ref: cake ref: cake
ledger_flutter:
git:
url: https://github.com/cake-tech/ledger-flutter.git
ref: cake-v3
watcher: ^1.1.0 watcher: ^1.1.0
dev_dependencies: dev_dependencies:

View file

@ -29,6 +29,9 @@ abstract class MoneroWalletAddressesBase extends WalletAddresses with Store {
@observable @observable
String address; String address;
@override
String get primaryAddress => getAddress(accountIndex: account?.id ?? 0, addressIndex: 0);
@override @override
String get latestAddress { String get latestAddress {
var addressIndex = subaddress_list.numSubaddresses(account?.id??0) - 1; var addressIndex = subaddress_list.numSubaddresses(account?.id??0) - 1;

View file

@ -40,7 +40,7 @@ class CwMweb {
} }
static Future<void> _initializeClient() async { static Future<void> _initializeClient() async {
print("initialize client called!"); print("_initializeClient() called!");
final appDir = await getApplicationSupportDirectory(); final appDir = await getApplicationSupportDirectory();
const ltcNodeUri = "ltc-electrum.cakewallet.com:9333"; const ltcNodeUri = "ltc-electrum.cakewallet.com:9333";
@ -54,7 +54,7 @@ class CwMweb {
log("Attempting to connect to server on port: $_port"); log("Attempting to connect to server on port: $_port");
// wait for the server to finish starting up before we try to connect to it: // wait for the server to finish starting up before we try to connect to it:
await Future.delayed(const Duration(seconds: 5)); await Future.delayed(const Duration(seconds: 8));
_clientChannel = ClientChannel('127.0.0.1', port: _port!, channelShutdownHandler: () { _clientChannel = ClientChannel('127.0.0.1', port: _port!, channelShutdownHandler: () {
_rpcClient = null; _rpcClient = null;
@ -83,10 +83,13 @@ class CwMweb {
log("Attempt $i failed: $e"); log("Attempt $i failed: $e");
log('Caught grpc error: ${e.message}'); log('Caught grpc error: ${e.message}');
_rpcClient = null; _rpcClient = null;
// necessary if the database isn't open:
await stop();
await Future.delayed(const Duration(seconds: 3)); await Future.delayed(const Duration(seconds: 3));
} catch (e) { } catch (e) {
log("Attempt $i failed: $e"); log("Attempt $i failed: $e");
_rpcClient = null; _rpcClient = null;
await stop();
await Future.delayed(const Duration(seconds: 3)); await Future.delayed(const Duration(seconds: 3));
} }
} }

View file

@ -18,6 +18,9 @@ abstract class NanoWalletAddressesBase extends WalletAddresses with Store {
@observable @observable
String address; String address;
@override
String get primaryAddress => address;
@observable @observable
NanoAccount? account; NanoAccount? account;

View file

@ -14,6 +14,9 @@ abstract class SolanaWalletAddressesBase extends WalletAddresses with Store {
@override @override
String address; String address;
@override
String get primaryAddress => address;
@override @override
Future<void> init() async { Future<void> init() async {
address = walletInfo.address; address = walletInfo.address;

View file

@ -17,6 +17,9 @@ abstract class TronWalletAddressesBase extends WalletAddresses with Store {
@observable @observable
String address; String address;
@override
String get primaryAddress => address;
@override @override
Future<void> init() async { Future<void> init() async {
address = walletInfo.address; address = walletInfo.address;

View file

@ -29,6 +29,9 @@ abstract class WowneroWalletAddressesBase extends WalletAddresses with Store {
@observable @observable
String address; String address;
@override
String get primaryAddress => getAddress(accountIndex: account?.id ?? 0, addressIndex: 0);
@override @override
String get latestAddress { String get latestAddress {
var addressIndex = subaddress_list.numSubaddresses(account?.id??0) - 1; var addressIndex = subaddress_list.numSubaddresses(account?.id??0) - 1;

View file

@ -5,7 +5,7 @@
**N:B** Throughout this guide, `walletx` refers to the specific wallet type you want to add. If you're adding `BNB` to CakeWallet, then `walletx` for you here is `bnb`. **N:B** Throughout this guide, `walletx` refers to the specific wallet type you want to add. If you're adding `BNB` to CakeWallet, then `walletx` for you here is `bnb`.
**Core Folder/Files Setup** **Core Folder/Files Setup**
- Idenitify your core component/package (major project component), which would power the integration e.g web3dart, solana, onchain etc - Identify your core component/package (major project component), which would power the integration e.g web3dart, solana, onchain etc
- Add a new entry to `WalletType` class in `cw_core/wallet_type.dart`. - Add a new entry to `WalletType` class in `cw_core/wallet_type.dart`.
- Fill out the necessary information in the various functions in the files, concerning the wallet name, the native currency type, symbol etc. - Fill out the necessary information in the various functions in the files, concerning the wallet name, the native currency type, symbol etc.
- Go to `cw_core/lib/currency_for_wallet_type.dart`, in the `currencyForWalletType` function, add a case for `walletx`, returning the native cryptocurrency for `walletx`. - Go to `cw_core/lib/currency_for_wallet_type.dart`, in the `currencyForWalletType` function, add a case for `walletx`, returning the native cryptocurrency for `walletx`.
@ -144,7 +144,7 @@ You can add as many node entries as desired.
} }
} }
- Next, well write the function to change walletX current node to default. An handy function we would make use of later on. Add a new preference key in `lib/entities/preference_key.dart` with the format `PreferencesKey.currentWalletXNodeIdKey`, well use it to identify the current node id. - Next, well write the function to change walletX current node to default. A handy function we would make use of later on. Add a new preference key in `lib/entities/preference_key.dart` with the format `PreferencesKey.currentWalletXNodeIdKey`, well use it to identify the current node id.
Future<void> changeWalletXCurrentNodeToDefault( Future<void> changeWalletXCurrentNodeToDefault(
{required SharedPreferences sharedPreferences, required Box<Node> nodes}) async { {required SharedPreferences sharedPreferences, required Box<Node> nodes}) async {
@ -228,7 +228,7 @@ Now you can run the codebase and successfully create a wallet for type walletX s
**Balance Screen** **Balance Screen**
- Go to `lib/view_model/dashboard/balance_view_model.dart` - Go to `lib/view_model/dashboard/balance_view_model.dart`
- Modify the function to adjust the way the balance is being display on the app: `isHomeScreenSettingsEnabled` - Modify the function to adjust the way the balance is being displayed on the app: `isHomeScreenSettingsEnabled`
- Add a case to the `availableBalanceLabel` getter to modify the text being displayed (Available or confirmed) - Add a case to the `availableBalanceLabel` getter to modify the text being displayed (Available or confirmed)
- Same for `additionalBalanceLabel` - Same for `additionalBalanceLabel`
- Next, go to `lib/reactions/fiat_rate_update.dart` - Next, go to `lib/reactions/fiat_rate_update.dart`

View file

@ -57,7 +57,7 @@ Proceed into the source code before proceeding with the next steps:
### 7. Execute Build & Setup Commands for Cake Wallet ### 7. Execute Build & Setup Commands for Cake Wallet
We need to generate project settings like app name, app icon, package name, etc. For this need to setup environment variables and configure project files. We need to generate project settings like app name, app icon, package name, etc. For this, we need to setup environment variables and configure project files.
Please pick what app you want to build: cakewallet or monero.com. Please pick what app you want to build: cakewallet or monero.com.
@ -92,7 +92,7 @@ Then we need to generate localization files and mobx models.
`$ flutter build ios --release` `$ flutter build ios --release`
Then you can open `ios/Runner.xcworkspace` with Xcode and you can to archive the application. Then you can open `ios/Runner.xcworkspace` with Xcode and you can archive the application.
Or if you want to run to connected device: Or if you want to run to connected device:

View file

@ -22,7 +22,7 @@ Then install `Desktop development with C++` packages via Visual Studio 2022, or
- `C++ 2022 Redistributable Update` - `C++ 2022 Redistributable Update`
- `C++ core desktop features` - `C++ core desktop features`
- `MVC v143 - VS 2022 C++ x64/x86 build tools` - `MVC v143 - VS 2022 C++ x64/x86 build tools`
- `C++ CMake tools for Windwos` - `C++ CMake tools for Windows`
- `Testing tools core features - Build Tools` - `Testing tools core features - Build Tools`
- `C++ AddressSanitizer`. - `C++ AddressSanitizer`.
@ -38,7 +38,7 @@ For building monero dependencies, it is required to install Windows WSL (https:/
### 5. Pull Cake Wallet source code ### 5. Pull Cake Wallet source code
You can downlaod CakeWallet source code from our [GitHub repository](github.com/cake-tech/cake_wallet) via git: You can download CakeWallet source code from our [GitHub repository](github.com/cake-tech/cake_wallet) via git:
`$ git clone https://github.com/cake-tech/cake_wallet.git --branch MrCyjaneK-cyjan-monerodart` `$ git clone https://github.com/cake-tech/cake_wallet.git --branch MrCyjaneK-cyjan-monerodart`
OR you can download it as [Zip archive](https://github.com/cake-tech/cake_wallet/archive/refs/heads/MrCyjaneK-cyjan-monerodart.zip) OR you can download it as [Zip archive](https://github.com/cake-tech/cake_wallet/archive/refs/heads/MrCyjaneK-cyjan-monerodart.zip)
@ -52,6 +52,6 @@ For that you need to run the shell (bash - typically same named utility should b
To configure the application, open the directory where you have downloaded or unarchived Cake Wallet sources and run `cakewallet.bat`. To configure the application, open the directory where you have downloaded or unarchived Cake Wallet sources and run `cakewallet.bat`.
Or if you used WSL and have active shell session you can run `$ ./cakewallet.sh` script in `scripts/windows` which will run `cakewallet.bat` in WSL. Or if you used WSL and have active shell session you can run `$ ./cakewallet.sh` script in `scripts/windows` which will run `cakewallet.bat` in WSL.
After execution of `cakewallet.bat` you should to get `Cake Wallet.zip` in project root directory which will contains `CakeWallet.exe` file and another needed files for run the application. Now you can extract files from `Cake Wallet.zip` archive and run the application. After execution of `cakewallet.bat` you should to get `Cake Wallet.zip` in project root directory which will contain `CakeWallet.exe` file and another needed files for run the application. Now you can extract files from `Cake Wallet.zip` archive and run the application.
Copyright (c) 2024 Cake Labs LLC. Copyright (c) 2024 Cake Labs LLC.

View file

@ -110,12 +110,12 @@ import workmanager
private func makeSecure() { private func makeSecure() {
if (!self.window.subviews.contains(textField)) { if (!self.window.subviews.contains(textField)) {
let view = UIView(frame: CGRect(x: 0, y: 0, width: textField.frame.self.width, height: textField.frame.self.height))
self.window.addSubview(textField) self.window.addSubview(textField)
textField.centerYAnchor.constraint(equalTo: self.window.centerYAnchor).isActive = true
textField.centerXAnchor.constraint(equalTo: self.window.centerXAnchor).isActive = true
self.window.layer.superlayer?.addSublayer(textField.layer) self.window.layer.superlayer?.addSublayer(textField.layer)
textField.layer.sublayers?.first?.addSublayer(self.window.layer) textField.layer.sublayers?.last!.addSublayer(self.window.layer)
textField.leftView = view
textField.leftViewMode = .always
} }
} }
}
}

View file

@ -106,34 +106,33 @@ class CWBitcoin extends Bitcoin {
} }
@override @override
Object createBitcoinTransactionCredentials(List<Output> outputs, Object createBitcoinTransactionCredentials(
{required TransactionPriority priority, int? feeRate}) { List<Output> outputs, {
required TransactionPriority priority,
int? feeRate,
UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any,
}) {
final bitcoinFeeRate = final bitcoinFeeRate =
priority == BitcoinTransactionPriority.custom && feeRate != null ? feeRate : null; priority == BitcoinTransactionPriority.custom && feeRate != null ? feeRate : null;
return BitcoinTransactionCredentials( return BitcoinTransactionCredentials(
outputs outputs
.map((out) => OutputInfo( .map((out) => OutputInfo(
fiatAmount: out.fiatAmount, fiatAmount: out.fiatAmount,
cryptoAmount: out.cryptoAmount, cryptoAmount: out.cryptoAmount,
address: out.address, address: out.address,
note: out.note, note: out.note,
sendAll: out.sendAll, sendAll: out.sendAll,
extractedAddress: out.extractedAddress, extractedAddress: out.extractedAddress,
isParsedAddress: out.isParsedAddress, isParsedAddress: out.isParsedAddress,
formattedCryptoAmount: out.formattedCryptoAmount, formattedCryptoAmount: out.formattedCryptoAmount,
memo: out.memo)) memo: out.memo))
.toList(), .toList(),
priority: priority as BitcoinTransactionPriority, priority: priority as BitcoinTransactionPriority,
feeRate: bitcoinFeeRate); feeRate: bitcoinFeeRate,
coinTypeToSpendFrom: coinTypeToSpendFrom,
);
} }
@override
Object createBitcoinTransactionCredentialsRaw(List<OutputInfo> outputs,
{TransactionPriority? priority, required int feeRate}) =>
BitcoinTransactionCredentials(outputs,
priority: priority != null ? priority as BitcoinTransactionPriority : null,
feeRate: feeRate);
@override @override
@computed @computed
List<ElectrumSubAddress> getSubAddresses(Object wallet) { List<ElectrumSubAddress> getSubAddresses(Object wallet) {
@ -205,9 +204,19 @@ class CWBitcoin extends Bitcoin {
(priority as BitcoinTransactionPriority).labelWithRate(rate, customRate); (priority as BitcoinTransactionPriority).labelWithRate(rate, customRate);
@override @override
List<BitcoinUnspent> getUnspents(Object wallet) { List<BitcoinUnspent> getUnspents(Object wallet,
{UnspentCoinType coinTypeToSpendFrom = UnspentCoinType.any}) {
final bitcoinWallet = wallet as ElectrumWallet; final bitcoinWallet = wallet as ElectrumWallet;
return bitcoinWallet.unspentCoins; return bitcoinWallet.unspentCoins.where((element) {
switch (coinTypeToSpendFrom) {
case UnspentCoinType.mweb:
return element.bitcoinAddressRecord.type == SegwitAddresType.mweb;
case UnspentCoinType.nonMweb:
return element.bitcoinAddressRecord.type != SegwitAddresType.mweb;
case UnspentCoinType.any:
return true;
}
}).toList();
} }
Future<void> updateUnspents(Object wallet) async { Future<void> updateUnspents(Object wallet) async {
@ -389,19 +398,21 @@ class CWBitcoin extends Bitcoin {
final history = await electrumClient.getHistory(sh); final history = await electrumClient.getHistory(sh);
final balance = await electrumClient.getBalance(sh); final balance = await electrumClient.getBalance(sh);
dInfoCopy.balance = balance.entries.first.value.toString(); dInfoCopy.balance = balance.entries.firstOrNull?.value.toString() ?? "0";
dInfoCopy.address = address; dInfoCopy.address = address;
dInfoCopy.transactionsCount = history.length; dInfoCopy.transactionsCount = history.length;
list.add(dInfoCopy); list.add(dInfoCopy);
} catch (e) { } catch (e, s) {
print(e); print("derivationInfoError: $e");
print("derivationInfoStack: $s");
} }
} }
} }
// sort the list such that derivations with the most transactions are first: // sort the list such that derivations with the most transactions are first:
list.sort((a, b) => b.transactionsCount.compareTo(a.transactionsCount)); list.sort((a, b) => b.transactionsCount.compareTo(a.transactionsCount));
return list; return list;
} }
@ -476,18 +487,30 @@ class CWBitcoin extends Bitcoin {
} }
@override @override
void setLedger(WalletBase wallet, Ledger ledger, LedgerDevice device) { void setLedgerConnection(WalletBase wallet, ledger.LedgerConnection connection) {
(wallet as BitcoinWallet).setLedger(ledger, device); (wallet as ElectrumWallet).setLedgerConnection(connection);
} }
@override @override
Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM, Future<List<HardwareAccountData>> getHardwareWalletBitcoinAccounts(LedgerViewModel ledgerVM,
{int index = 0, int limit = 5}) async { {int index = 0, int limit = 5}) async {
final hardwareWalletService = BitcoinHardwareWalletService(ledgerVM.ledger, ledgerVM.device); final hardwareWalletService = BitcoinHardwareWalletService(ledgerVM.connection);
try { try {
return hardwareWalletService.getAvailableAccounts(index: index, limit: limit); return hardwareWalletService.getAvailableAccounts(index: index, limit: limit);
} on LedgerException catch (err) { } catch (err) {
print(err.message); print(err);
throw err;
}
}
@override
Future<List<HardwareAccountData>> getHardwareWalletLitecoinAccounts(LedgerViewModel ledgerVM,
{int index = 0, int limit = 5}) async {
final hardwareWalletService = LitecoinHardwareWalletService(ledgerVM.connection);
try {
return hardwareWalletService.getAvailableAccounts(index: index, limit: limit);
} catch (err) {
print(err);
throw err; throw err;
} }
} }
@ -672,4 +695,15 @@ class CWBitcoin extends Bitcoin {
return null; return null;
} }
} }
String? getUnusedSegwitAddress(Object wallet) {
try {
final electrumWallet = wallet as ElectrumWallet;
final segwitAddress = electrumWallet.walletAddresses.allAddresses
.firstWhere((element) => !element.isUsed && element.type == SegwitAddresType.p2wpkh);
return segwitAddress.address;
} catch (_) {
return null;
}
}
} }

View file

@ -36,9 +36,9 @@ class AddressValidator extends TextValidator {
'|[0-9a-zA-Z]{105}|addr1[0-9a-zA-Z]{98}'; '|[0-9a-zA-Z]{105}|addr1[0-9a-zA-Z]{98}';
case CryptoCurrency.btc: case CryptoCurrency.btc:
pattern = pattern =
'${P2pkhAddress.regex.pattern}|${P2shAddress.regex.pattern}|${P2wpkhAddress.regex.pattern}|${P2trAddress.regex.pattern}|${P2wshAddress.regex.pattern}|${SilentPaymentAddress.regex.pattern}'; '${P2pkhAddress.regex.pattern}|${P2shAddress.regex.pattern}|${RegExp(r'(bc|tb)1q[ac-hj-np-z02-9]{25,39}}').pattern}|${P2trAddress.regex.pattern}|${P2wshAddress.regex.pattern}|${SilentPaymentAddress.regex.pattern}';
case CryptoCurrency.ltc: case CryptoCurrency.ltc:
pattern = '^${P2wpkhAddress.regex.pattern}\$|^${MwebAddress.regex.pattern}\$'; pattern = '^${RegExp(r'ltc1q[ac-hj-np-z02-9]{25,39}').pattern}\$|^${MwebAddress.regex.pattern}\$';
case CryptoCurrency.nano: case CryptoCurrency.nano:
pattern = '[0-9a-zA-Z_]+'; pattern = '[0-9a-zA-Z_]+';
case CryptoCurrency.banano: case CryptoCurrency.banano:

View file

@ -2,6 +2,7 @@ import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:cake_wallet/core/secure_storage.dart'; import 'package:cake_wallet/core/secure_storage.dart';
import 'package:cake_wallet/entities/get_encryption_key.dart';
import 'package:cake_wallet/entities/transaction_description.dart'; import 'package:cake_wallet/entities/transaction_description.dart';
import 'package:cake_wallet/themes/theme_list.dart'; import 'package:cake_wallet/themes/theme_list.dart';
import 'package:cw_core/root_dir.dart'; import 'package:cw_core/root_dir.dart';
@ -105,7 +106,15 @@ class BackupService {
if (entity.path == archivePath || entity.path == tmpDir.path) { if (entity.path == archivePath || entity.path == tmpDir.path) {
return; return;
} }
final filename = entity.absolute;
for (var ignore in ignoreFiles) {
final filename = entity.absolute.path;
if (filename.endsWith(ignore) && !filename.contains("wallets/")) {
print("ignoring backup file: $filename");
return;
}
}
print("restoring: $filename");
if (entity.statSync().type == FileSystemEntityType.directory) { if (entity.statSync().type == FileSystemEntityType.directory) {
zipEncoder.addDirectory(Directory(entity.path)); zipEncoder.addDirectory(Directory(entity.path));
} else { } else {
@ -148,14 +157,29 @@ class BackupService {
await _importPreferencesDump(); await _importPreferencesDump();
} }
// checked with .endsWith - so this should be the last part of the filename
static const ignoreFiles = [
"flutter_assets/kernel_blob.bin",
"flutter_assets/vm_snapshot_data",
"flutter_assets/isolate_snapshot_data",
".lock",
];
Future<void> _importBackupV2(Uint8List data, String password) async { Future<void> _importBackupV2(Uint8List data, String password) async {
final appDir = await getAppDir(); final appDir = await getAppDir();
final decryptedData = await _decryptV2(data, password); final decryptedData = await _decryptV2(data, password);
final zip = ZipDecoder().decodeBytes(decryptedData); final zip = ZipDecoder().decodeBytes(decryptedData);
outer:
for (var file in zip.files) { for (var file in zip.files) {
final filename = file.name; final filename = file.name;
for (var ignore in ignoreFiles) {
if (filename.endsWith(ignore) && !filename.contains("wallets/")) {
print("ignoring backup file: $filename");
continue outer;
}
}
print("restoring: $filename");
if (file.isFile) { if (file.isFile) {
final content = file.content as List<int>; final content = file.content as List<int>;
File('${appDir.path}/' + filename) File('${appDir.path}/' + filename)
@ -206,6 +230,16 @@ class BackupService {
json.decode(transactionDescriptionFile.readAsStringSync()) as Map<String, dynamic>; json.decode(transactionDescriptionFile.readAsStringSync()) as Map<String, dynamic>;
final descriptionsMap = jsonData.map((key, value) => final descriptionsMap = jsonData.map((key, value) =>
MapEntry(key, TransactionDescription.fromJson(value as Map<String, dynamic>))); MapEntry(key, TransactionDescription.fromJson(value as Map<String, dynamic>)));
if (!_transactionDescriptionBox.isOpen) {
final transactionDescriptionsBoxKey = await getEncryptionKey(secureStorage: secureStorageShared, forKey: TransactionDescription.boxKey);
final transactionDescriptionBox = await CakeHive.openBox<TransactionDescription>(
TransactionDescription.boxName,
encryptionKey: transactionDescriptionsBoxKey,
);
await transactionDescriptionBox.putAll(descriptionsMap);
return;
}
await _transactionDescriptionBox.putAll(descriptionsMap); await _transactionDescriptionBox.putAll(descriptionsMap);
} }

View file

@ -167,6 +167,7 @@ import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_i
import 'package:cake_wallet/view_model/wallet_list/wallet_edit_view_model.dart'; import 'package:cake_wallet/view_model/wallet_list/wallet_edit_view_model.dart';
import 'package:cake_wallet/view_model/wallet_restore_choose_derivation_view_model.dart'; import 'package:cake_wallet/view_model/wallet_restore_choose_derivation_view_model.dart';
import 'package:cw_core/nano_account.dart'; import 'package:cw_core/nano_account.dart';
import 'package:cw_core/unspent_coin_type.dart';
import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/unspent_coins_info.dart';
import 'package:cw_core/wallet_service.dart'; import 'package:cw_core/wallet_service.dart';
import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/transaction_info.dart';
@ -725,8 +726,8 @@ Future<void> setup({
getIt.get<SendTemplateStore>(), getIt.get<SendTemplateStore>(),
getIt.get<FiatConversionStore>())); getIt.get<FiatConversionStore>()));
getIt.registerFactory<SendViewModel>( getIt.registerFactoryParam<SendViewModel, UnspentCoinType?, void>(
() => SendViewModel( (coinTypeToSpendFrom, _) => SendViewModel(
getIt.get<AppStore>(), getIt.get<AppStore>(),
getIt.get<SendTemplateViewModel>(), getIt.get<SendTemplateViewModel>(),
getIt.get<FiatConversionStore>(), getIt.get<FiatConversionStore>(),
@ -734,12 +735,13 @@ Future<void> setup({
getIt.get<ContactListViewModel>(), getIt.get<ContactListViewModel>(),
_transactionDescriptionBox, _transactionDescriptionBox,
getIt.get<AppStore>().wallet!.isHardwareWallet ? getIt.get<LedgerViewModel>() : null, getIt.get<AppStore>().wallet!.isHardwareWallet ? getIt.get<LedgerViewModel>() : null,
coinTypeToSpendFrom: coinTypeToSpendFrom ?? UnspentCoinType.any,
), ),
); );
getIt.registerFactoryParam<SendPage, PaymentRequest?, void>( getIt.registerFactoryParam<SendPage, PaymentRequest?, UnspentCoinType?>(
(PaymentRequest? initialPaymentRequest, _) => SendPage( (PaymentRequest? initialPaymentRequest, coinTypeToSpendFrom) => SendPage(
sendViewModel: getIt.get<SendViewModel>(), sendViewModel: getIt.get<SendViewModel>(param1: coinTypeToSpendFrom),
authService: getIt.get<AuthService>(), authService: getIt.get<AuthService>(),
initialPaymentRequest: initialPaymentRequest, initialPaymentRequest: initialPaymentRequest,
)); ));
@ -1215,14 +1217,21 @@ Future<void> setup({
getIt.registerFactory(() => SupportOtherLinksPage(getIt.get<SupportViewModel>())); getIt.registerFactory(() => SupportOtherLinksPage(getIt.get<SupportViewModel>()));
getIt.registerFactory(() { getIt.registerFactoryParam<UnspentCoinsListViewModel, UnspentCoinType?, void>(
(coinTypeToSpendFrom, _) {
final wallet = getIt.get<AppStore>().wallet; final wallet = getIt.get<AppStore>().wallet;
return UnspentCoinsListViewModel(wallet: wallet!, unspentCoinsInfo: _unspentCoinsInfoSource); return UnspentCoinsListViewModel(
wallet: wallet!,
unspentCoinsInfo: _unspentCoinsInfoSource,
coinTypeToSpendFrom: coinTypeToSpendFrom ?? UnspentCoinType.any,
);
}); });
getIt.registerFactory(() => getIt.registerFactoryParam<UnspentCoinsListPage, UnspentCoinType?, void>(
UnspentCoinsListPage(unspentCoinsListViewModel: getIt.get<UnspentCoinsListViewModel>())); (coinTypeToSpendFrom, _) => UnspentCoinsListPage(
unspentCoinsListViewModel:
getIt.get<UnspentCoinsListViewModel>(param1: coinTypeToSpendFrom)));
getIt.registerFactoryParam<UnspentCoinsDetailsViewModel, UnspentCoinsItem, getIt.registerFactoryParam<UnspentCoinsDetailsViewModel, UnspentCoinsItem,
UnspentCoinsListViewModel>( UnspentCoinsListViewModel>(

View file

@ -254,6 +254,10 @@ Future<void> defaultSettingsMigration(
case 41: case 41:
_deselectQuantex(sharedPreferences); _deselectQuantex(sharedPreferences);
await _addSethNode(nodes, sharedPreferences); await _addSethNode(nodes, sharedPreferences);
await updateTronNodesWithNowNodes(sharedPreferences: sharedPreferences, nodes: nodes);
break;
case 42:
updateBtcElectrumNodeToUseSSL(nodes, sharedPreferences);
break; break;
default: default:
break; break;
@ -269,6 +273,15 @@ Future<void> defaultSettingsMigration(
await sharedPreferences.setInt(PreferencesKey.currentDefaultSettingsMigrationVersion, version); await sharedPreferences.setInt(PreferencesKey.currentDefaultSettingsMigrationVersion, version);
} }
void updateBtcElectrumNodeToUseSSL(Box<Node> nodes, SharedPreferences sharedPreferences) {
final btcElectrumNode = nodes.values.firstWhereOrNull((element) => element.uriRaw == newCakeWalletBitcoinUri);
if (btcElectrumNode != null) {
btcElectrumNode.useSSL = true;
btcElectrumNode.save();
}
}
void _deselectQuantex(SharedPreferences sharedPreferences) { void _deselectQuantex(SharedPreferences sharedPreferences) {
final Map<String, dynamic> exchangeProvidersSelection = final Map<String, dynamic> exchangeProvidersSelection =
json.decode(sharedPreferences.getString(PreferencesKey.exchangeProvidersSelection) ?? "{}") json.decode(sharedPreferences.getString(PreferencesKey.exchangeProvidersSelection) ?? "{}")
@ -898,7 +911,9 @@ Future<void> changeDefaultBitcoinNode(
final newCakeWalletBitcoinNode = final newCakeWalletBitcoinNode =
Node(uri: newCakeWalletBitcoinUri, type: WalletType.bitcoin, useSSL: false); Node(uri: newCakeWalletBitcoinUri, type: WalletType.bitcoin, useSSL: false);
await nodeSource.add(newCakeWalletBitcoinNode); if (!nodeSource.values.any((element) => element.uriRaw == newCakeWalletBitcoinUri)) {
await nodeSource.add(newCakeWalletBitcoinNode);
}
if (needToReplaceCurrentBitcoinNode) { if (needToReplaceCurrentBitcoinNode) {
await sharedPreferences.setInt( await sharedPreferences.setInt(
@ -930,6 +945,10 @@ Future<void> _addBitcoinNode({
bool replaceExisting = false, bool replaceExisting = false,
bool useSSL = false, bool useSSL = false,
}) async { }) async {
bool isNodeExists = nodeSource.values.any((element) => element.uriRaw == nodeUri);
if (isNodeExists) {
return;
}
const cakeWalletBitcoinNodeUriPattern = '.cakewallet.com'; const cakeWalletBitcoinNodeUriPattern = '.cakewallet.com';
final currentBitcoinNodeId = final currentBitcoinNodeId =
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey); sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
@ -1317,3 +1336,16 @@ Future<void> removeMoneroWorld(
await changeMoneroCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes); await changeMoneroCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
} }
} }
Future<void> updateTronNodesWithNowNodes({
required SharedPreferences sharedPreferences,
required Box<Node> nodes,
}) async {
final tronNowNodesUri = 'trx.nownodes.io';
if (nodes.values.any((node) => node.uriRaw == tronNowNodesUri)) return;
await nodes.add(Node(uri: tronNowNodesUri, type: WalletType.tron));
await replaceTronDefaultNode(sharedPreferences: sharedPreferences, nodes: nodes);
}

View file

@ -0,0 +1,65 @@
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as ledger;
class HardwareWalletDevice {
final String name;
final HardwareWalletDeviceType type;
final HardwareWalletConnectionType connectionType;
const HardwareWalletDevice({
required this.name,
required this.type,
required this.connectionType,
});
factory HardwareWalletDevice.fromLedgerDevice(ledger.LedgerDevice device) =>
HardwareWalletDevice(
name: device.name,
type: device.deviceInfo.toGeneric(),
connectionType: device.connectionType.toGeneric(),
);
}
enum HardwareWalletDeviceType {
ledgerBlue,
ledgerNanoS,
ledgerNanoX,
ledgerNanoSPlus,
ledgerStax,
ledgerFlex;
}
enum HardwareWalletConnectionType {
usb,
ble,
nfc;
}
extension ToGenericHardwareWalletDeviceType on ledger.LedgerDeviceType {
HardwareWalletDeviceType toGeneric() {
switch (this) {
case ledger.LedgerDeviceType.blue:
return HardwareWalletDeviceType.ledgerBlue;
case ledger.LedgerDeviceType.nanoS:
return HardwareWalletDeviceType.ledgerNanoS;
case ledger.LedgerDeviceType.nanoSP:
return HardwareWalletDeviceType.ledgerNanoSPlus;
case ledger.LedgerDeviceType.nanoX:
return HardwareWalletDeviceType.ledgerNanoX;
case ledger.LedgerDeviceType.stax:
return HardwareWalletDeviceType.ledgerStax;
case ledger.LedgerDeviceType.flex:
return HardwareWalletDeviceType.ledgerFlex;
}
}
}
extension ToGenericHardwareWalletConnectionType on ledger.ConnectionType {
HardwareWalletConnectionType toGeneric() {
switch (this) {
case ledger.ConnectionType.usb:
return HardwareWalletConnectionType.usb;
case ledger.ConnectionType.ble:
return HardwareWalletConnectionType.ble;
}
}
}

View file

@ -191,21 +191,21 @@ class CWEthereum extends Ethereum {
String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress; String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress;
@override @override
void setLedger(WalletBase wallet, Ledger ledger, LedgerDevice device) { void setLedgerConnection(
((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedger( WalletBase wallet, ledger.LedgerConnection connection) {
ledger, ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials)
device.connectionType == ConnectionType.usb ? device : null, .setLedgerConnection(
wallet.walletInfo.derivationInfo?.derivationPath); connection, wallet.walletInfo.derivationInfo?.derivationPath);
} }
@override @override
Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM, Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM,
{int index = 0, int limit = 5}) async { {int index = 0, int limit = 5}) async {
final hardwareWalletService = EVMChainHardwareWalletService(ledgerVM.ledger, ledgerVM.device); final hardwareWalletService = EVMChainHardwareWalletService(ledgerVM.connection);
try { try {
return await hardwareWalletService.getAvailableAccounts(index: index, limit: limit); return await hardwareWalletService.getAvailableAccounts(index: index, limit: limit);
} on LedgerException catch (err) { } catch (err) {
print(err.message); print(err);
throw err; throw err;
} }
} }

View file

@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart'; import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart';
import 'package:cake_wallet/app_scroll_behavior.dart'; import 'package:cake_wallet/app_scroll_behavior.dart';
import 'package:cake_wallet/buy/order.dart'; import 'package:cake_wallet/buy/order.dart';
@ -43,6 +44,7 @@ import 'package:hive/hive.dart';
import 'package:cw_core/root_dir.dart'; import 'package:cw_core/root_dir.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:cw_core/window_size.dart'; import 'package:cw_core/window_size.dart';
import 'package:logging/logging.dart';
final navigatorKey = GlobalKey<NavigatorState>(); final navigatorKey = GlobalKey<NavigatorState>();
final rootKey = GlobalKey<RootState>(); final rootKey = GlobalKey<RootState>();
@ -68,8 +70,18 @@ Future<void> runAppWithZone({Key? topLevelKey}) async {
}; };
await initializeAppAtRoot(); await initializeAppAtRoot();
runApp(App(key: topLevelKey)); if (kDebugMode) {
final appDocDir = await getAppDir();
final ledgerFile = File('${appDocDir.path}/ledger_log.txt');
if (!ledgerFile.existsSync()) ledgerFile.createSync();
Logger.root.onRecord.listen((event) async {
final content = ledgerFile.readAsStringSync();
ledgerFile.writeAsStringSync("$content\n${event.message}");
});
}
runApp(App(key: topLevelKey));
isAppRunning = true; isAppRunning = true;
}, (error, stackTrace) async { }, (error, stackTrace) async {
if (!isAppRunning) { if (!isAppRunning) {
@ -192,7 +204,7 @@ Future<void> initializeAppConfigs() async {
transactionDescriptions: transactionDescriptions, transactionDescriptions: transactionDescriptions,
secureStorage: secureStorage, secureStorage: secureStorage,
anonpayInvoiceInfo: anonpayInvoiceInfo, anonpayInvoiceInfo: anonpayInvoiceInfo,
initialMigrationVersion: 41, initialMigrationVersion: 42,
); );
} }

View file

@ -190,20 +190,21 @@ class CWPolygon extends Polygon {
String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress; String getTokenAddress(CryptoCurrency asset) => (asset as Erc20Token).contractAddress;
@override @override
void setLedger(WalletBase wallet, Ledger ledger, LedgerDevice device) { void setLedgerConnection(
((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials).setLedger( WalletBase wallet, ledger.LedgerConnection connection) {
ledger, ((wallet as EVMChainWallet).evmChainPrivateKey as EvmLedgerCredentials)
device.connectionType == ConnectionType.usb ? device : null, .setLedgerConnection(
wallet.walletInfo.derivationInfo?.derivationPath); connection, wallet.walletInfo.derivationInfo?.derivationPath);
} }
@override @override
Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM, Future<List<HardwareAccountData>> getHardwareWalletAccounts(LedgerViewModel ledgerVM,
{int index = 0, int limit = 5}) async { {int index = 0, int limit = 5}) async {
final hardwareWalletService = EVMChainHardwareWalletService(ledgerVM.ledger, ledgerVM.device); final hardwareWalletService = EVMChainHardwareWalletService(ledgerVM.connection);
try { try {
return await hardwareWalletService.getAvailableAccounts(index: index, limit: limit); return await hardwareWalletService.getAvailableAccounts(index: index, limit: limit);
} on LedgerException catch (err) { } catch (err) {
print(err);
throw err; throw err;
} }
} }

View file

@ -120,6 +120,7 @@ import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/nano_account.dart'; import 'package:cw_core/nano_account.dart';
import 'package:cw_core/node.dart'; import 'package:cw_core/node.dart';
import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/unspent_coin_type.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
@ -184,7 +185,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
final type = settings.arguments as WalletType; final type = settings.arguments as WalletType;
final walletGroupsDisplayVM = getIt.get<WalletGroupsDisplayViewModel>(param1: type); final walletGroupsDisplayVM = getIt.get<WalletGroupsDisplayViewModel>(param1: type);
return CupertinoPageRoute<void>(builder: (_) => WalletGroupsDisplayPage(walletGroupsDisplayVM)); return CupertinoPageRoute<void>(
builder: (_) => WalletGroupsDisplayPage(walletGroupsDisplayVM));
case Routes.newWallet: case Routes.newWallet:
final args = settings.arguments as NewWalletArguments; final args = settings.arguments as NewWalletArguments;
@ -348,13 +350,17 @@ Route<dynamic> createRoute(RouteSettings settings) {
settings: settings, builder: (_) => getIt.get<DashboardPage>()); settings: settings, builder: (_) => getIt.get<DashboardPage>());
case Routes.send: case Routes.send:
final initialPaymentRequest = settings.arguments as PaymentRequest?; final args = settings.arguments as Map<String, dynamic>?;
final initialPaymentRequest = args?['paymentRequest'] as PaymentRequest?;
final coinTypeToSpendFrom = args?['coinTypeToSpendFrom'] as UnspentCoinType?;
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
fullscreenDialog: true, fullscreenDialog: true,
builder: (_) => getIt.get<SendPage>( builder: (_) => getIt.get<SendPage>(
param1: initialPaymentRequest, param1: initialPaymentRequest,
)); param2: coinTypeToSpendFrom,
),
);
case Routes.sendTemplate: case Routes.sendTemplate:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
@ -604,7 +610,9 @@ Route<dynamic> createRoute(RouteSettings settings) {
fullscreenDialog: true, builder: (_) => getIt.get<SupportOtherLinksPage>()); fullscreenDialog: true, builder: (_) => getIt.get<SupportOtherLinksPage>());
case Routes.unspentCoinsList: case Routes.unspentCoinsList:
return MaterialPageRoute<void>(builder: (_) => getIt.get<UnspentCoinsListPage>()); final coinTypeToSpendFrom = settings.arguments as UnspentCoinType?;
return MaterialPageRoute<void>(
builder: (_) => getIt.get<UnspentCoinsListPage>(param1: coinTypeToSpendFrom));
case Routes.unspentCoinsDetails: case Routes.unspentCoinsDetails:
final args = settings.arguments as List; final args = settings.arguments as List;
@ -778,7 +786,7 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.walletGroupDescription: case Routes.walletGroupDescription:
final walletType = settings.arguments as WalletType; final walletType = settings.arguments as WalletType;
return MaterialPageRoute<void>( return MaterialPageRoute<void>(
builder: (_) => WalletGroupDescriptionPage( builder: (_) => WalletGroupDescriptionPage(
selectedWalletType: walletType, selectedWalletType: walletType,

View file

@ -3,15 +3,14 @@ import 'dart:io';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/connect_device/debug_device_page.dart';
import 'package:cake_wallet/src/screens/connect_device/widgets/device_tile.dart'; import 'package:cake_wallet/src/screens/connect_device/widgets/device_tile.dart';
import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'package:cake_wallet/themes/extensions/cake_text_theme.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart'; import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:ledger_flutter/ledger_flutter.dart'; import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
typedef OnConnectDevice = void Function(BuildContext, LedgerViewModel); typedef OnConnectDevice = void Function(BuildContext, LedgerViewModel);
@ -19,7 +18,8 @@ class ConnectDevicePageParams {
final WalletType walletType; final WalletType walletType;
final OnConnectDevice onConnectDevice; final OnConnectDevice onConnectDevice;
ConnectDevicePageParams({required this.walletType, required this.onConnectDevice}); ConnectDevicePageParams(
{required this.walletType, required this.onConnectDevice});
} }
class ConnectDevicePage extends BasePage { class ConnectDevicePage extends BasePage {
@ -35,7 +35,8 @@ class ConnectDevicePage extends BasePage {
String get title => S.current.restore_title_from_hardware_wallet; String get title => S.current.restore_title_from_hardware_wallet;
@override @override
Widget body(BuildContext context) => ConnectDevicePageBody(walletType, onConnectDevice, ledgerVM); Widget body(BuildContext context) =>
ConnectDevicePageBody(walletType, onConnectDevice, ledgerVM);
} }
class ConnectDevicePageBody extends StatefulWidget { class ConnectDevicePageBody extends StatefulWidget {
@ -43,47 +44,35 @@ class ConnectDevicePageBody extends StatefulWidget {
final OnConnectDevice onConnectDevice; final OnConnectDevice onConnectDevice;
final LedgerViewModel ledgerVM; final LedgerViewModel ledgerVM;
const ConnectDevicePageBody(this.walletType, this.onConnectDevice, this.ledgerVM); const ConnectDevicePageBody(
this.walletType, this.onConnectDevice, this.ledgerVM);
@override @override
ConnectDevicePageBodyState createState() => ConnectDevicePageBodyState(); ConnectDevicePageBodyState createState() => ConnectDevicePageBodyState();
} }
class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> { class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
final imageLedger = 'assets/images/ledger_nano.png';
final ledger = Ledger(
options: LedgerOptions(
scanMode: ScanMode.balanced,
maxScanDuration: const Duration(minutes: 5),
),
onPermissionRequest: (_) async {
Map<Permission, PermissionStatus> statuses = await [
Permission.bluetoothScan,
Permission.bluetoothConnect,
Permission.bluetoothAdvertise,
].request();
return statuses.values.where((status) => status.isDenied).isEmpty;
},
);
var bleIsEnabled = true;
var bleDevices = <LedgerDevice>[]; var bleDevices = <LedgerDevice>[];
var usbDevices = <LedgerDevice>[]; var usbDevices = <LedgerDevice>[];
late Timer? _usbRefreshTimer = null; late Timer? _usbRefreshTimer = null;
late Timer? _bleRefreshTimer = null; late Timer? _bleRefreshTimer = null;
late Timer? _bleStateTimer = null;
late StreamSubscription<LedgerDevice>? _bleRefresh = null; late StreamSubscription<LedgerDevice>? _bleRefresh = null;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
_bleRefreshTimer = Timer.periodic(Duration(seconds: 1), (_) => _refreshBleDevices()); _bleStateTimer = Timer.periodic(
Duration(seconds: 1), (_) => widget.ledgerVM.updateBleState());
_bleRefreshTimer =
Timer.periodic(Duration(seconds: 1), (_) => _refreshBleDevices());
if (Platform.isAndroid) { if (Platform.isAndroid) {
_usbRefreshTimer = Timer.periodic(Duration(seconds: 1), (_) => _refreshUsbDevices()); _usbRefreshTimer =
Timer.periodic(Duration(seconds: 1), (_) => _refreshUsbDevices());
} }
}); });
} }
@ -91,35 +80,59 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
@override @override
void dispose() { void dispose() {
_bleRefreshTimer?.cancel(); _bleRefreshTimer?.cancel();
_bleStateTimer?.cancel();
_usbRefreshTimer?.cancel(); _usbRefreshTimer?.cancel();
_bleRefresh?.cancel(); _bleRefresh?.cancel();
super.dispose(); super.dispose();
} }
Future<void> _refreshUsbDevices() async { Future<void> _refreshUsbDevices() async {
final dev = await ledger.listUsbDevices(); final dev = await widget.ledgerVM.ledgerPlusUSB.devices;
if (usbDevices.length != dev.length) setState(() => usbDevices = dev); if (usbDevices.length != dev.length) setState(() => usbDevices = dev);
// _usbRefresh = widget.ledgerVM
// .scanForUsbDevices()
// .listen((device) => setState(() => usbDevices.add(device)))
// ..onError((e) {
// throw e.toString();
// });
// Keep polling until the lfp lib gets updated
// _usbRefreshTimer?.cancel();
// _usbRefreshTimer = null;
} }
Future<void> _refreshBleDevices() async { Future<void> _refreshBleDevices() async {
try { try {
_bleRefresh = ledger.scan().listen((device) => setState(() => bleDevices.add(device))) _bleRefresh = widget.ledgerVM
.scanForBleDevices()
.listen((device) => setState(() => bleDevices.add(device)))
..onError((e) { ..onError((e) {
throw e.toString(); throw e.toString();
}); });
setState(() => bleIsEnabled = true);
_bleRefreshTimer?.cancel(); _bleRefreshTimer?.cancel();
_bleRefreshTimer = null; _bleRefreshTimer = null;
} catch (e) { } catch (e) {
setState(() => bleIsEnabled = false); print(e);
} }
} }
Future<void> _connectToDevice(LedgerDevice device) async { Future<void> _connectToDevice(LedgerDevice device) async {
await widget.ledgerVM.connectLedger(device); await widget.ledgerVM.connectLedger(device, widget.walletType);
widget.onConnectDevice(context, widget.ledgerVM); widget.onConnectDevice(context, widget.ledgerVM);
} }
String _getDeviceTileLeading(LedgerDeviceType deviceInfo) {
switch (deviceInfo) {
case LedgerDeviceType.nanoX:
return 'assets/images/hardware_wallet/ledger_nano_x.png';
case LedgerDeviceType.stax:
return 'assets/images/hardware_wallet/ledger_stax.png';
case LedgerDeviceType.flex:
return 'assets/images/hardware_wallet/ledger_flex.png';
default:
return 'assets/images/hardware_wallet/ledger_nano_x.png';
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Center( return Center(
@ -139,7 +152,9 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor), color: Theme.of(context)
.extension<CakeTextTheme>()!
.titleColor),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
), ),
@ -152,18 +167,25 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
// title: "Debug Ledger", // title: "Debug Ledger",
// leading: imageLedger, // leading: imageLedger,
// ), // ),
if (!bleIsEnabled) Observer(
Padding( builder: (_) => Offstage(
padding: EdgeInsets.only(left: 20, right: 20, bottom: 20), offstage: widget.ledgerVM.bleIsEnabled,
child: Text( child: Padding(
S.of(context).ledger_please_enable_bluetooth, padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
style: TextStyle( child: Text(
fontSize: 16, S.of(context).ledger_please_enable_bluetooth,
fontWeight: FontWeight.w500, style: TextStyle(
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor), fontSize: 16,
textAlign: TextAlign.center, fontWeight: FontWeight.w500,
color: Theme.of(context)
.extension<CakeTextTheme>()!
.titleColor),
textAlign: TextAlign.center,
),
), ),
), ),
),
if (bleDevices.length > 0) ...[ if (bleDevices.length > 0) ...[
Padding( Padding(
padding: EdgeInsets.only(left: 20, right: 20, bottom: 20), padding: EdgeInsets.only(left: 20, right: 20, bottom: 20),
@ -174,7 +196,9 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor, color: Theme.of(context)
.extension<CakeTextTheme>()!
.titleColor,
), ),
), ),
), ),
@ -186,7 +210,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
child: DeviceTile( child: DeviceTile(
onPressed: () => _connectToDevice(device), onPressed: () => _connectToDevice(device),
title: device.name, title: device.name,
leading: imageLedger, leading: _getDeviceTileLeading(device.deviceInfo),
connectionType: device.connectionType, connectionType: device.connectionType,
), ),
), ),
@ -203,7 +227,9 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor, color: Theme.of(context)
.extension<CakeTextTheme>()!
.titleColor,
), ),
), ),
), ),
@ -215,7 +241,7 @@ class ConnectDevicePageBodyState extends State<ConnectDevicePageBody> {
child: DeviceTile( child: DeviceTile(
onPressed: () => _connectToDevice(device), onPressed: () => _connectToDevice(device),
title: device.name, title: device.name,
leading: imageLedger, leading: _getDeviceTileLeading(device.deviceInfo),
connectionType: device.connectionType, connectionType: device.connectionType,
), ),
), ),

View file

@ -1,15 +1,15 @@
// import 'dart:convert'; // import 'dart:typed_data';
// //
// import 'package:basic_utils/basic_utils.dart';
// import 'package:bitcoin_base/bitcoin_base.dart';
// import 'package:cake_wallet/src/screens/base_page.dart'; // import 'package:cake_wallet/src/screens/base_page.dart';
// import 'package:cake_wallet/src/screens/connect_device/widgets/device_tile.dart'; // import 'package:cake_wallet/src/screens/connect_device/widgets/device_tile.dart';
// import 'package:cake_wallet/src/widgets/primary_button.dart'; // import 'package:cake_wallet/src/widgets/primary_button.dart';
// import 'package:cake_wallet/utils/responsive_layout_util.dart'; // import 'package:cake_wallet/utils/responsive_layout_util.dart';
// import 'package:convert/convert.dart';
// import 'package:flutter/material.dart'; // import 'package:flutter/material.dart';
// import 'package:ledger_bitcoin/ledger_bitcoin.dart'; // import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
// import 'package:ledger_flutter/ledger_flutter.dart'; // import 'package:ledger_litecoin/ledger_litecoin.dart';
// import 'package:permission_handler/permission_handler.dart'; // import 'package:permission_handler/permission_handler.dart';
// import 'package:polyseed/polyseed.dart';
// //
// class DebugDevicePage extends BasePage { // class DebugDevicePage extends BasePage {
// @override // @override
@ -50,7 +50,9 @@
// }, // },
// ); // );
// //
// late BitcoinLedgerApp btc; // // late BitcoinLedgerApp btc;
// late LitecoinLedgerApp ltc;
//
// var devices = <LedgerDevice>[]; // var devices = <LedgerDevice>[];
// var status = ""; // var status = "";
// var counter = 0; // var counter = 0;
@ -59,7 +61,8 @@
// @override // @override
// void initState() { // void initState() {
// super.initState(); // super.initState();
// btc = BitcoinLedgerApp(ledger); // // btc = BitcoinLedgerApp(ledger);
// ltc = LitecoinLedgerApp(ledger);
// } // }
// //
// @override // @override
@ -81,7 +84,7 @@
// //
// @override // @override
// Widget build(BuildContext context) { // Widget build(BuildContext context) {
// final imageLedger = 'assets/images/ledger_nano.png'; // final imageLedger = 'assets/images/hardware_wallet/ledger_nano_x.png';
// //
// return Center( // return Center(
// child: Container( // child: Container(
@ -99,40 +102,25 @@
// DebugButton( // DebugButton(
// title: "Get Version", // title: "Get Version",
// method: "Version", // method: "Version",
// func: () async => await btc.getVersion(selectedDevice!), // // func: () async => await btc.getVersion(selectedDevice!),
// ), // func: () async => await ltc.getVersion(selectedDevice!),
// DebugButton(
// title: "Get Master Fingerprint",
// method: "Master Fingerprint",
// func: () async => hex.encode(await btc.getMasterFingerprint(selectedDevice!)),
// ),
// DebugButton(
// title: "Get XPub",
// method: "XPub",
// func: () async => await btc.getXPubKey(selectedDevice!, derivationPath: "m/84'/0'/$counter'"),
// ), // ),
// DebugButton( // DebugButton(
// title: "Get Wallet Address", // title: "Get Wallet Address",
// method: "Wallet Address", // method: "Wallet Address",
// func: () async { // func: () async {
// setState(() => counter++); // setState(() => counter++);
// final derivationPath = "m/84'/0'/$counter'/0/0"; // final derivationPath = "m/84'/2'/0'/0/0";
// return await btc.getAccounts(selectedDevice!, accountsDerivationPath: derivationPath); // return await ltc.getAccounts(selectedDevice!,
// accountsDerivationPath: derivationPath);
// // return await btc.getAccounts(selectedDevice!, accountsDerivationPath: derivationPath);
// // return await ethereum!.getHardwareWalletAccounts(selectedDevice!); // // return await ethereum!.getHardwareWalletAccounts(selectedDevice!);
// }, // },
// ), // ),
// DebugButton( // DebugButton(
// title: "Send Money", // title: "Send Money",
// method: "Sig", // method: "Raw Tx",
// func: () async { // func: sendMoney
// final psbt = PsbtV2();
// final psbtBuf = base64.decode(
// "cHNidP8BAgQCAAAAAQQBAQEFAQIAAQ4gTW6k/cwKKu1u7m9oKr5ob7VcAC0IPkfaDitRi/FkD7sBDwQAAAAAARAE/////wEA/ekBAQAAAAABA9AYVQLI722H0osKMa/4dvMucrnKV1Myxtlp0l0BoOBDAQAAAAD/////ku6r2ABaHt9N26f/P4eMljX8t1f4lBcFfEwuNm/uXYoBAAAAAP////+YeAl8arEGKOcyrWJAYwSboyCstkhHN8zn7/vy7pkYTAEAAAAA/////wHlHgAAAAAAABYAFKdq0umSucBGVkl2MpT6Hgo/0a/xAkcwRAIgMkiJmNFbEi2I3CQYOwyV/JepCnFQRvj4xghkySpFcJMCIGAypkkWltfj+ucvqUIu27tusDAIAAB+rBhX/GV7hPlEASEDyLmWyTLjLfC9kn8pnW42jW5N6EJo5fObjWWEyfLDu9UCSDBFAiEAg9crVtwBPF+sWk+Th6pLwzDjJGItwsUCvoBPtmMTEb4CIDGuM7WOguV0TP21oidF3bSUZlEAjUHWfWzxLKw+3LofASEDfN16xKb70UZSeQyX5Tlh8iRq7np5Nlz9GYdcSU50sKwCSDBFAiEAvotOblaEiBptRWkvb6bj2MGyRjTphKLBLiHYmrRMTCgCIEKJH+z65uPSSz1NIb0d/u3bU9l0xcWk0idEsXjB+BIiASEDrAEiEtrSNKxbh6F/KPaCTafF2LVjCzb75WB+x4xSuoQAAAAAAQEf5R4AAAAAAAAWABSnatLpkrnARlZJdjKU+h4KP9Gv8SIGA3xMuxmPsBAm9aMEUBs3N46DB+Kdts3bZR/Wxt+uM0H4GKtN6bpUAACAAAAAgAAAAIAAAAAAAAAAAAABBBTk7bEOxYcdXDi1eeWraYDufm6eJgEDCOgDAAAAAAAAAAEEFDX3g/pnDXIfsRw8shK42NZn+SdpAQMIiBMAAAAAAAAiAgN8TLsZj7AQJvWjBFAbNzeOgwfinbbN22Uf1sbfrjNB+BirTem6VAAAgAAAAIAAAACAAAAAAAAAAAAA"
// );
// psbt.deserialize(psbtBuf);
// final result = await btc.signPsbt(selectedDevice!, psbt: psbt);
// return result.toHexString();
// },
// ), // ),
// Padding( // Padding(
// padding: EdgeInsets.only(top: 20), // padding: EdgeInsets.only(top: 20),
@ -147,18 +135,18 @@
// ...devices // ...devices
// .map( // .map(
// (device) => Padding( // (device) => Padding(
// padding: EdgeInsets.only(bottom: 20), // padding: EdgeInsets.only(bottom: 20),
// child: DeviceTile( // child: DeviceTile(
// onPressed: () { // onPressed: () {
// setState(() => selectedDevice = device); // setState(() => selectedDevice = device);
// ledger.connect(device); // ledger.connect(device);
// }, // },
// title: device.name, // title: device.name,
// leading: imageLedger, // leading: imageLedger,
// connectionType: device.connectionType, // connectionType: device.connectionType,
// ), // ),
// ), // ),
// ) // )
// .toList(), // .toList(),
// PrimaryButton( // PrimaryButton(
// text: "Refresh BLE", // text: "Refresh BLE",
@ -188,6 +176,42 @@
// ); // );
// } // }
// //
// Future<String> sendMoney() async {
// final readyInputs = [
// LedgerTransaction(
// rawTx: "010000000001018c055c85c3724c98842d27712771dd0de139711f5940bba2df4615c5522184740000000017160014faf7f6dfb4e70798b92c93f33b4c51024491829df0ffffff022b05c70000000000160014f489f947fd13a1fb44ac168427081d3f30b6ce0cde9dd82e0000000017a914d5eca376cb49d65031220ff9093b7d407073ed0d8702483045022100f648c9f6a9b8f35b6ec29bbfae312c95ed3d56ce6a3f177d994efe90562ec4bd02205b82ce2c94bc0c9d152c3afc668b200bd82f48d6a14e83c66ba0f154cd5f69190121038f1dca119420d4aa7ad04af1c0d65304723789cccc56d335b18692390437f35900000000",
// outputIndex: 0,
// ownerPublicKey:
// HexUtils.decode("03b2e67958ed3356e329e05cf94c3bee6b20c17175ac3b2a1278e073bf44f5d6ec"),
// ownerDerivationPath: "m/84'/2'/0'/0/0",
// sequence: 0xffffffff,
// )
// ];
//
// final outputs = [
// BitcoinOutput(
// address: P2wpkhAddress.fromAddress(
// address: "ltc1qn0g5e36xaj07lqj6w9xn52ng07hud42g3jf5ps",
// network: LitecoinNetwork.mainnet),
// value: BigInt.from(1000000)),
// BitcoinOutput(
// address: P2wpkhAddress.fromAddress(
// address: "ltc1qrx29qz4ghu4j0xk37ptgk7034cwpmjyxhrcnk9",
// network: LitecoinNetwork.mainnet),
// value: BigInt.from(12042705)),
// ];
// return await ltc.createTransaction(selectedDevice!,
// inputs: readyInputs,
// outputs: outputs
// .map((e) => TransactionOutput.fromBigInt(
// e.value, Uint8List.fromList(e.address.toScriptPubKey().toBytes())))
// .toList(),
// sigHashType: 0x01,
// additionals: ["bech32"],
// isSegWit: true,
// useTrustedInputForSegwit: true);
// }
//
// Widget DebugButton( // Widget DebugButton(
// {required String title, required String method, required Future<dynamic> Function() func}) { // {required String title, required String method, required Future<dynamic> Function() func}) {
// return Padding( // return Padding(

View file

@ -1,6 +1,6 @@
import 'package:cake_wallet/themes/extensions/option_tile_theme.dart'; import 'package:cake_wallet/themes/extensions/option_tile_theme.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:ledger_flutter/ledger_flutter.dart'; import 'package:ledger_flutter_plus/ledger_flutter_plus.dart';
class DeviceTile extends StatelessWidget { class DeviceTile extends StatelessWidget {
const DeviceTile({ const DeviceTile({

View file

@ -19,11 +19,12 @@ import 'package:cake_wallet/themes/extensions/balance_page_theme.dart';
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart'; import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
import 'package:cake_wallet/themes/extensions/sync_indicator_theme.dart'; import 'package:cake_wallet/themes/extensions/sync_indicator_theme.dart';
import 'package:cake_wallet/utils/feature_flag.dart'; import 'package:cake_wallet/utils/feature_flag.dart';
import 'package:cake_wallet/utils/payment_request.dart';
import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/nft_view_model.dart';
import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/crypto_currency.dart';
import 'package:flutter/cupertino.dart'; import 'package:cw_core/unspent_coin_type.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
@ -187,7 +188,7 @@ class CryptoBalanceWidget extends StatelessWidget {
Padding( Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Image.asset( child: Image.asset(
'assets/images/ledger_nano.png', 'assets/images/hardware_wallet/ledger_nano_x.png',
width: 24, width: 24,
color: Theme.of(context) color: Theme.of(context)
.extension<DashboardPageTheme>()! .extension<DashboardPageTheme>()!
@ -837,216 +838,293 @@ class BalanceRowWidget extends StatelessWidget {
color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor, color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
), ),
child: Container( child: Container(
margin: const EdgeInsets.only(top: 0, left: 24, right: 8, bottom: 16),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Stack( Container(
children: [ margin: const EdgeInsets.only(top: 16, left: 24, right: 8, bottom: 16),
if (currency == CryptoCurrency.ltc) child: Stack(
Row( children: [
mainAxisAlignment: MainAxisAlignment.end, if (currency == CryptoCurrency.ltc)
children: [ Row(
Container( mainAxisAlignment: MainAxisAlignment.end,
padding: EdgeInsets.only(right: 16, top: 16), children: [
child: Column( Container(
children: [ padding: EdgeInsets.only(right: 16, top: 0),
Container( child: Column(
decoration: BoxDecoration( children: [
color: Colors.white, Container(
shape: BoxShape.circle, child: ImageIcon(
AssetImage('assets/images/mweb_logo.png'),
color: Theme.of(context)
.extension<BalancePageTheme>()!
.assetTitleColor,
size: 40,
),
), ),
child: ImageIcon( const SizedBox(height: 10),
AssetImage('assets/images/mweb_logo.png'), Text(
color: Color.fromARGB(255, 11, 70, 129), 'MWEB',
size: 40, style: TextStyle(
fontSize: 15,
fontFamily: 'Lato',
fontWeight: FontWeight.w800,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.assetTitleColor,
height: 1,
),
),
],
),
),
],
),
if (hasSecondAvailableBalance)
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'${secondAvailableBalanceLabel}',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.labelTextColor,
height: 1,
), ),
), ),
const SizedBox(height: 10), SizedBox(height: 8),
Text( AutoSizeText(
'MWEB', secondAvailableBalance,
style: TextStyle( style: TextStyle(
fontSize: 15, fontSize: 24,
fontFamily: 'Lato', fontFamily: 'Lato',
fontWeight: FontWeight.w800, fontWeight: FontWeight.w900,
color: Theme.of(context) color: Theme.of(context)
.extension<BalancePageTheme>()! .extension<BalancePageTheme>()!
.assetTitleColor, .assetTitleColor,
height: 1, height: 1,
), ),
maxLines: 1,
textAlign: TextAlign.center,
), ),
SizedBox(height: 6),
if (!isTestnet)
Text(
'${secondAvailableFiatBalance}',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
fontFamily: 'Lato',
fontWeight: FontWeight.w500,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.textColor,
height: 1,
),
),
], ],
), ),
), ],
], ),
), ],
if (hasSecondAvailableBalance) ),
Row( ),
children: [ Container(
Column( margin: const EdgeInsets.only(top: 0, left: 24, right: 8, bottom: 16),
crossAxisAlignment: CrossAxisAlignment.start, child: Stack(
children: [ children: [
SizedBox(height: 24), if (hasSecondAdditionalBalance)
Text( Row(
'${secondAvailableBalanceLabel}', children: [
textAlign: TextAlign.center, Column(
style: TextStyle( crossAxisAlignment: CrossAxisAlignment.start,
fontSize: 12, children: [
fontFamily: 'Lato', SizedBox(height: 24),
fontWeight: FontWeight.w400,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.labelTextColor,
height: 1,
),
),
SizedBox(height: 8),
AutoSizeText(
secondAvailableBalance,
style: TextStyle(
fontSize: 20,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.assetTitleColor,
height: 1,
),
maxLines: 1,
textAlign: TextAlign.center,
),
SizedBox(height: 4),
if (!isTestnet)
Text( Text(
'${secondAvailableFiatBalance}', '${secondAdditionalBalanceLabel}',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
fontFamily: 'Lato', fontFamily: 'Lato',
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
color: color: Theme.of(context)
Theme.of(context).extension<BalancePageTheme>()!.textColor, .extension<BalancePageTheme>()!
.labelTextColor,
height: 1, height: 1,
), ),
), ),
], SizedBox(height: 8),
), AutoSizeText(
], secondAdditionalBalance,
),
],
),
Stack(
children: [
if (hasSecondAdditionalBalance)
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 24),
Text(
'${secondAdditionalBalanceLabel}',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.labelTextColor,
height: 1,
),
),
SizedBox(height: 8),
AutoSizeText(
secondAdditionalBalance,
style: TextStyle(
fontSize: 20,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.assetTitleColor,
height: 1,
),
maxLines: 1,
textAlign: TextAlign.center,
),
SizedBox(height: 4),
if (!isTestnet)
Text(
'${secondAdditionalFiatBalance}',
textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 20,
fontFamily: 'Lato', fontFamily: 'Lato',
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
color: color: Theme.of(context)
Theme.of(context).extension<BalancePageTheme>()!.textColor, .extension<BalancePageTheme>()!
.assetTitleColor,
height: 1, height: 1,
), ),
maxLines: 1,
textAlign: TextAlign.center,
), ),
], SizedBox(height: 4),
), if (!isTestnet)
], Text(
), '${secondAdditionalFiatBalance}',
// TODO: smarter peg in / out buttons textAlign: TextAlign.center,
// if (currency == CryptoCurrency.ltc) style: TextStyle(
// Row( fontSize: 12,
// mainAxisAlignment: MainAxisAlignment.end, fontFamily: 'Lato',
// children: [ fontWeight: FontWeight.w400,
// Container( color: Theme.of(context)
// margin: EdgeInsets.only(top: 24, right: 8), .extension<BalancePageTheme>()!
// child: ElevatedButton( .textColor,
// style: ElevatedButton.styleFrom( height: 1,
// backgroundColor: Theme.of(context).highlightColor, ),
// ), ),
// onPressed: () { ],
// final mwebAddress = ),
// bitcoin!.getUnusedMwebAddress(dashboardViewModel.wallet); ],
// if (mwebAddress == null) return; ),
// final paymentRequest = ],
// PaymentRequest.fromUri(Uri.parse("litecoin:${mwebAddress}")); ),
// Navigator.of(context)
// .pushNamed(Routes.send, arguments: paymentRequest);
// },
// child: Container(
// color: Colors.transparent,
// margin: EdgeInsets.all(4),
// child: Column(
// mainAxisSize: MainAxisSize.max,
// crossAxisAlignment: CrossAxisAlignment.center,
// children: <Widget>[
// Container(
// alignment: Alignment.center,
// decoration: BoxDecoration(shape: BoxShape.circle),
// child: Image.asset(
// 'assets/images/received.png',
// color: Theme.of(context)
// .extension<BalancePageTheme>()!
// .balanceAmountColor,
// width: 64,
// height: 32,
// ),
// ),
// SizedBox(height: 4),
// Text(
// S.of(context).litecoin_mweb_pegin,
// style: TextStyle(
// fontSize: 10,
// color: Theme.of(context)
// .extension<DashboardPageTheme>()!
// .cardTextColor),
// )
// ],
// ),
// ),
// ),
// ),
// ],
// ),
],
), ),
IntrinsicHeight(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Semantics(
label: S.of(context).litecoin_mweb_pegin,
child: OutlinedButton(
onPressed: () {
final mwebAddress =
bitcoin!.getUnusedMwebAddress(dashboardViewModel.wallet);
PaymentRequest? paymentRequest = null;
if ((mwebAddress?.isNotEmpty ?? false)) {
paymentRequest =
PaymentRequest.fromUri(Uri.parse("litecoin:${mwebAddress}"));
}
Navigator.pushNamed(
context,
Routes.send,
arguments: {
'paymentRequest': paymentRequest,
'coinTypeToSpendFrom': UnspentCoinType.nonMweb,
},
);
},
style: OutlinedButton.styleFrom(
backgroundColor: Colors.grey.shade400
.withAlpha(50),
side: BorderSide(color: Colors.grey.shade400
.withAlpha(50), width: 0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
),
child: Container(
padding: EdgeInsets.symmetric(vertical: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
height: 30,
width: 30,
'assets/images/received.png',
color: Theme.of(context)
.extension<BalancePageTheme>()!
.balanceAmountColor,
),
const SizedBox(width: 8),
Text(
S.of(context).litecoin_mweb_pegin,
style: TextStyle(
color: Theme.of(context)
.extension<BalancePageTheme>()!
.textColor,
),
),
],
),
),
),
),
),
SizedBox(width: 24),
Expanded(
child: Semantics(
label: S.of(context).litecoin_mweb_pegout,
child: OutlinedButton(
onPressed: () {
final litecoinAddress =
bitcoin!.getUnusedSegwitAddress(dashboardViewModel.wallet);
PaymentRequest? paymentRequest = null;
if ((litecoinAddress?.isNotEmpty ?? false)) {
paymentRequest = PaymentRequest.fromUri(
Uri.parse("litecoin:${litecoinAddress}"));
}
Navigator.pushNamed(
context,
Routes.send,
arguments: {
'paymentRequest': paymentRequest,
'coinTypeToSpendFrom': UnspentCoinType.mweb,
},
);
},
style: OutlinedButton.styleFrom(
backgroundColor: Colors.grey.shade400
.withAlpha(50),
side: BorderSide(color: Colors.grey.shade400
.withAlpha(50), width: 0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
),
child: Container(
padding: EdgeInsets.symmetric(vertical: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Image.asset(
height: 30,
width: 30,
'assets/images/upload.png',
color: Theme.of(context)
.extension<BalancePageTheme>()!
.balanceAmountColor,
),
const SizedBox(width: 8),
Text(
S.of(context).litecoin_mweb_pegout,
style: TextStyle(
color: Theme.of(context)
.extension<BalancePageTheme>()!
.textColor,
),
),
],
),
),
),
),
),
],
),
),
),
SizedBox(height: 16),
], ],
), ),
), ),

View file

@ -67,7 +67,7 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
final mainImageColor = Theme.of(context).extension<DashboardPageTheme>()!.pageTitleTextColor; final mainImageColor = Theme.of(context).extension<DashboardPageTheme>()!.pageTitleTextColor;
final brightImageColor = Theme.of(context).extension<InfoTheme>()!.textColor; final brightImageColor = Theme.of(context).extension<InfoTheme>()!.textColor;
final imageColor = widget.themeType == ThemeType.bright ? brightImageColor : mainImageColor; final imageColor = widget.themeType == ThemeType.bright ? brightImageColor : mainImageColor;
final imageLedger = Image.asset('assets/images/ledger_nano.png', width: 40, color: imageColor); final imageLedger = Image.asset('assets/images/hardware_wallet/ledger_nano_x.png', width: 40, color: imageColor);
final imageSeedKeys = Image.asset('assets/images/restore_wallet_image.png', color: imageColor); final imageSeedKeys = Image.asset('assets/images/restore_wallet_image.png', color: imageColor);
final imageBackup = Image.asset('assets/images/backup.png', color: imageColor); final imageBackup = Image.asset('assets/images/backup.png', color: imageColor);
@ -186,4 +186,4 @@ class _RestoreOptionsBodyState extends State<_RestoreOptionsBody> {
} }
} }
} }
} }

View file

@ -28,6 +28,7 @@ import 'package:cake_wallet/utils/request_review_handler.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/send/output.dart'; import 'package:cake_wallet/view_model/send/output.dart';
import 'package:cw_core/unspent_coin_type.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/view_model/send/send_view_model.dart'; import 'package:cake_wallet/view_model/send/send_view_model.dart';
import 'package:cake_wallet/view_model/send/send_view_model_state.dart'; import 'package:cake_wallet/view_model/send/send_view_model_state.dart';
@ -394,16 +395,19 @@ class SendPage extends BasePage {
if (sendViewModel.wallet.isHardwareWallet) { if (sendViewModel.wallet.isHardwareWallet) {
if (!sendViewModel.ledgerViewModel!.isConnected) { if (!sendViewModel.ledgerViewModel!.isConnected) {
await Navigator.of(context).pushNamed(Routes.connectDevices, await Navigator.of(context).pushNamed(
Routes.connectDevices,
arguments: ConnectDevicePageParams( arguments: ConnectDevicePageParams(
walletType: sendViewModel.walletType, walletType: sendViewModel.walletType,
onConnectDevice: (BuildContext context, _) { onConnectDevice: (BuildContext context, _) {
sendViewModel.ledgerViewModel!.setLedger(sendViewModel.wallet); sendViewModel.ledgerViewModel!
.setLedger(sendViewModel.wallet);
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
)); ));
} else { } else {
sendViewModel.ledgerViewModel!.setLedger(sendViewModel.wallet); sendViewModel.ledgerViewModel!
.setLedger(sendViewModel.wallet);
} }
} }
@ -509,6 +513,10 @@ class SendPage extends BasePage {
newContactAddress = newContactAddress =
newContactAddress ?? sendViewModel.newContactAddress(); newContactAddress ?? sendViewModel.newContactAddress();
if (sendViewModel.coinTypeToSpendFrom != UnspentCoinType.any) {
newContactAddress = null;
}
final successMessage = S.of(_dialogContext).send_success( final successMessage = S.of(_dialogContext).send_success(
sendViewModel.selectedCryptoCurrency.toString()); sendViewModel.selectedCryptoCurrency.toString());

View file

@ -14,7 +14,6 @@ import 'package:cake_wallet/view_model/send/output.dart';
import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:keyboard_actions/keyboard_actions.dart'; import 'package:keyboard_actions/keyboard_actions.dart';
@ -373,7 +372,10 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
padding: EdgeInsets.only(top: 6), padding: EdgeInsets.only(top: 6),
child: GestureDetector( child: GestureDetector(
key: ValueKey('send_page_unspent_coin_button_key'), key: ValueKey('send_page_unspent_coin_button_key'),
onTap: () => Navigator.of(context).pushNamed(Routes.unspentCoinsList), onTap: () => Navigator.of(context).pushNamed(
Routes.unspentCoinsList,
arguments: widget.sendViewModel.coinTypeToSpendFrom,
),
child: Container( child: Container(
color: Colors.transparent, color: Colors.transparent,
child: Row( child: Row(

View file

@ -435,7 +435,7 @@ abstract class DashboardViewModelBase with Store {
} }
@computed @computed
bool get hasMweb => wallet.type == WalletType.litecoin && (Platform.isIOS || Platform.isAndroid); bool get hasMweb => wallet.type == WalletType.litecoin && (Platform.isIOS || Platform.isAndroid) && !wallet.isHardwareWallet;
@computed @computed
bool get showMwebCard => hasMweb && settingsStore.mwebCardDisplay && !mwebEnabled; bool get showMwebCard => hasMweb && settingsStore.mwebCardDisplay && !mwebEnabled;

View file

@ -1,3 +1,4 @@
import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart';
@ -9,11 +10,19 @@ import 'package:cake_wallet/wallet_type_utils.dart';
import 'package:cw_core/hardware/device_connection_type.dart'; import 'package:cw_core/hardware/device_connection_type.dart';
import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
import 'package:ledger_flutter/ledger_flutter.dart';
import 'package:ledger_flutter_plus/ledger_flutter_plus.dart' as sdk;
import 'package:mobx/mobx.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
class LedgerViewModel { part 'ledger_view_model.g.dart';
late final Ledger ledger;
class LedgerViewModel = LedgerViewModelBase with _$LedgerViewModel;
abstract class LedgerViewModelBase with Store {
// late final Ledger ledger;
late final sdk.LedgerInterface ledgerPlusBLE;
late final sdk.LedgerInterface ledgerPlusUSB;
bool get _doesSupportHardwareWallets { bool get _doesSupportHardwareWallets {
if (!DeviceInfo.instance.isMobile) { if (!DeviceInfo.instance.isMobile) {
@ -21,53 +30,97 @@ class LedgerViewModel {
} }
if (isMoneroOnly) { if (isMoneroOnly) {
return DeviceConnectionType.supportedConnectionTypes(WalletType.monero, Platform.isIOS) return DeviceConnectionType.supportedConnectionTypes(
WalletType.monero, Platform.isIOS)
.isNotEmpty; .isNotEmpty;
} }
return true; return true;
} }
LedgerViewModel() { LedgerViewModelBase() {
if (_doesSupportHardwareWallets) { if (_doesSupportHardwareWallets) {
ledger = Ledger( reaction((_) => bleIsEnabled, (_) {
options: LedgerOptions( if (bleIsEnabled) _initBLE();
scanMode: ScanMode.balanced, });
maxScanDuration: const Duration(minutes: 5), updateBleState();
),
onPermissionRequest: (_) async {
Map<Permission, PermissionStatus> statuses = await [
Permission.bluetoothScan,
Permission.bluetoothConnect,
Permission.bluetoothAdvertise,
].request();
return statuses.values.where((status) => status.isDenied).isEmpty; if (!Platform.isIOS) {
}, ledgerPlusUSB = sdk.LedgerInterface.usb();
); }
} }
} }
Future<void> connectLedger(LedgerDevice device) async { @observable
await ledger.connect(device); bool bleIsEnabled = false;
if (device.connectionType == ConnectionType.usb) _device = device; bool _bleIsInitialized = false;
Future<void> _initBLE() async {
if (bleIsEnabled && !_bleIsInitialized) {
ledgerPlusBLE = sdk.LedgerInterface.ble(onPermissionRequest: (_) async {
Map<Permission, PermissionStatus> statuses = await [
Permission.bluetoothScan,
Permission.bluetoothConnect,
Permission.bluetoothAdvertise,
].request();
return statuses.values.where((status) => status.isDenied).isEmpty;
});
_bleIsInitialized = true;
}
} }
LedgerDevice? _device; Future<void> updateBleState() async {
final bleState = await sdk.UniversalBle.getBluetoothAvailabilityState();
bool get isConnected => ledger.devices.isNotEmpty || _device != null; final newState = bleState == sdk.AvailabilityState.poweredOn;
LedgerDevice get device => _device ?? ledger.devices.first; if (newState != bleIsEnabled) bleIsEnabled = newState;
}
Stream<sdk.LedgerDevice> scanForBleDevices() => ledgerPlusBLE.scan();
Stream<sdk.LedgerDevice> scanForUsbDevices() => ledgerPlusUSB.scan();
Future<void> connectLedger(sdk.LedgerDevice device, WalletType type) async {
if (isConnected) {
try {
await _connection!.disconnect();
} catch (_) {}
}
final ledger = device.connectionType == sdk.ConnectionType.ble
? ledgerPlusBLE
: ledgerPlusUSB;
if (_connectionChangeListener == null) {
_connectionChangeListener = ledger.deviceStateChanges.listen((event) {
print('Ledger Device State Changed: $event');
if (event == sdk.BleConnectionState.disconnected) {
_connection = null;
_connectionChangeListener?.cancel();
}
});
}
_connection = await ledger.connect(device);
}
StreamSubscription<sdk.BleConnectionState>? _connectionChangeListener;
sdk.LedgerConnection? _connection;
bool get isConnected => _connection != null && !(_connection!.isDisconnected);
sdk.LedgerConnection get connection => _connection!;
void setLedger(WalletBase wallet) { void setLedger(WalletBase wallet) {
switch (wallet.type) { switch (wallet.type) {
case WalletType.bitcoin: case WalletType.bitcoin:
return bitcoin!.setLedger(wallet, ledger, device); case WalletType.litecoin:
return bitcoin!.setLedgerConnection(wallet, connection);
case WalletType.ethereum: case WalletType.ethereum:
return ethereum!.setLedger(wallet, ledger, device); return ethereum!.setLedgerConnection(wallet, connection);
case WalletType.polygon: case WalletType.polygon:
return polygon!.setLedger(wallet, ledger, device); return polygon!.setLedgerConnection(wallet, connection);
default: default:
throw Exception('Unexpected wallet type: ${wallet.type}'); throw Exception('Unexpected wallet type: ${wallet.type}');
} }

View file

@ -65,15 +65,16 @@ class LinkViewModel {
if (isNanoGptLink) { if (isNanoGptLink) {
switch (currentLink?.authority ?? '') { switch (currentLink?.authority ?? '') {
case "exchange": case "exchange":
case "send":
return PaymentRequest.fromUri(currentLink); return PaymentRequest.fromUri(currentLink);
case "send":
return {"paymentRequest": PaymentRequest.fromUri(currentLink)};
case "buy": case "buy":
return true; return true;
} }
} }
if (_isValidPaymentUri) { if (_isValidPaymentUri) {
return PaymentRequest.fromUri(currentLink); return {"paymentRequest": PaymentRequest.fromUri(currentLink)};
} }
return null; return null;

View file

@ -37,7 +37,8 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
spendKey = '', spendKey = '',
wif = '', wif = '',
address = '', address = '',
super(appStore, walletInfoSource, walletCreationService, seedSettingsViewModel, type: type, isRecovery: true); super(appStore, walletInfoSource, walletCreationService, seedSettingsViewModel,
type: type, isRecovery: true);
@observable @observable
int height; int height;
@ -113,7 +114,14 @@ abstract class WalletRestorationFromQRVMBase extends WalletCreationVM with Store
); );
case WalletType.bitcoin: case WalletType.bitcoin:
case WalletType.litecoin: case WalletType.litecoin:
final derivationInfo = (await getDerivationInfoFromQRCredentials(restoreWallet)).first;
final derivationInfoList = await getDerivationInfoFromQRCredentials(restoreWallet);
DerivationInfo derivationInfo;
if (derivationInfoList.isEmpty) {
derivationInfo = getDefaultCreateDerivation()!;
} else {
derivationInfo = derivationInfoList.first;
}
return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials( return bitcoin!.createBitcoinRestoreWalletFromSeedCredentials(
name: name, name: name,
mnemonic: restoreWallet.mnemonicSeed ?? '', mnemonic: restoreWallet.mnemonicSeed ?? '',

View file

@ -20,10 +20,10 @@ import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cw_core/exceptions.dart'; import 'package:cw_core/exceptions.dart';
import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/transaction_priority.dart';
import 'package:cw_core/unspent_coin_type.dart';
import 'package:cake_wallet/view_model/send/output.dart'; import 'package:cake_wallet/view_model/send/output.dart';
import 'package:cake_wallet/view_model/send/send_template_view_model.dart'; import 'package:cake_wallet/view_model/send/send_template_view_model.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:ledger_flutter/ledger_flutter.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:cake_wallet/entities/template.dart'; import 'package:cake_wallet/entities/template.dart';
import 'package:cake_wallet/core/address_validator.dart'; import 'package:cake_wallet/core/address_validator.dart';
@ -67,8 +67,9 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
this.balanceViewModel, this.balanceViewModel,
this.contactListViewModel, this.contactListViewModel,
this.transactionDescriptionBox, this.transactionDescriptionBox,
this.ledgerViewModel, this.ledgerViewModel, {
) : state = InitialExecutionState(), this.coinTypeToSpendFrom = UnspentCoinType.any,
}) : state = InitialExecutionState(),
currencies = appStore.wallet!.balance.keys.toList(), currencies = appStore.wallet!.balance.keys.toList(),
selectedCryptoCurrency = appStore.wallet!.currency, selectedCryptoCurrency = appStore.wallet!.currency,
hasMultipleTokens = isEVMCompatibleChain(appStore.wallet!.type) || hasMultipleTokens = isEVMCompatibleChain(appStore.wallet!.type) ||
@ -97,6 +98,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
ObservableList<Output> outputs; ObservableList<Output> outputs;
final UnspentCoinType coinTypeToSpendFrom;
@action @action
void addOutput() { void addOutput() {
outputs outputs
@ -119,7 +122,17 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
@computed @computed
bool get isBatchSending => outputs.length > 1; bool get isBatchSending => outputs.length > 1;
bool get shouldDisplaySendALL => walletType != WalletType.solana; bool get shouldDisplaySendALL {
if (walletType == WalletType.solana) return false;
if (walletType == WalletType.ethereum && selectedCryptoCurrency == CryptoCurrency.eth)
return false;
if (walletType == WalletType.polygon && selectedCryptoCurrency == CryptoCurrency.matic)
return false;
return true;
}
@computed @computed
String get pendingTransactionFiatAmount { String get pendingTransactionFiatAmount {
@ -217,7 +230,14 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
PendingTransaction? pendingTransaction; PendingTransaction? pendingTransaction;
@computed @computed
String get balance => wallet.balance[selectedCryptoCurrency]!.formattedFullAvailableBalance; String get balance {
if (coinTypeToSpendFrom == UnspentCoinType.mweb) {
return balanceViewModel.balances.values.first.secondAvailableBalance;
} else if (coinTypeToSpendFrom == UnspentCoinType.nonMweb) {
return balanceViewModel.balances.values.first.availableBalance;
}
return wallet.balance[selectedCryptoCurrency]!.formattedFullAvailableBalance;
}
@computed @computed
bool get isFiatDisabled => balanceViewModel.isFiatDisabled; bool get isFiatDisabled => balanceViewModel.isFiatDisabled;
@ -387,16 +407,16 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
state = ExecutedSuccessfullyState(); state = ExecutedSuccessfullyState();
return pendingTransaction; return pendingTransaction;
} catch (e) { } catch (e) {
if (e is LedgerException) { // if (e is LedgerException) {
final errorCode = e.errorCode.toRadixString(16); // final errorCode = e.errorCode.toRadixString(16);
final fallbackMsg = // final fallbackMsg =
e.message.isNotEmpty ? e.message : "Unexpected Ledger Error Code: $errorCode"; // e.message.isNotEmpty ? e.message : "Unexpected Ledger Error Code: $errorCode";
final errorMsg = ledgerViewModel!.interpretErrorCode(errorCode) ?? fallbackMsg; // final errorMsg = ledgerViewModel!.interpretErrorCode(errorCode) ?? fallbackMsg;
//
state = FailureState(errorMsg); // state = FailureState(errorMsg);
} else { // } else {
state = FailureState(translateErrorMessage(e, wallet.type, wallet.currency)); state = FailureState(translateErrorMessage(e, wallet.type, wallet.currency));
} // }
} }
return null; return null;
} }
@ -461,12 +481,18 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
nano!.updateTransactions(wallet); nano!.updateTransactions(wallet);
} }
if (pendingTransaction!.id.isNotEmpty) { if (pendingTransaction!.id.isNotEmpty) {
final descriptionKey = '${pendingTransaction!.id}_${wallet.walletAddresses.primaryAddress}';
_settingsStore.shouldSaveRecipientAddress _settingsStore.shouldSaveRecipientAddress
? await transactionDescriptionBox.add(TransactionDescription( ? await transactionDescriptionBox.add(TransactionDescription(
id: pendingTransaction!.id, recipientAddress: address, transactionNote: note)) id: descriptionKey,
: await transactionDescriptionBox recipientAddress: address,
.add(TransactionDescription(id: pendingTransaction!.id, transactionNote: note)); transactionNote: note))
: await transactionDescriptionBox.add(TransactionDescription(
id: descriptionKey,
transactionNote: note));
} }
state = TransactionCommitted(); state = TransactionCommitted();
@ -494,8 +520,12 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
case WalletType.bitcoin: case WalletType.bitcoin:
case WalletType.litecoin: case WalletType.litecoin:
case WalletType.bitcoinCash: case WalletType.bitcoinCash:
return bitcoin!.createBitcoinTransactionCredentials(outputs, return bitcoin!.createBitcoinTransactionCredentials(
priority: priority!, feeRate: customBitcoinFeeRate); outputs,
priority: priority!,
feeRate: customBitcoinFeeRate,
coinTypeToSpendFrom: coinTypeToSpendFrom,
);
case WalletType.monero: case WalletType.monero:
return monero! return monero!

View file

@ -110,9 +110,11 @@ abstract class TransactionDetailsViewModelBase with Store {
} catch (e) {} } catch (e) {}
})); }));
final descriptionKey = '${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}';
final description = transactionDescriptionBox.values.firstWhere( final description = transactionDescriptionBox.values.firstWhere(
(val) => val.id == transactionInfo.txHash, (val) => val.id == descriptionKey || val.id == transactionInfo.txHash,
orElse: () => TransactionDescription(id: transactionInfo.txHash)); orElse: () => TransactionDescription(id: descriptionKey));
items.add(TextFieldListItem( items.add(TextFieldListItem(
title: S.current.note_tap_to_change, title: S.current.note_tap_to_change,

View file

@ -3,6 +3,7 @@ import 'package:cake_wallet/monero/monero.dart';
import 'package:cake_wallet/utils/exception_handler.dart'; import 'package:cake_wallet/utils/exception_handler.dart';
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart'; import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart';
import 'package:cake_wallet/wownero/wownero.dart'; import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cw_core/unspent_coin_type.dart';
import 'package:cw_core/unspent_coins_info.dart'; import 'package:cw_core/unspent_coins_info.dart';
import 'package:cw_core/unspent_transaction_output.dart'; import 'package:cw_core/unspent_transaction_output.dart';
import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_base.dart';
@ -16,9 +17,11 @@ part 'unspent_coins_list_view_model.g.dart';
class UnspentCoinsListViewModel = UnspentCoinsListViewModelBase with _$UnspentCoinsListViewModel; class UnspentCoinsListViewModel = UnspentCoinsListViewModelBase with _$UnspentCoinsListViewModel;
abstract class UnspentCoinsListViewModelBase with Store { abstract class UnspentCoinsListViewModelBase with Store {
UnspentCoinsListViewModelBase( UnspentCoinsListViewModelBase({
{required this.wallet, required Box<UnspentCoinsInfo> unspentCoinsInfo}) required this.wallet,
: _unspentCoinsInfo = unspentCoinsInfo, required Box<UnspentCoinsInfo> unspentCoinsInfo,
this.coinTypeToSpendFrom = UnspentCoinType.any,
}) : _unspentCoinsInfo = unspentCoinsInfo,
_items = ObservableList<UnspentCoinsItem>() { _items = ObservableList<UnspentCoinsItem>() {
_updateUnspentCoinsInfo(); _updateUnspentCoinsInfo();
_updateUnspents(); _updateUnspents();
@ -26,6 +29,7 @@ abstract class UnspentCoinsListViewModelBase with Store {
WalletBase wallet; WalletBase wallet;
final Box<UnspentCoinsInfo> _unspentCoinsInfo; final Box<UnspentCoinsInfo> _unspentCoinsInfo;
final UnspentCoinType coinTypeToSpendFrom;
@observable @observable
ObservableList<UnspentCoinsItem> _items; ObservableList<UnspentCoinsItem> _items;
@ -103,7 +107,7 @@ abstract class UnspentCoinsListViewModelBase with Store {
case WalletType.bitcoin: case WalletType.bitcoin:
case WalletType.litecoin: case WalletType.litecoin:
case WalletType.bitcoinCash: case WalletType.bitcoinCash:
return bitcoin!.getUnspents(wallet); return bitcoin!.getUnspents(wallet, coinTypeToSpendFrom: coinTypeToSpendFrom);
default: default:
return List.empty(); return List.empty();
} }

View file

@ -1,14 +1,15 @@
import 'dart:math'; import 'dart:developer' as dev;
import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/fiat_conversion_service.dart';
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart'; import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart'; import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
import 'package:cake_wallet/entities/fiat_api_mode.dart';
import 'package:cake_wallet/entities/fiat_currency.dart'; import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/ethereum/ethereum.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/haven/haven.dart'; import 'package:cake_wallet/haven/haven.dart';
import 'package:cake_wallet/monero/monero.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/polygon/polygon.dart';
import 'package:cake_wallet/solana/solana.dart'; import 'package:cake_wallet/solana/solana.dart';
import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/store/app_store.dart';
@ -24,16 +25,14 @@ import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_i
import 'package:cake_wallet/wownero/wownero.dart'; import 'package:cake_wallet/wownero/wownero.dart';
import 'package:cw_core/amount_converter.dart'; import 'package:cw_core/amount_converter.dart';
import 'package:cw_core/currency.dart'; import 'package:cw_core/currency.dart';
import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
import 'package:cw_monero/api/wallet.dart';
import 'package:flutter/foundation.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
part 'wallet_address_list_view_model.g.dart'; part 'wallet_address_list_view_model.g.dart';
class WalletAddressListViewModel = WalletAddressListViewModelBase with _$WalletAddressListViewModel; class WalletAddressListViewModel = WalletAddressListViewModelBase
with _$WalletAddressListViewModel;
abstract class PaymentURI { abstract class PaymentURI {
PaymentURI({required this.amount, required this.address}); PaymentURI({required this.amount, required this.address});
@ -43,12 +42,11 @@ abstract class PaymentURI {
} }
class MoneroURI extends PaymentURI { class MoneroURI extends PaymentURI {
MoneroURI({required String amount, required String address}) MoneroURI({required super.amount, required super.address});
: super(amount: amount, address: address);
@override @override
String toString() { String toString() {
var base = 'monero:' + address; var base = 'monero:$address';
if (amount.isNotEmpty) { if (amount.isNotEmpty) {
base += '?tx_amount=${amount.replaceAll(',', '.')}'; base += '?tx_amount=${amount.replaceAll(',', '.')}';
@ -59,12 +57,11 @@ class MoneroURI extends PaymentURI {
} }
class HavenURI extends PaymentURI { class HavenURI extends PaymentURI {
HavenURI({required String amount, required String address}) HavenURI({required super.amount, required super.address});
: super(amount: amount, address: address);
@override @override
String toString() { String toString() {
var base = 'haven:' + address; var base = 'haven:$address';
if (amount.isNotEmpty) { if (amount.isNotEmpty) {
base += '?tx_amount=${amount.replaceAll(',', '.')}'; base += '?tx_amount=${amount.replaceAll(',', '.')}';
@ -75,12 +72,11 @@ class HavenURI extends PaymentURI {
} }
class BitcoinURI extends PaymentURI { class BitcoinURI extends PaymentURI {
BitcoinURI({required String amount, required String address}) BitcoinURI({required super.amount, required super.address});
: super(amount: amount, address: address);
@override @override
String toString() { String toString() {
var base = 'bitcoin:' + address; var base = 'bitcoin:$address';
if (amount.isNotEmpty) { if (amount.isNotEmpty) {
base += '?amount=${amount.replaceAll(',', '.')}'; base += '?amount=${amount.replaceAll(',', '.')}';
@ -91,12 +87,11 @@ class BitcoinURI extends PaymentURI {
} }
class LitecoinURI extends PaymentURI { class LitecoinURI extends PaymentURI {
LitecoinURI({required String amount, required String address}) LitecoinURI({required super.amount, required super.address});
: super(amount: amount, address: address);
@override @override
String toString() { String toString() {
var base = 'litecoin:' + address; var base = 'litecoin:$address';
if (amount.isNotEmpty) { if (amount.isNotEmpty) {
base += '?amount=${amount.replaceAll(',', '.')}'; base += '?amount=${amount.replaceAll(',', '.')}';
@ -107,12 +102,11 @@ class LitecoinURI extends PaymentURI {
} }
class EthereumURI extends PaymentURI { class EthereumURI extends PaymentURI {
EthereumURI({required String amount, required String address}) EthereumURI({required super.amount, required super.address});
: super(amount: amount, address: address);
@override @override
String toString() { String toString() {
var base = 'ethereum:' + address; var base = 'ethereum:$address';
if (amount.isNotEmpty) { if (amount.isNotEmpty) {
base += '?amount=${amount.replaceAll(',', '.')}'; base += '?amount=${amount.replaceAll(',', '.')}';
@ -123,8 +117,7 @@ class EthereumURI extends PaymentURI {
} }
class BitcoinCashURI extends PaymentURI { class BitcoinCashURI extends PaymentURI {
BitcoinCashURI({required String amount, required String address}) BitcoinCashURI({required super.amount, required super.address});
: super(amount: amount, address: address);
@override @override
String toString() { String toString() {
@ -139,12 +132,11 @@ class BitcoinCashURI extends PaymentURI {
} }
class NanoURI extends PaymentURI { class NanoURI extends PaymentURI {
NanoURI({required String amount, required String address}) NanoURI({required super.amount, required super.address});
: super(amount: amount, address: address);
@override @override
String toString() { String toString() {
var base = 'nano:' + address; var base = 'nano:$address';
if (amount.isNotEmpty) { if (amount.isNotEmpty) {
base += '?amount=${amount.replaceAll(',', '.')}'; base += '?amount=${amount.replaceAll(',', '.')}';
} }
@ -154,12 +146,11 @@ class NanoURI extends PaymentURI {
} }
class PolygonURI extends PaymentURI { class PolygonURI extends PaymentURI {
PolygonURI({required String amount, required String address}) PolygonURI({required super.amount, required super.address});
: super(amount: amount, address: address);
@override @override
String toString() { String toString() {
var base = 'polygon:' + address; var base = 'polygon:$address';
if (amount.isNotEmpty) { if (amount.isNotEmpty) {
base += '?amount=${amount.replaceAll(',', '.')}'; base += '?amount=${amount.replaceAll(',', '.')}';
@ -170,12 +161,12 @@ class PolygonURI extends PaymentURI {
} }
class SolanaURI extends PaymentURI { class SolanaURI extends PaymentURI {
SolanaURI({required String amount, required String address}) SolanaURI({required super.amount, required super.address});
: super(amount: amount, address: address);
@override @override
String toString() { String toString() {
var base = 'solana:' + address; var base = 'solana:$address';
if (amount.isNotEmpty) { if (amount.isNotEmpty) {
base += '?amount=${amount.replaceAll(',', '.')}'; base += '?amount=${amount.replaceAll(',', '.')}';
} }
@ -185,12 +176,12 @@ class SolanaURI extends PaymentURI {
} }
class TronURI extends PaymentURI { class TronURI extends PaymentURI {
TronURI({required String amount, required String address}) TronURI({required super.amount, required super.address});
: super(amount: amount, address: address);
@override @override
String toString() { String toString() {
var base = 'tron:' + address; var base = 'tron:$address';
if (amount.isNotEmpty) { if (amount.isNotEmpty) {
base += '?amount=${amount.replaceAll(',', '.')}'; base += '?amount=${amount.replaceAll(',', '.')}';
} }
@ -200,12 +191,11 @@ class TronURI extends PaymentURI {
} }
class WowneroURI extends PaymentURI { class WowneroURI extends PaymentURI {
WowneroURI({required String amount, required String address}) WowneroURI({required super.amount, required super.address});
: super(amount: amount, address: address);
@override @override
String toString() { String toString() {
var base = 'wownero:' + address; var base = 'wownero:$address';
if (amount.isNotEmpty) { if (amount.isNotEmpty) {
base += '?tx_amount=${amount.replaceAll(',', '.')}'; base += '?tx_amount=${amount.replaceAll(',', '.')}';
@ -215,7 +205,8 @@ class WowneroURI extends PaymentURI {
} }
} }
abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewModel with Store { abstract class WalletAddressListViewModelBase
extends WalletChangeListenerViewModel with Store {
WalletAddressListViewModelBase({ WalletAddressListViewModelBase({
required AppStore appStore, required AppStore appStore,
required this.yatStore, required this.yatStore,
@ -223,9 +214,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
}) : _baseItems = <ListItem>[], }) : _baseItems = <ListItem>[],
selectedCurrency = walletTypeToCryptoCurrency(appStore.wallet!.type), selectedCurrency = walletTypeToCryptoCurrency(appStore.wallet!.type),
_cryptoNumberFormat = NumberFormat(_cryptoNumberPattern), _cryptoNumberFormat = NumberFormat(_cryptoNumberPattern),
hasAccounts = appStore.wallet!.type == WalletType.monero || hasAccounts = [WalletType.monero, WalletType.wownero, WalletType.haven]
appStore.wallet!.type == WalletType.wownero || .contains(appStore.wallet!.type),
appStore.wallet!.type == WalletType.haven,
amount = '', amount = '',
_settingsStore = appStore.settingsStore, _settingsStore = appStore.settingsStore,
super(appStore: appStore) { super(appStore: appStore) {
@ -237,9 +227,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
_init(); _init();
selectedCurrency = walletTypeToCryptoCurrency(wallet.type); selectedCurrency = walletTypeToCryptoCurrency(wallet.type);
hasAccounts = wallet.type == WalletType.monero || hasAccounts = [WalletType.monero, WalletType.wownero, WalletType.haven]
wallet.type == WalletType.wownero || .contains(wallet.type);
wallet.type == WalletType.haven;
} }
static const String _cryptoNumberPattern = '0.00000000'; static const String _cryptoNumberPattern = '0.00000000';
@ -249,7 +238,11 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
final FiatConversionStore fiatConversionStore; final FiatConversionStore fiatConversionStore;
final SettingsStore _settingsStore; final SettingsStore _settingsStore;
List<Currency> get currencies => [walletTypeToCryptoCurrency(wallet.type), ...FiatCurrency.all]; double? _fiatRate;
String _rawAmount = '';
List<Currency> get currencies =>
[walletTypeToCryptoCurrency(wallet.type), ...FiatCurrency.all];
String get buttonTitle { String get buttonTitle {
if (isElectrumWallet) { if (isElectrumWallet) {
@ -275,9 +268,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
WalletType get type => wallet.type; WalletType get type => wallet.type;
@computed @computed
WalletAddressListItem get address { WalletAddressListItem get address => WalletAddressListItem(
return WalletAddressListItem(address: wallet.walletAddresses.address, isPrimary: false); address: wallet.walletAddresses.address, isPrimary: false);
}
@computed @computed
PaymentURI get uri { PaymentURI get uri {
@ -321,8 +313,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
final addressList = ObservableList<ListItem>(); final addressList = ObservableList<ListItem>();
if (wallet.type == WalletType.monero) { if (wallet.type == WalletType.monero) {
final primaryAddress = monero!.getSubaddressList(wallet).subaddresses.first; final primaryAddress =
final addressItems = monero!.getSubaddressList(wallet).subaddresses.map((subaddress) { monero!.getSubaddressList(wallet).subaddresses.first;
final addressItems =
monero!.getSubaddressList(wallet).subaddresses.map((subaddress) {
final isPrimary = subaddress == primaryAddress; final isPrimary = subaddress == primaryAddress;
return WalletAddressListItem( return WalletAddressListItem(
@ -338,8 +332,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
} }
if (wallet.type == WalletType.wownero) { if (wallet.type == WalletType.wownero) {
final primaryAddress = wownero!.getSubaddressList(wallet).subaddresses.first; final primaryAddress =
final addressItems = wownero!.getSubaddressList(wallet).subaddresses.map((subaddress) { wownero!.getSubaddressList(wallet).subaddresses.first;
final addressItems =
wownero!.getSubaddressList(wallet).subaddresses.map((subaddress) {
final isPrimary = subaddress == primaryAddress; final isPrimary = subaddress == primaryAddress;
return WalletAddressListItem( return WalletAddressListItem(
@ -352,8 +348,10 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
} }
if (wallet.type == WalletType.haven) { if (wallet.type == WalletType.haven) {
final primaryAddress = haven!.getSubaddressList(wallet).subaddresses.first; final primaryAddress =
final addressItems = haven!.getSubaddressList(wallet).subaddresses.map((subaddress) { haven!.getSubaddressList(wallet).subaddresses.first;
final addressItems =
haven!.getSubaddressList(wallet).subaddresses.map((subaddress) {
final isPrimary = subaddress == primaryAddress; final isPrimary = subaddress == primaryAddress;
return WalletAddressListItem( return WalletAddressListItem(
@ -367,7 +365,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
if (isElectrumWallet) { if (isElectrumWallet) {
if (bitcoin!.hasSelectedSilentPayments(wallet)) { if (bitcoin!.hasSelectedSilentPayments(wallet)) {
final addressItems = bitcoin!.getSilentPaymentAddresses(wallet).map((address) { final addressItems =
bitcoin!.getSilentPaymentAddresses(wallet).map((address) {
final isPrimary = address.id == 0; final isPrimary = address.id == 0;
return WalletAddressListItem( return WalletAddressListItem(
@ -418,7 +417,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
if (wallet.type == WalletType.litecoin && addressItems.length >= 1000) { if (wallet.type == WalletType.litecoin && addressItems.length >= 1000) {
// find the index of the last item with a txCount > 0 // find the index of the last item with a txCount > 0
final addressItemsList = addressItems.toList(); final addressItemsList = addressItems.toList();
int index = addressItemsList.lastIndexWhere((item) => (item.txCount ?? 0) > 0); int index = addressItemsList
.lastIndexWhere((item) => (item.txCount ?? 0) > 0);
if (index == -1) { if (index == -1) {
index = 0; index = 0;
} }
@ -432,19 +432,22 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
if (wallet.type == WalletType.ethereum) { if (wallet.type == WalletType.ethereum) {
final primaryAddress = ethereum!.getAddress(wallet); final primaryAddress = ethereum!.getAddress(wallet);
addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress)); addressList.add(WalletAddressListItem(
isPrimary: true, name: null, address: primaryAddress));
} }
if (wallet.type == WalletType.polygon) { if (wallet.type == WalletType.polygon) {
final primaryAddress = polygon!.getAddress(wallet); final primaryAddress = polygon!.getAddress(wallet);
addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress)); addressList.add(WalletAddressListItem(
isPrimary: true, name: null, address: primaryAddress));
} }
if (wallet.type == WalletType.solana) { if (wallet.type == WalletType.solana) {
final primaryAddress = solana!.getAddress(wallet); final primaryAddress = solana!.getAddress(wallet);
addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress)); addressList.add(WalletAddressListItem(
isPrimary: true, name: null, address: primaryAddress));
} }
if (wallet.type == WalletType.nano) { if (wallet.type == WalletType.nano) {
@ -458,18 +461,21 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
if (wallet.type == WalletType.tron) { if (wallet.type == WalletType.tron) {
final primaryAddress = tron!.getAddress(wallet); final primaryAddress = tron!.getAddress(wallet);
addressList.add(WalletAddressListItem(isPrimary: true, name: null, address: primaryAddress)); addressList.add(WalletAddressListItem(
isPrimary: true, name: null, address: primaryAddress));
} }
for (var i = 0; i < addressList.length; i++) { for (var i = 0; i < addressList.length; i++) {
if (!(addressList[i] is WalletAddressListItem)) continue; if (!(addressList[i] is WalletAddressListItem)) continue;
(addressList[i] as WalletAddressListItem).isHidden = wallet.walletAddresses.hiddenAddresses (addressList[i] as WalletAddressListItem).isHidden = wallet
.walletAddresses.hiddenAddresses
.contains((addressList[i] as WalletAddressListItem).address); .contains((addressList[i] as WalletAddressListItem).address);
} }
for (var i = 0; i < addressList.length; i++) { for (var i = 0; i < addressList.length; i++) {
if (!(addressList[i] is WalletAddressListItem)) continue; if (!(addressList[i] is WalletAddressListItem)) continue;
(addressList[i] as WalletAddressListItem).isManual = wallet.walletAddresses.manualAddresses (addressList[i] as WalletAddressListItem).isManual = wallet
.walletAddresses.manualAddresses
.contains((addressList[i] as WalletAddressListItem).address); .contains((addressList[i] as WalletAddressListItem).address);
} }
@ -487,7 +493,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
Future<void> toggleHideAddress(WalletAddressListItem item) async { Future<void> toggleHideAddress(WalletAddressListItem item) async {
if (item.isHidden) { if (item.isHidden) {
wallet.walletAddresses.hiddenAddresses.removeWhere((element) => element == item.address); wallet.walletAddresses.hiddenAddresses
.removeWhere((element) => element == item.address);
} else { } else {
wallet.walletAddresses.hiddenAddresses.add(item.address); wallet.walletAddresses.hiddenAddresses.add(item.address);
} }
@ -512,57 +519,58 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
@computed @computed
String get accountLabel { String get accountLabel {
if (wallet.type == WalletType.monero) { switch (wallet.type) {
return monero!.getCurrentAccount(wallet).label; case WalletType.monero:
return monero!.getCurrentAccount(wallet).label;
case WalletType.wownero:
wownero!.getCurrentAccount(wallet).label;
case WalletType.haven:
return haven!.getCurrentAccount(wallet).label;
default:
return '';
} }
if (wallet.type == WalletType.wownero) {
return wownero!.getCurrentAccount(wallet).label;
}
if (wallet.type == WalletType.haven) {
return haven!.getCurrentAccount(wallet).label;
}
return ''; return '';
} }
@computed @computed
bool get hasAddressList => bool get hasAddressList => [
wallet.type == WalletType.monero || WalletType.monero,
wallet.type == WalletType.wownero || WalletType.wownero,
wallet.type == WalletType.haven || WalletType.haven,
wallet.type == WalletType.bitcoinCash || WalletType.bitcoinCash,
wallet.type == WalletType.bitcoin || WalletType.bitcoin,
wallet.type == WalletType.litecoin; WalletType.litecoin
].contains(wallet.type);
@computed @computed
bool get isElectrumWallet => bool get isElectrumWallet => [
wallet.type == WalletType.bitcoin || WalletType.bitcoin,
wallet.type == WalletType.litecoin || WalletType.litecoin,
wallet.type == WalletType.bitcoinCash; WalletType.bitcoinCash
].contains(wallet.type);
@computed @computed
bool get isBalanceAvailable => isElectrumWallet; bool get isBalanceAvailable => isElectrumWallet;
@computed @computed
bool get isReceivedAvailable => bool get isReceivedAvailable =>
wallet.type == WalletType.monero || wallet.type == WalletType.wownero; [WalletType.monero, WalletType.wownero].contains(wallet.type);
@computed @computed
bool get isSilentPayments => bool get isSilentPayments =>
wallet.type == WalletType.bitcoin && bitcoin!.hasSelectedSilentPayments(wallet); wallet.type == WalletType.bitcoin &&
bitcoin!.hasSelectedSilentPayments(wallet);
@computed @computed
bool get isAutoGenerateSubaddressEnabled => bool get isAutoGenerateSubaddressEnabled =>
_settingsStore.autoGenerateSubaddressStatus != AutoGenerateSubaddressStatus.disabled && _settingsStore.autoGenerateSubaddressStatus !=
AutoGenerateSubaddressStatus.disabled &&
!isSilentPayments; !isSilentPayments;
@computed @computed
bool get showAddManualAddresses => bool get showAddManualAddresses =>
!isAutoGenerateSubaddressEnabled || !isAutoGenerateSubaddressEnabled ||
wallet.type == WalletType.monero || [WalletType.monero, WalletType.wownero].contains(wallet.type);
wallet.type == WalletType.wownero;
List<ListItem> _baseItems; List<ListItem> _baseItems;
@ -574,7 +582,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
@action @action
Future<void> setAddressType(dynamic option) async { Future<void> setAddressType(dynamic option) async {
if (wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin) { if ([WalletType.bitcoin, WalletType.litecoin].contains(wallet.type)) {
await bitcoin!.setAddressType(wallet, option); await bitcoin!.setAddressType(wallet, option);
} }
} }
@ -586,13 +594,15 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
_baseItems.add(WalletAddressHiddenListHeader()); _baseItems.add(WalletAddressHiddenListHeader());
} }
if (wallet.type == WalletType.monero || if ([
wallet.type == WalletType.wownero || WalletType.monero,
wallet.type == WalletType.haven) { WalletType.wownero,
WalletType.haven,
].contains(wallet.type)) {
_baseItems.add(WalletAccountListHeader()); _baseItems.add(WalletAccountListHeader());
} }
if (wallet.type != WalletType.nano && wallet.type != WalletType.banano) { if (![WalletType.nano, WalletType.banano].contains(wallet.type)) {
_baseItems.add(WalletAddressListHeader()); _baseItems.add(WalletAddressListHeader());
} }
if (wallet.isEnabledAutoGenerateSubaddress) { if (wallet.isEnabledAutoGenerateSubaddress) {
@ -603,11 +613,27 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
@action @action
void selectCurrency(Currency currency) { void selectCurrency(Currency currency) {
selectedCurrency = currency; selectedCurrency = currency;
if (currency is FiatCurrency && _settingsStore.fiatCurrency != currency) {
final cryptoCurrency = walletTypeToCryptoCurrency(wallet.type);
dev.log("Requesting Fiat rate for $cryptoCurrency-$currency");
FiatConversionService.fetchPrice(
crypto: cryptoCurrency,
fiat: currency,
torOnly: _settingsStore.fiatApiMode == FiatApiMode.torOnly,
).then((value) {
dev.log("Received Fiat rate 1 $cryptoCurrency = $value $currency");
_fiatRate = value;
_convertAmountToCrypto();
});
}
} }
@action @action
void changeAmount(String amount) { void changeAmount(String amount) {
this.amount = amount; this.amount = amount;
this._rawAmount = amount;
if (selectedCurrency is FiatCurrency) { if (selectedCurrency is FiatCurrency) {
_convertAmountToCrypto(); _convertAmountToCrypto();
} }
@ -618,11 +644,20 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
searchText = text; searchText = text;
} }
@action
void _convertAmountToCrypto() { void _convertAmountToCrypto() {
final cryptoCurrency = walletTypeToCryptoCurrency(wallet.type); final cryptoCurrency = walletTypeToCryptoCurrency(wallet.type);
final fiatRate =
_fiatRate ?? (fiatConversionStore.prices[cryptoCurrency] ?? 0.0);
if (fiatRate <= 0.0) {
dev.log("Invalid Fiat Rate $fiatRate");
amount = '';
return;
}
try { try {
final crypto = final crypto = double.parse(_rawAmount.replaceAll(',', '.')) / fiatRate;
double.parse(amount.replaceAll(',', '.')) / fiatConversionStore.prices[cryptoCurrency]!;
final cryptoAmountTmp = _cryptoNumberFormat.format(crypto); final cryptoAmountTmp = _cryptoNumberFormat.format(crypto);
if (amount != cryptoAmountTmp) { if (amount != cryptoAmountTmp) {
amount = cryptoAmountTmp; amount = cryptoAmountTmp;

View file

@ -115,7 +115,9 @@ abstract class WalletCreationVMBase with Store {
getIt.get<BackgroundTasks>().registerSyncTask(); getIt.get<BackgroundTasks>().registerSyncTask();
_appStore.authenticationStore.allowed(); _appStore.authenticationStore.allowed();
state = ExecutedSuccessfullyState(); state = ExecutedSuccessfullyState();
} catch (e, _) { } catch (e, s) {
print("error: $e");
print("stack: $s");
state = FailureState(e.toString()); state = FailureState(e.toString());
} }
} }
@ -198,11 +200,16 @@ abstract class WalletCreationVMBase with Store {
switch (walletType) { switch (walletType) {
case WalletType.bitcoin: case WalletType.bitcoin:
case WalletType.litecoin: case WalletType.litecoin:
return bitcoin!.getDerivationsFromMnemonic( final derivationList = await bitcoin!.getDerivationsFromMnemonic(
mnemonic: restoreWallet.mnemonicSeed!, mnemonic: restoreWallet.mnemonicSeed!,
node: node, node: node,
passphrase: restoreWallet.passphrase, passphrase: restoreWallet.passphrase,
); );
if (derivationList.firstOrNull?.transactionsCount == 0 && derivationList.length > 1)
return [];
return derivationList;
case WalletType.nano: case WalletType.nano:
return nanoUtil!.getDerivationsFromMnemonic( return nanoUtil!.getDerivationsFromMnemonic(
mnemonic: restoreWallet.mnemonicSeed!, mnemonic: restoreWallet.mnemonicSeed!,

View file

@ -13,7 +13,6 @@ import 'package:cw_core/wallet_credentials.dart';
import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_info.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:ledger_flutter/ledger_flutter.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
part 'wallet_hardware_restore_view_model.g.dart'; part 'wallet_hardware_restore_view_model.g.dart';
@ -58,7 +57,11 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with
switch (type) { switch (type) {
case WalletType.bitcoin: case WalletType.bitcoin:
accounts = await bitcoin! accounts = await bitcoin!
.getHardwareWalletAccounts(ledgerViewModel, index: _nextIndex, limit: limit); .getHardwareWalletBitcoinAccounts(ledgerViewModel, index: _nextIndex, limit: limit);
break;
case WalletType.litecoin:
accounts = await bitcoin!
.getHardwareWalletLitecoinAccounts(ledgerViewModel, index: _nextIndex, limit: limit);
break; break;
case WalletType.ethereum: case WalletType.ethereum:
accounts = await ethereum! accounts = await ethereum!
@ -74,9 +77,10 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with
availableAccounts.addAll(accounts); availableAccounts.addAll(accounts);
_nextIndex += limit; _nextIndex += limit;
} on LedgerException catch (e) { // } on LedgerException catch (e) {
error = ledgerViewModel.interpretErrorCode(e.errorCode.toRadixString(16)); // error = ledgerViewModel.interpretErrorCode(e.errorCode.toRadixString(16));
} catch (e) { } catch (e) {
print(e);
error = S.current.ledger_connection_error; error = S.current.ledger_connection_error;
} }
@ -89,6 +93,7 @@ abstract class WalletHardwareRestoreViewModelBase extends WalletCreationVM with
WalletCredentials credentials; WalletCredentials credentials;
switch (type) { switch (type) {
case WalletType.bitcoin: case WalletType.bitcoin:
case WalletType.litecoin:
credentials = credentials =
bitcoin!.createBitcoinHardwareWalletCredentials(name: name, accountData: selectedAccount!); bitcoin!.createBitcoinHardwareWalletCredentials(name: name, accountData: selectedAccount!);
break; break;

View file

@ -17,6 +17,7 @@ import package_info_plus
import path_provider_foundation import path_provider_foundation
import share_plus import share_plus
import shared_preferences_foundation import shared_preferences_foundation
import universal_ble
import url_launcher_macos import url_launcher_macos
import wakelock_plus import wakelock_plus
@ -33,6 +34,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
UniversalBlePlugin.register(with: registry.registrar(forPlugin: "UniversalBlePlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin")) WakelockPlusMacosPlugin.register(with: registry.registrar(forPlugin: "WakelockPlusMacosPlugin"))
} }

View file

@ -12,7 +12,7 @@ dependencies:
version: 4.0.2 version: 4.0.2
shared_preferences: ^2.0.15 shared_preferences: ^2.0.15
# provider: ^6.0.3 # provider: ^6.0.3
rxdart: ^0.27.4 rxdart: ^0.28.0
yaml: ^3.1.1 yaml: ^3.1.1
#barcode_scan: any #barcode_scan: any
barcode_scan2: ^4.2.1 barcode_scan2: ^4.2.1
@ -97,7 +97,7 @@ dependencies:
polyseed: ^0.0.6 polyseed: ^0.0.6
nostr_tools: ^1.0.9 nostr_tools: ^1.0.9
solana: ^0.30.1 solana: ^0.30.1
ledger_flutter: ^1.0.1 ledger_flutter_plus: ^1.4.1
hashlib: ^1.19.2 hashlib: ^1.19.2
dev_dependencies: dev_dependencies:
@ -125,10 +125,6 @@ dependency_overrides:
bech32: bech32:
git: git:
url: https://github.com/cake-tech/bech32.git url: https://github.com/cake-tech/bech32.git
ledger_flutter:
git:
url: https://github.com/cake-tech/ledger-flutter.git
ref: cake-v3
web3dart: web3dart:
git: git:
url: https://github.com/cake-tech/web3dart.git url: https://github.com/cake-tech/web3dart.git
@ -155,6 +151,7 @@ flutter:
assets: assets:
- assets/images/ - assets/images/
- assets/images/flags/ - assets/images/flags/
- assets/images/hardware_wallet/
- assets/node_list.yml - assets/node_list.yml
- assets/haven_node_list.yml - assets/haven_node_list.yml
- assets/bitcoin_electrum_server_list.yml - assets/bitcoin_electrum_server_list.yml

View file

@ -114,7 +114,7 @@
"change_currency": "تغيير العملة", "change_currency": "تغيير العملة",
"change_current_node": "هل أنت متأكد من تغيير العقدة الحالية إلى ${node}؟", "change_current_node": "هل أنت متأكد من تغيير العقدة الحالية إلى ${node}؟",
"change_current_node_title": "تغيير العقدة الحالية", "change_current_node_title": "تغيير العقدة الحالية",
"change_exchange_provider": "تغيير مزود الصرف", "change_exchange_provider": "تغيير مزود المبادلة",
"change_language": "تغيير اللغة", "change_language": "تغيير اللغة",
"change_language_to": "هل تريد تغيير اللغة إلى ${language}؟", "change_language_to": "هل تريد تغيير اللغة إلى ${language}؟",
"change_password": "تغيير كلمة المرور", "change_password": "تغيير كلمة المرور",
@ -281,8 +281,8 @@
"etherscan_history": "Etherscan تاريخ", "etherscan_history": "Etherscan تاريخ",
"event": "ﺙﺪﺣ", "event": "ﺙﺪﺣ",
"events": "ﺙﺍﺪﺣﻷﺍ", "events": "ﺙﺍﺪﺣﻷﺍ",
"exchange": "تبادل", "exchange": "تبديل",
"exchange_incorrect_current_wallet_for_xmr": "إذا كنت ترغب في استبدال XMR من رصيد Cake Wallet Monero ، فيرجى التبديل إلى محفظة Monero أولاً.", "exchange_incorrect_current_wallet_for_xmr": "إذا كنت ترغب في تبديل XMR من رصيد محفظة الكعكة ، فيرجى التبديل إلى محفظة Monero أولاً.",
"exchange_new_template": "قالب جديد", "exchange_new_template": "قالب جديد",
"exchange_provider_unsupported": "${providerName} لم يعد مدعومًا!", "exchange_provider_unsupported": "${providerName} لم يعد مدعومًا!",
"exchange_result_confirm": "بالضغط على تأكيد ، سترسل ${fetchingLabel} ${from} من محفظتك المسماة ${walletName} إلى العنوان الموضح أدناه. أو يمكنك الإرسال من محفظتك الخارجية إلى العنوان أدناه / QR.\n\nيرجى الضغط على تأكيد للمتابعة أو الرجوع لتغيير المبالغ.", "exchange_result_confirm": "بالضغط على تأكيد ، سترسل ${fetchingLabel} ${from} من محفظتك المسماة ${walletName} إلى العنوان الموضح أدناه. أو يمكنك الإرسال من محفظتك الخارجية إلى العنوان أدناه / QR.\n\nيرجى الضغط على تأكيد للمتابعة أو الرجوع لتغيير المبالغ.",
@ -309,7 +309,7 @@
"fill_code": "يرجى ملء رمز التحقق المرسل إلى بريدك الإلكتروني", "fill_code": "يرجى ملء رمز التحقق المرسل إلى بريدك الإلكتروني",
"filter_by": "تصفية حسب", "filter_by": "تصفية حسب",
"first_wallet_text": "محفظة رائعة ل Monero, Bitcoin, Ethereum, Litecoin و Haven", "first_wallet_text": "محفظة رائعة ل Monero, Bitcoin, Ethereum, Litecoin و Haven",
"fixed_pair_not_supported": "هذا الزوج الثابت غير مدعوم في التبادلات المحددة", "fixed_pair_not_supported": "لا يتم دعم هذا الزوج الثابت مع خدمات المبادلة المحددة",
"fixed_rate": "السعر الثابت", "fixed_rate": "السعر الثابت",
"fixed_rate_alert": "ستتمكن من إدخال مبلغ الاستلام عند تشغيل وضع السعر الثابت. هل تريد التبديل إلى وضع السعر الثابت؟", "fixed_rate_alert": "ستتمكن من إدخال مبلغ الاستلام عند تشغيل وضع السعر الثابت. هل تريد التبديل إلى وضع السعر الثابت؟",
"forgot_password": "هل نسيت كلمة السر", "forgot_password": "هل نسيت كلمة السر",
@ -439,7 +439,7 @@
"node_test": "تجربة", "node_test": "تجربة",
"nodes": "العقد", "nodes": "العقد",
"nodes_list_reset_to_default_message": "هل أنت متأكد أنك تريد إعادة تعيين الإعدادات إلى الافتراضي؟", "nodes_list_reset_to_default_message": "هل أنت متأكد أنك تريد إعادة تعيين الإعدادات إلى الافتراضي؟",
"none_of_selected_providers_can_exchange": "لا يمكن لأي من مقدمي الخدمة المختارين إجراء هذا التبادل", "none_of_selected_providers_can_exchange": "لا يمكن لأي من مقدمي الخدمات المختارين إجراء هذا المبادلة",
"noNFTYet": "ﻥﻵﺍ ﻰﺘﺣ NFTs ﺪﺟﻮﻳ ﻻ", "noNFTYet": "ﻥﻵﺍ ﻰﺘﺣ NFTs ﺪﺟﻮﻳ ﻻ",
"normal": "طبيعي", "normal": "طبيعي",
"note_optional": "ملاحظة (اختياري)", "note_optional": "ملاحظة (اختياري)",

View file

@ -114,7 +114,7 @@
"change_currency": "Смени валута", "change_currency": "Смени валута",
"change_current_node": "Сигурни ли сте, че искате да промените сегашния node на ${node}?", "change_current_node": "Сигурни ли сте, че искате да промените сегашния node на ${node}?",
"change_current_node_title": "Промени сегашния node", "change_current_node_title": "Промени сегашния node",
"change_exchange_provider": "Промяна на Exchange Provider", "change_exchange_provider": "Промяна на доставчика на суап",
"change_language": "Смяна на езика", "change_language": "Смяна на езика",
"change_language_to": "Смяна на езика на ${language}?", "change_language_to": "Смяна на езика на ${language}?",
"change_password": "Смяна на парола", "change_password": "Смяна на парола",
@ -281,8 +281,8 @@
"etherscan_history": "История на Etherscan", "etherscan_history": "История на Etherscan",
"event": "Събитие", "event": "Събитие",
"events": "събития", "events": "събития",
"exchange": "Exchange", "exchange": "Разметка",
"exchange_incorrect_current_wallet_for_xmr": "Ако искате да обмените XMR от своя Cake Wallet Monero баланс, първо изберете своя Monero портфейл.", "exchange_incorrect_current_wallet_for_xmr": "Ако искате да смените XMR от вашия баланс на портфейла на тортата Monero, моля, преминете първо към вашия портфейл Monero.",
"exchange_new_template": "Нов шаблон", "exchange_new_template": "Нов шаблон",
"exchange_provider_unsupported": "${providerName} вече не се поддържа!", "exchange_provider_unsupported": "${providerName} вече не се поддържа!",
"exchange_result_confirm": "By pressing confirm, you will be sending ${fetchingLabel} ${from} from your wallet called ${walletName} to the address shown below. Or you can send from your external wallet to the below address/QR code.\n\nPlease press confirm to continue or go back to change the amounts.", "exchange_result_confirm": "By pressing confirm, you will be sending ${fetchingLabel} ${from} from your wallet called ${walletName} to the address shown below. Or you can send from your external wallet to the below address/QR code.\n\nPlease press confirm to continue or go back to change the amounts.",
@ -309,7 +309,7 @@
"fill_code": "Моля, въведето кода за потвърждаване, изпратен на Вашия имейл", "fill_code": "Моля, въведето кода за потвърждаване, изпратен на Вашия имейл",
"filter_by": "Филтрирай по", "filter_by": "Филтрирай по",
"first_wallet_text": "Невероятен портфейл за Monero, Bitcoin, Ethereum, Litecoin и Haven", "first_wallet_text": "Невероятен портфейл за Monero, Bitcoin, Ethereum, Litecoin и Haven",
"fixed_pair_not_supported": "Този fixed pair не се поддържа от избраната борса", "fixed_pair_not_supported": "Тази фиксирана двойка не се поддържа с избраните услуги за суап",
"fixed_rate": "Постоянен обменен курс", "fixed_rate": "Постоянен обменен курс",
"fixed_rate_alert": "Ще можете да въведете сумата за получаване, когато е избранен постоянен обменен курс. Искате ли да изберете постоянен обменен курс?", "fixed_rate_alert": "Ще можете да въведете сумата за получаване, когато е избранен постоянен обменен курс. Искате ли да изберете постоянен обменен курс?",
"forgot_password": "Забравена парола", "forgot_password": "Забравена парола",
@ -439,7 +439,7 @@
"node_test": "Тест", "node_test": "Тест",
"nodes": "Nodes", "nodes": "Nodes",
"nodes_list_reset_to_default_message": "Сигурни ли сте, че искате да възстановите фабричните настройки?", "nodes_list_reset_to_default_message": "Сигурни ли сте, че искате да възстановите фабричните настройки?",
"none_of_selected_providers_can_exchange": "Нито един от избраните provider-ъри не може да направи този превод", "none_of_selected_providers_can_exchange": "Нито един от избраните доставчици не може да направи този размяна",
"noNFTYet": "Все още няма NFT", "noNFTYet": "Все още няма NFT",
"normal": "нормално", "normal": "нормално",
"note_optional": "Бележка (не е задължително)", "note_optional": "Бележка (не е задължително)",

View file

@ -114,7 +114,7 @@
"change_currency": "Změnit měnu", "change_currency": "Změnit měnu",
"change_current_node": "Opravdu chcete změnit současný uzel na ${node}?", "change_current_node": "Opravdu chcete změnit současný uzel na ${node}?",
"change_current_node_title": "Změnit současný uzel", "change_current_node_title": "Změnit současný uzel",
"change_exchange_provider": "Změnit směnárnu", "change_exchange_provider": "Změnit poskytovatele swapu",
"change_language": "Změnit jazyk", "change_language": "Změnit jazyk",
"change_language_to": "Změnit jazyk na ${language}?", "change_language_to": "Změnit jazyk na ${language}?",
"change_password": "Změnit heslo", "change_password": "Změnit heslo",
@ -281,8 +281,8 @@
"etherscan_history": "Historie Etherscanu", "etherscan_history": "Historie Etherscanu",
"event": "událost", "event": "událost",
"events": "Události", "events": "Události",
"exchange": "Směnit", "exchange": "Swap",
"exchange_incorrect_current_wallet_for_xmr": "Pokud chcete směnit XMR z Monero částky v Cake Wallet, prosím přepněte se nejprve do své Monero peněženky.", "exchange_incorrect_current_wallet_for_xmr": "Pokud chcete vyměnit XMR z vaší dortové peněženky Monero Balance, nejprve přepněte na peněženku Monero.",
"exchange_new_template": "Nová šablona", "exchange_new_template": "Nová šablona",
"exchange_provider_unsupported": "${providerName} již není podporováno!", "exchange_provider_unsupported": "${providerName} již není podporováno!",
"exchange_result_confirm": "Po stisknutí Potvrdit odešlete ${fetchingLabel} ${from} ze své peněženky s názvem ${walletName} na adresu uvedenou níže. Nebo můžete prostředky poslat ze své externí peněženky na níže uvedenou adresu/QR kód.\n\nProsím stiskněte Potvrdit pro pokračování, nebo se vraťte zpět pro změnu částky.", "exchange_result_confirm": "Po stisknutí Potvrdit odešlete ${fetchingLabel} ${from} ze své peněženky s názvem ${walletName} na adresu uvedenou níže. Nebo můžete prostředky poslat ze své externí peněženky na níže uvedenou adresu/QR kód.\n\nProsím stiskněte Potvrdit pro pokračování, nebo se vraťte zpět pro změnu částky.",
@ -309,7 +309,7 @@
"fill_code": "Prosím vyplňte ověřovací kód zaslaný na Váš e-mail", "fill_code": "Prosím vyplňte ověřovací kód zaslaný na Váš e-mail",
"filter_by": "Filtrovat podle", "filter_by": "Filtrovat podle",
"first_wallet_text": "Úžasná peněženka pro Monero, Bitcoin, Ethereum, Litecoin a Haven", "first_wallet_text": "Úžasná peněženka pro Monero, Bitcoin, Ethereum, Litecoin a Haven",
"fixed_pair_not_supported": "Tento pár s pevným kurzem není ve zvolené směnárně podporován", "fixed_pair_not_supported": "Tento pevný pár není podporován vybranými službami swapu",
"fixed_rate": "Pevný kurz", "fixed_rate": "Pevný kurz",
"fixed_rate_alert": "Když je zvolený pevný kurz, můžete zadat konkrétní částku, kterou chcete dostat. Chcete se přepnout do režimu s pevným kurzem?", "fixed_rate_alert": "Když je zvolený pevný kurz, můžete zadat konkrétní částku, kterou chcete dostat. Chcete se přepnout do režimu s pevným kurzem?",
"forgot_password": "Zapomenuté heslo", "forgot_password": "Zapomenuté heslo",
@ -439,7 +439,7 @@
"node_test": "Otestovat", "node_test": "Otestovat",
"nodes": "Uzly", "nodes": "Uzly",
"nodes_list_reset_to_default_message": "Opravdu chcete zrušit nastavení a vrátit výchozí hodnotu?", "nodes_list_reset_to_default_message": "Opravdu chcete zrušit nastavení a vrátit výchozí hodnotu?",
"none_of_selected_providers_can_exchange": "Žádný ze zvolených poskytovatelů nemůže provést tuto směnu", "none_of_selected_providers_can_exchange": "Žádný z vybraných poskytovatelů nemůže tuto swap provést",
"noNFTYet": "Zatím žádné NFT", "noNFTYet": "Zatím žádné NFT",
"normal": "Normální", "normal": "Normální",
"note_optional": "Poznámka (nepovinné)", "note_optional": "Poznámka (nepovinné)",

View file

@ -97,7 +97,7 @@
"cake_pay_account_note": "Melden Sie sich nur mit einer E-Mail-Adresse an, um Karten anzuzeigen und zu kaufen. Einige sind sogar mit Rabatt erhältlich!", "cake_pay_account_note": "Melden Sie sich nur mit einer E-Mail-Adresse an, um Karten anzuzeigen und zu kaufen. Einige sind sogar mit Rabatt erhältlich!",
"cake_pay_learn_more": "Kaufen und lösen Sie Geschenkkarten sofort in der App ein!\nWischen Sie von links nach rechts, um mehr zu erfahren.", "cake_pay_learn_more": "Kaufen und lösen Sie Geschenkkarten sofort in der App ein!\nWischen Sie von links nach rechts, um mehr zu erfahren.",
"cake_pay_save_order": "Die Karte sollte innerhalb von 1 Werktag an Ihre E-Mail gesendet werden, \n Ihre Bestell-ID zu speichern:", "cake_pay_save_order": "Die Karte sollte innerhalb von 1 Werktag an Ihre E-Mail gesendet werden, \n Ihre Bestell-ID zu speichern:",
"cake_pay_subtitle": "Kaufen Sie weltweite Prepaid -Karten und Geschenkkarten", "cake_pay_subtitle": "Kaufen Sie weltweite Prepaid-Karten und Geschenkkarten",
"cake_pay_web_cards_subtitle": "Kaufen Sie weltweit Prepaid-Karten und Geschenkkarten", "cake_pay_web_cards_subtitle": "Kaufen Sie weltweit Prepaid-Karten und Geschenkkarten",
"cake_pay_web_cards_title": "Cake Pay-Webkarten", "cake_pay_web_cards_title": "Cake Pay-Webkarten",
"cake_wallet": "Cake Wallet", "cake_wallet": "Cake Wallet",
@ -114,7 +114,7 @@
"change_currency": "Währung ändern", "change_currency": "Währung ändern",
"change_current_node": "Möchten Sie den aktuellen Knoten wirklich zu ${node}? ändern?", "change_current_node": "Möchten Sie den aktuellen Knoten wirklich zu ${node}? ändern?",
"change_current_node_title": "Aktuellen Knoten ändern", "change_current_node_title": "Aktuellen Knoten ändern",
"change_exchange_provider": "Exchange-Anbieter ändern", "change_exchange_provider": "Swap-Anbieter ändern",
"change_language": "Sprache ändern", "change_language": "Sprache ändern",
"change_language_to": "Sprache zu ${language} ändern?", "change_language_to": "Sprache zu ${language} ändern?",
"change_password": "Passwort ändern", "change_password": "Passwort ändern",
@ -131,7 +131,7 @@
"choose_one": "Wähle ein", "choose_one": "Wähle ein",
"choose_relay": "Bitte wählen Sie ein zu verwendendes Relais aus", "choose_relay": "Bitte wählen Sie ein zu verwendendes Relais aus",
"choose_wallet_currency": "Bitte wählen Sie die Währung der Wallet:", "choose_wallet_currency": "Bitte wählen Sie die Währung der Wallet:",
"choose_wallet_group": "Wählen Sie Brieftaschengruppe", "choose_wallet_group": "Wählen Sie Walletgruppe",
"clear": "Zurücksetzen", "clear": "Zurücksetzen",
"clearnet_link": "Clearnet-Link", "clearnet_link": "Clearnet-Link",
"close": "Schließen", "close": "Schließen",
@ -179,7 +179,7 @@
"create_invoice": "Rechnung erstellen", "create_invoice": "Rechnung erstellen",
"create_new": "Neue Wallet erstellen", "create_new": "Neue Wallet erstellen",
"create_new_account": "Neues Konto erstellen", "create_new_account": "Neues Konto erstellen",
"create_new_seed": "Neue Samen erstellen", "create_new_seed": "Neue Seed erstellen",
"creating_new_wallet": "Neue Wallet erstellen", "creating_new_wallet": "Neue Wallet erstellen",
"creating_new_wallet_error": "Fehler: ${description}", "creating_new_wallet_error": "Fehler: ${description}",
"creation_date": "Erstellungsdatum", "creation_date": "Erstellungsdatum",
@ -189,9 +189,9 @@
"custom_value": "Benutzerdefinierten Wert", "custom_value": "Benutzerdefinierten Wert",
"dark_theme": "Dunkel", "dark_theme": "Dunkel",
"debit_card": "Debitkarte", "debit_card": "Debitkarte",
"debit_card_terms": "Die Speicherung und Nutzung Ihrer Zahlungskartennummer (und Ihrer Zahlungskartennummer entsprechenden Anmeldeinformationen) in dieser digitalen Geldbörse unterliegt den Allgemeinen Geschäftsbedingungen des geltenden Karteninhabervertrags mit dem Zahlungskartenaussteller, gültig ab von Zeit zu Zeit.", "debit_card_terms": "Die Speicherung und Nutzung Ihrer Zahlungskartennummer (und Ihrer Zahlungskartennummer entsprechenden Anmeldeinformationen) in dieser digitalen Wallet unterliegt den Allgemeinen Geschäftsbedingungen des geltenden Karteninhabervertrags mit dem Zahlungskartenaussteller, gültig ab von Zeit zu Zeit.",
"decimal_places_error": "Zu viele Nachkommastellen", "decimal_places_error": "Zu viele Nachkommastellen",
"decimals_cannot_be_zero": "Token -Dezimalzahl kann nicht Null sein.", "decimals_cannot_be_zero": "Token-Dezimalzahl kann nicht Null sein.",
"default_buy_provider": "Standard-Kaufanbieter", "default_buy_provider": "Standard-Kaufanbieter",
"default_sell_provider": "Standard-Verkaufsanbieter", "default_sell_provider": "Standard-Verkaufsanbieter",
"delete": "Löschen", "delete": "Löschen",
@ -236,7 +236,7 @@
"electrum_address_disclaimer": "Wir generieren jedes Mal neue Adressen, wenn Sie eine verwenden, aber vorherige Adressen funktionieren weiterhin", "electrum_address_disclaimer": "Wir generieren jedes Mal neue Adressen, wenn Sie eine verwenden, aber vorherige Adressen funktionieren weiterhin",
"email_address": "E-Mail-Adresse", "email_address": "E-Mail-Adresse",
"enable": "Aktivieren", "enable": "Aktivieren",
"enable_mempool_api": "Mempool -API für genaue Gebühren und Daten", "enable_mempool_api": "Mempool-API für genaue Gebühren und Daten",
"enable_replace_by_fee": "Aktivieren Sie Ersatz für Fee", "enable_replace_by_fee": "Aktivieren Sie Ersatz für Fee",
"enable_silent_payments_scanning": "Scannen Sie stille Zahlungen, bis die Spitze erreicht ist", "enable_silent_payments_scanning": "Scannen Sie stille Zahlungen, bis die Spitze erreicht ist",
"enabled": "Ermöglicht", "enabled": "Ermöglicht",
@ -245,7 +245,7 @@
"enter_code": "Code eingeben", "enter_code": "Code eingeben",
"enter_seed_phrase": "Geben Sie Ihre Seed-Phrase ein", "enter_seed_phrase": "Geben Sie Ihre Seed-Phrase ein",
"enter_totp_code": "Bitte geben Sie den TOTP-Code ein.", "enter_totp_code": "Bitte geben Sie den TOTP-Code ein.",
"enter_wallet_password": "Geben Sie das Brieftaschenkennwort ein", "enter_wallet_password": "Geben Sie das Walletkennwort ein",
"enter_your_note": "Geben Sie Ihre Bemerkung ein…", "enter_your_note": "Geben Sie Ihre Bemerkung ein…",
"enter_your_pin": "PIN eingeben", "enter_your_pin": "PIN eingeben",
"enter_your_pin_again": "Geben Sie Ihre PIN erneut ein", "enter_your_pin_again": "Geben Sie Ihre PIN erneut ein",
@ -281,8 +281,8 @@
"etherscan_history": "Etherscan-Geschichte", "etherscan_history": "Etherscan-Geschichte",
"event": "Ereignis", "event": "Ereignis",
"events": "Veranstaltungen", "events": "Veranstaltungen",
"exchange": "Umwechseln", "exchange": "Tauschen",
"exchange_incorrect_current_wallet_for_xmr": "Wenn Sie XMR von Ihrem Cake Wallet Monero-Guthaben umtauschen möchten, wechseln Sie bitte zuerst zu Ihrer Monero-Wallet.", "exchange_incorrect_current_wallet_for_xmr": "Wenn Sie XMR aus Ihrer CakeWallet Monero-Balance tauschen möchten, wechseln Sie zuerst zu Ihrer Monero-Wallet.",
"exchange_new_template": "Neue Vorlage", "exchange_new_template": "Neue Vorlage",
"exchange_provider_unsupported": "${providerName} wird nicht mehr unterstützt!", "exchange_provider_unsupported": "${providerName} wird nicht mehr unterstützt!",
"exchange_result_confirm": "Durch Drücken von \"Bestätigen\" wird ${fetchingLabel} ${from} von Ihrer Wallet namens ${walletName} an die unten angegebene Adresse gesendet. Alternativ können Sie von einer externen Wallet an die unten angegebene Adresse / QR-Code senden.\n\nBitte bestätigen Sie, um fortzufahren, oder gehen Sie zurück, um die Beträge zu ändern.", "exchange_result_confirm": "Durch Drücken von \"Bestätigen\" wird ${fetchingLabel} ${from} von Ihrer Wallet namens ${walletName} an die unten angegebene Adresse gesendet. Alternativ können Sie von einer externen Wallet an die unten angegebene Adresse / QR-Code senden.\n\nBitte bestätigen Sie, um fortzufahren, oder gehen Sie zurück, um die Beträge zu ändern.",
@ -309,7 +309,7 @@
"fill_code": "Geben Sie den Bestätigungscode ein, den Sie per E-Mail erhalten haben", "fill_code": "Geben Sie den Bestätigungscode ein, den Sie per E-Mail erhalten haben",
"filter_by": "Filtern nach", "filter_by": "Filtern nach",
"first_wallet_text": "Eine großartige Wallet für Monero, Bitcoin, Ethereum, Litecoin, und Haven", "first_wallet_text": "Eine großartige Wallet für Monero, Bitcoin, Ethereum, Litecoin, und Haven",
"fixed_pair_not_supported": "Dieses feste Paar wird von den ausgewählten Vermittlungsstellen nicht unterstützt", "fixed_pair_not_supported": "Dieses feste Paar wird nicht von den ausgewählten Swap-Diensten unterstützt",
"fixed_rate": "Feste Rate", "fixed_rate": "Feste Rate",
"fixed_rate_alert": "Sie können den Empfangsbetrag eingeben, wenn der Festratenmodus aktiviert ist. Möchten Sie in den Festratenmodus wechseln?", "fixed_rate_alert": "Sie können den Empfangsbetrag eingeben, wenn der Festratenmodus aktiviert ist. Möchten Sie in den Festratenmodus wechseln?",
"forgot_password": "Passwort vergessen", "forgot_password": "Passwort vergessen",
@ -349,9 +349,9 @@
"incoming": "Eingehend", "incoming": "Eingehend",
"incorrect_seed": "Der eingegebene Text ist ungültig.", "incorrect_seed": "Der eingegebene Text ist ungültig.",
"inputs": "Eingänge", "inputs": "Eingänge",
"insufficient_lamport_for_tx": "Sie haben nicht genug SOL, um die Transaktion und ihre Transaktionsgebühr abzudecken. Bitte fügen Sie Ihrer Brieftasche mehr Sol hinzu oder reduzieren Sie die SO -Menge, die Sie senden.", "insufficient_lamport_for_tx": "Sie haben nicht genug SOL, um die Transaktion und ihre Transaktionsgebühr abzudecken. Bitte fügen Sie Ihrer Wallet mehr Sol hinzu oder reduzieren Sie die SOL-Menge, die Sie senden.",
"insufficient_lamports": "Sie haben nicht genug SOL, um die Transaktion und ihre Transaktionsgebühr abzudecken. Sie brauchen mindestens ${solValueNeeded} Sol. Bitte fügen Sie mehr Sol zu Ihrer Wallet hinzu oder reduzieren Sie den von Ihnen gesendeten Sol -Betrag", "insufficient_lamports": "Sie haben nicht genug SOL, um die Transaktion und ihre Transaktionsgebühr abzudecken. Sie brauchen mindestens ${solValueNeeded} Sol. Bitte fügen Sie mehr Sol zu Ihrer Wallet hinzu oder reduzieren Sie den von Ihnen gesendeten Sol-Betrag",
"insufficientFundsForRentError": "Sie haben nicht genug SOL, um die Transaktionsgebühr und die Miete für das Konto zu decken. Bitte fügen Sie mehr Sol zu Ihrer Brieftasche hinzu oder reduzieren Sie den von Ihnen gesendeten Sol -Betrag", "insufficientFundsForRentError": "Sie haben nicht genug SOL, um die Transaktionsgebühr und die Miete für das Konto zu decken. Bitte fügen Sie mehr Sol zu Ihrer Wallet hinzu oder reduzieren Sie den von Ihnen gesendeten Sol-Betrag",
"introducing_cake_pay": "Einführung von Cake Pay!", "introducing_cake_pay": "Einführung von Cake Pay!",
"invalid_input": "Ungültige Eingabe", "invalid_input": "Ungültige Eingabe",
"invalid_password": "Ungültiges Passwort", "invalid_password": "Ungültiges Passwort",
@ -365,20 +365,20 @@
"ledger_error_wrong_app": "Bitte stellen Sie sicher, dass Sie die richtige App auf Ihrem Ledger geöffnet haben", "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.", "ledger_please_enable_bluetooth": "Bitte aktivieren Sie Bluetooth um sich mit Ihren Ledger zu verbinden.",
"light_theme": "Hell", "light_theme": "Hell",
"litecoin_enable_mweb_sync": "Aktivieren Sie das MWEB -Scannen", "litecoin_enable_mweb_sync": "Aktivieren Sie das MWEB-Scannen",
"litecoin_mweb": "MWeb", "litecoin_mweb": "MWeb",
"litecoin_mweb_always_scan": "Setzen Sie MWeb immer scannen", "litecoin_mweb_always_scan": "Setzen Sie MWeb immer scannen",
"litecoin_mweb_description": "MWEB ist ein neues Protokoll, das schnellere, billigere und privatere Transaktionen zu Litecoin bringt", "litecoin_mweb_description": "MWWB ist ein neues Protokoll, das schnellere, billigere und privatere Transaktionen zu Litecoin bringt",
"litecoin_mweb_dismiss": "Zurückweisen", "litecoin_mweb_dismiss": "Zurückweisen",
"litecoin_mweb_display_card": "MWEB -Karte anzeigen", "litecoin_mweb_display_card": "MWEB-Karte anzeigen",
"litecoin_mweb_enable_later": "Sie können MWEB unter Anzeigeeinstellungen erneut aktivieren.", "litecoin_mweb_enable_later": "Sie können MWEB unter Anzeigeeinstellungen erneut aktivieren.",
"litecoin_mweb_pegin": "Peg in", "litecoin_mweb_pegin": "Peg in",
"litecoin_mweb_pegout": "Abstecken", "litecoin_mweb_pegout": "Abstecken",
"litecoin_mweb_scanning": "MWEB Scanning", "litecoin_mweb_scanning": "MWEB Scanning",
"litecoin_mweb_settings": "MWEB -Einstellungen", "litecoin_mweb_settings": "MWEB-Einstellungen",
"litecoin_mweb_warning": "Durch die Verwendung von MWEB wird zunächst ~ 600 MB Daten heruntergeladen und kann je nach Netzwerkgeschwindigkeit bis zu 30 Minuten dauern. Diese ersten Daten werden nur einmal heruntergeladen und für alle Litecoin -Brieftaschen verfügbar", "litecoin_mweb_warning": "Durch die Verwendung von MWEB wird zunächst ~ 600 MB Daten heruntergeladen und kann je nach Netzwerkgeschwindigkeit bis zu 30 Minuten dauern. Diese ersten Daten werden nur einmal heruntergeladen und für alle Litecoin-Wallets verfügbar",
"litecoin_what_is_mweb": "Was ist MWeb?", "litecoin_what_is_mweb": "Was ist MWeb?",
"live_fee_rates": "Live -Gebührenpreise über API", "live_fee_rates": "Live-Gebührenpreise über API",
"load_more": "Mehr laden", "load_more": "Mehr laden",
"loading_your_wallet": "Wallet wird geladen", "loading_your_wallet": "Wallet wird geladen",
"login": "Einloggen", "login": "Einloggen",
@ -439,7 +439,7 @@
"node_test": "Test", "node_test": "Test",
"nodes": "Knoten", "nodes": "Knoten",
"nodes_list_reset_to_default_message": "Möchten Sie wirklich die Standardeinstellungen wiederherstellen?", "nodes_list_reset_to_default_message": "Möchten Sie wirklich die Standardeinstellungen wiederherstellen?",
"none_of_selected_providers_can_exchange": "Keiner der ausgewählten Anbieter kann diesen Austausch vornehmen", "none_of_selected_providers_can_exchange": "Keiner der ausgewählten Anbieter kann diesen Tausch machen",
"noNFTYet": "Noch keine NFTs", "noNFTYet": "Noch keine NFTs",
"normal": "Normal", "normal": "Normal",
"note_optional": "Bemerkung (optional)", "note_optional": "Bemerkung (optional)",
@ -533,8 +533,8 @@
"rename": "Umbenennen", "rename": "Umbenennen",
"rep_warning": "Repräsentative Warnung", "rep_warning": "Repräsentative Warnung",
"rep_warning_sub": "Ihr Vertreter scheint nicht gut zu sein. Tippen Sie hier, um eine neue auszuwählen", "rep_warning_sub": "Ihr Vertreter scheint nicht gut zu sein. Tippen Sie hier, um eine neue auszuwählen",
"repeat_wallet_password": "Wiederholen Sie das Brieftaschenkennwort", "repeat_wallet_password": "Wiederholen Sie das Walletkennwort",
"repeated_password_is_incorrect": "Wiederholtes Passwort ist falsch. Bitte wiederholen Sie das Brieftaschenkennwort erneut.", "repeated_password_is_incorrect": "Wiederholtes Passwort ist falsch. Bitte wiederholen Sie das Walletkennwort erneut.",
"require_for_adding_contacts": "Erforderlich zum Hinzufügen von Kontakten", "require_for_adding_contacts": "Erforderlich zum Hinzufügen von Kontakten",
"require_for_all_security_and_backup_settings": "Für alle Sicherheits- und Sicherungseinstellungen erforderlich", "require_for_all_security_and_backup_settings": "Für alle Sicherheits- und Sicherungseinstellungen erforderlich",
"require_for_assessing_wallet": "Für den Zugriff auf die Wallet erforderlich", "require_for_assessing_wallet": "Für den Zugriff auf die Wallet erforderlich",
@ -576,7 +576,7 @@
"restore_wallet": "Wallet wiederherstellen", "restore_wallet": "Wallet wiederherstellen",
"restore_wallet_name": "Walletname", "restore_wallet_name": "Walletname",
"restore_wallet_restore_description": "Beschreibung zur Wallet-Wiederherstellung", "restore_wallet_restore_description": "Beschreibung zur Wallet-Wiederherstellung",
"robinhood_option_description": "Kaufen und übertragen Sie sofort mit Ihrem Debitkarten-, Bankkonto- oder Robinhood -Guthaben. Nur USA.", "robinhood_option_description": "Kaufen und übertragen Sie sofort mit Ihrem Debitkarten-, Bankkonto- oder Robinhood-Guthaben. Nur USA.",
"router_no_route": "Keine Route definiert für ${name}", "router_no_route": "Keine Route definiert für ${name}",
"save": "Speichern", "save": "Speichern",
"save_backup_password": "Bitte stellen Sie sicher, dass Sie Ihr Sicherungskennwort gespeichert haben. Ohne dieses können Sie Ihre Sicherungsdateien nicht importieren.", "save_backup_password": "Bitte stellen Sie sicher, dass Sie Ihr Sicherungskennwort gespeichert haben. Ohne dieses können Sie Ihre Sicherungsdateien nicht importieren.",
@ -622,8 +622,8 @@
"seed_share": "Seed teilen", "seed_share": "Seed teilen",
"seed_title": "Seed", "seed_title": "Seed",
"seedtype": "Seedtyp", "seedtype": "Seedtyp",
"seedtype_alert_content": "Das Teilen von Samen mit anderen Brieftaschen ist nur mit bip39 Seedype möglich.", "seedtype_alert_content": "Das Teilen von Seeds mit anderen Wallet ist nur mit bip39 Seedype möglich.",
"seedtype_alert_title": "Seedype -Alarm", "seedtype_alert_title": "Seedype-Alarm",
"seedtype_legacy": "Veraltet (25 Wörter)", "seedtype_legacy": "Veraltet (25 Wörter)",
"seedtype_polyseed": "Polyseed (16 Wörter)", "seedtype_polyseed": "Polyseed (16 Wörter)",
"seedtype_wownero": "WOWNO (14 Wörter)", "seedtype_wownero": "WOWNO (14 Wörter)",
@ -691,7 +691,7 @@
"setup_your_debit_card": "Richten Sie Ihre Debitkarte ein", "setup_your_debit_card": "Richten Sie Ihre Debitkarte ein",
"share": "Teilen", "share": "Teilen",
"share_address": "Adresse teilen ", "share_address": "Adresse teilen ",
"shared_seed_wallet_groups": "Gemeinsame Samenbrieftaschengruppen", "shared_seed_wallet_groups": "Gemeinsame Walletsseed Gruppen",
"show_details": "Details anzeigen", "show_details": "Details anzeigen",
"show_keys": "Seed/Schlüssel anzeigen", "show_keys": "Seed/Schlüssel anzeigen",
"show_market_place": "Marktplatz anzeigen", "show_market_place": "Marktplatz anzeigen",
@ -711,12 +711,12 @@
"silent_payments_disclaimer": "Neue Adressen sind keine neuen Identitäten. Es ist eine Wiederverwendung einer bestehenden Identität mit einem anderen Etikett.", "silent_payments_disclaimer": "Neue Adressen sind keine neuen Identitäten. Es ist eine Wiederverwendung einer bestehenden Identität mit einem anderen Etikett.",
"silent_payments_display_card": "Zeigen Sie stille Zahlungskarte", "silent_payments_display_card": "Zeigen Sie stille Zahlungskarte",
"silent_payments_scan_from_date": "Scan ab Datum", "silent_payments_scan_from_date": "Scan ab Datum",
"silent_payments_scan_from_date_or_blockheight": "Bitte geben Sie die Blockhöhe ein, die Sie für eingehende stille Zahlungen scannen möchten, oder verwenden Sie stattdessen das Datum. Sie können wählen, ob die Brieftasche jeden Block scannt oder nur die angegebene Höhe überprüft.", "silent_payments_scan_from_date_or_blockheight": "Bitte geben Sie die Blockhöhe ein, die Sie für eingehende stille Zahlungen scannen möchten, oder verwenden Sie stattdessen das Datum. Sie können wählen, ob die Wallet jeden Block scannt oder nur die angegebene Höhe überprüft.",
"silent_payments_scan_from_height": "Scan aus der Blockhöhe scannen", "silent_payments_scan_from_height": "Scan aus der Blockhöhe scannen",
"silent_payments_scanned_tip": "Gescannt zum Trinkgeld! (${tip})", "silent_payments_scanned_tip": "Gescannt zum Trinkgeld! (${tip})",
"silent_payments_scanning": "Stille Zahlungen scannen", "silent_payments_scanning": "Stille Zahlungen scannen",
"silent_payments_settings": "Einstellungen für stille Zahlungen", "silent_payments_settings": "Einstellungen für stille Zahlungen",
"single_seed_wallets_group": "Einzelne Samenbriefen", "single_seed_wallets_group": "Einzelne Wallets",
"slidable": "Verschiebbar", "slidable": "Verschiebbar",
"sort_by": "Sortiere nach", "sort_by": "Sortiere nach",
"spend_key_private": "Spend Key (geheim)", "spend_key_private": "Spend Key (geheim)",
@ -755,11 +755,11 @@
"syncing_wallet_alert_title": "Ihr Wallet wird synchronisiert", "syncing_wallet_alert_title": "Ihr Wallet wird synchronisiert",
"template": "Vorlage", "template": "Vorlage",
"template_name": "Vorlagenname", "template_name": "Vorlagenname",
"testnet_coins_no_value": "Testnet -Münzen haben keinen Wert", "testnet_coins_no_value": "Testnet-Münzen haben keinen Wert",
"third_intro_content": "Yats leben auch außerhalb von Cake Wallet. Jede Wallet-Adresse auf der Welt kann durch ein Yat ersetzt werden!", "third_intro_content": "Yats leben auch außerhalb von Cake Wallet. Jede Wallet-Adresse auf der Welt kann durch ein Yat ersetzt werden!",
"third_intro_title": "Yat spielt gut mit anderen", "third_intro_title": "Yat spielt gut mit anderen",
"thorchain_contract_address_not_supported": "Thorchain unterstützt das Senden an eine Vertragsadresse nicht", "thorchain_contract_address_not_supported": "Thorchain unterstützt das Senden an eine Vertragsadresse nicht",
"thorchain_taproot_address_not_supported": "Der Thorchain -Anbieter unterstützt keine Taproot -Adressen. Bitte ändern Sie die Adresse oder wählen Sie einen anderen Anbieter aus.", "thorchain_taproot_address_not_supported": "Der Thorchain-Anbieter unterstützt keine Taproot-Adressen. Bitte ändern Sie die Adresse oder wählen Sie einen anderen Anbieter aus.",
"time": "${minutes}m ${seconds}s", "time": "${minutes}m ${seconds}s",
"tip": "Hinweis:", "tip": "Hinweis:",
"today": "Heute", "today": "Heute",
@ -879,12 +879,12 @@
"voting_weight": "Stimmgewicht", "voting_weight": "Stimmgewicht",
"waitFewSecondForTxUpdate": "Bitte warten Sie einige Sekunden, bis die Transaktion im Transaktionsverlauf angezeigt wird", "waitFewSecondForTxUpdate": "Bitte warten Sie einige Sekunden, bis die Transaktion im Transaktionsverlauf angezeigt wird",
"waiting_payment_confirmation": "Warte auf Zahlungsbestätigung", "waiting_payment_confirmation": "Warte auf Zahlungsbestätigung",
"wallet_group": "Brieftaschengruppe", "wallet_group": "Walletgruppe",
"wallet_group_description_four": "eine Brieftasche mit einem völlig neuen Samen schaffen.", "wallet_group_description_four": "eine Wallet mit einem völlig neuen Seed schaffen.",
"wallet_group_description_one": "In Kuchenbrieftasche können Sie eine erstellen", "wallet_group_description_one": "In CakeWallet können Sie eine erstellen",
"wallet_group_description_three": "Sehen Sie den Bildschirm zur verfügbaren Brieftaschen und/oder Brieftaschengruppen. Oder wählen", "wallet_group_description_three": "Sehen Sie den Bildschirm zur verfügbaren Wallet und/oder Walletgruppen. Oder wählen",
"wallet_group_description_two": "Durch die Auswahl einer vorhandenen Brieftasche, mit der ein Samen geteilt werden kann. Jede Brieftaschengruppe kann eine einzelne Brieftasche jedes Währungstyps enthalten. \n\n Sie können auswählen", "wallet_group_description_two": "Durch die Auswahl einer vorhandenen Wallet, mit der ein Seed geteilt werden kann. Jede Walletgruppe kann eine einzelne Wallet jedes Währungstyps enthalten. \n\n Sie können auswählen",
"wallet_group_empty_state_text_one": "Sieht so aus, als hätten Sie keine kompatiblen Brieftaschengruppen !\n\n TAP", "wallet_group_empty_state_text_one": "Sieht so aus, als hätten Sie keine kompatiblen Walletgruppen !\n\n TAP",
"wallet_group_empty_state_text_two": "unten, um einen neuen zu machen.", "wallet_group_empty_state_text_two": "unten, um einen neuen zu machen.",
"wallet_keys": "Wallet-Seed/-Schlüssel", "wallet_keys": "Wallet-Seed/-Schlüssel",
"wallet_list_create_new_wallet": "Neue Wallet erstellen", "wallet_list_create_new_wallet": "Neue Wallet erstellen",
@ -901,7 +901,7 @@
"wallet_menu": "Wallet-Menü", "wallet_menu": "Wallet-Menü",
"wallet_name": "Walletname", "wallet_name": "Walletname",
"wallet_name_exists": "Wallet mit diesem Namen existiert bereits", "wallet_name_exists": "Wallet mit diesem Namen existiert bereits",
"wallet_password_is_empty": "Brieftaschenkennwort ist leer. Brieftaschenkennwort sollte nicht leer sein", "wallet_password_is_empty": "Walletkennwort ist leer. Walletkennwort sollte nicht leer sein",
"wallet_recovery_height": "Erstellungshöhe", "wallet_recovery_height": "Erstellungshöhe",
"wallet_restoration_store_incorrect_seed_length": "Falsche Seed-Länge", "wallet_restoration_store_incorrect_seed_length": "Falsche Seed-Länge",
"wallet_seed": "Wallet-Seed", "wallet_seed": "Wallet-Seed",
@ -941,4 +941,4 @@
"you_will_get": "Konvertieren zu", "you_will_get": "Konvertieren zu",
"you_will_send": "Konvertieren von", "you_will_send": "Konvertieren von",
"yy": "YY" "yy": "YY"
} }

View file

@ -114,7 +114,7 @@
"change_currency": "Change Currency", "change_currency": "Change Currency",
"change_current_node": "Are you sure to change current node to ${node}?", "change_current_node": "Are you sure to change current node to ${node}?",
"change_current_node_title": "Change current node", "change_current_node_title": "Change current node",
"change_exchange_provider": "Change Exchange Provider", "change_exchange_provider": "Change Swap Provider",
"change_language": "Change language", "change_language": "Change language",
"change_language_to": "Change language to ${language}?", "change_language_to": "Change language to ${language}?",
"change_password": "Change password", "change_password": "Change password",
@ -281,8 +281,8 @@
"etherscan_history": "Etherscan history", "etherscan_history": "Etherscan history",
"event": "Event", "event": "Event",
"events": "Events", "events": "Events",
"exchange": "Exchange", "exchange": "Swap",
"exchange_incorrect_current_wallet_for_xmr": "If you want to exchange XMR from your Cake Wallet Monero balance, please switch to your Monero wallet first.", "exchange_incorrect_current_wallet_for_xmr": "If you want to swap XMR from your Cake Wallet Monero balance, please switch to your Monero wallet first.",
"exchange_new_template": "New template", "exchange_new_template": "New template",
"exchange_provider_unsupported": "${providerName} is no longer supported!", "exchange_provider_unsupported": "${providerName} is no longer supported!",
"exchange_result_confirm": "By pressing confirm, you will be sending ${fetchingLabel} ${from} from your wallet called ${walletName} to the address shown below. Or you can send from your external wallet to the below address/QR code.\n\nPlease press confirm to continue or go back to change the amounts.", "exchange_result_confirm": "By pressing confirm, you will be sending ${fetchingLabel} ${from} from your wallet called ${walletName} to the address shown below. Or you can send from your external wallet to the below address/QR code.\n\nPlease press confirm to continue or go back to change the amounts.",
@ -309,7 +309,7 @@
"fill_code": "Please fill in the verification code provided to your email", "fill_code": "Please fill in the verification code provided to your email",
"filter_by": "Filter by", "filter_by": "Filter by",
"first_wallet_text": "Awesome wallet for Monero, Bitcoin, Ethereum, Litecoin, and Haven", "first_wallet_text": "Awesome wallet for Monero, Bitcoin, Ethereum, Litecoin, and Haven",
"fixed_pair_not_supported": "This fixed pair is not supported with the selected exchanges", "fixed_pair_not_supported": "This fixed pair is not supported with the selected swap services",
"fixed_rate": "Fixed rate", "fixed_rate": "Fixed rate",
"fixed_rate_alert": "You will be able to enter receive amount when fixed rate mode is checked. Do you want to switch to fixed rate mode?", "fixed_rate_alert": "You will be able to enter receive amount when fixed rate mode is checked. Do you want to switch to fixed rate mode?",
"forgot_password": "Forgot Password", "forgot_password": "Forgot Password",
@ -441,7 +441,7 @@
"node_test": "Test", "node_test": "Test",
"nodes": "Nodes", "nodes": "Nodes",
"nodes_list_reset_to_default_message": "Are you sure that you want to reset settings to default?", "nodes_list_reset_to_default_message": "Are you sure that you want to reset settings to default?",
"none_of_selected_providers_can_exchange": "None of the selected providers can make this exchange", "none_of_selected_providers_can_exchange": "None of the selected providers can make this swap",
"noNFTYet": "No NFTs yet", "noNFTYet": "No NFTs yet",
"normal": "Normal", "normal": "Normal",
"note_optional": "Note (optional)", "note_optional": "Note (optional)",

View file

@ -281,8 +281,8 @@
"etherscan_history": "historia de etherscan", "etherscan_history": "historia de etherscan",
"event": "Evento", "event": "Evento",
"events": "Eventos", "events": "Eventos",
"exchange": "Intercambiar", "exchange": "Intercambio",
"exchange_incorrect_current_wallet_for_xmr": "Si desea intercambiar XMR de su saldo de Cake Wallet Monero, primero cambie a su billetera Monero.", "exchange_incorrect_current_wallet_for_xmr": "Si desea intercambiar XMR desde su billetera de pastel Monero Balance, primero cambie a su billetera Monero.",
"exchange_new_template": "Nueva plantilla", "exchange_new_template": "Nueva plantilla",
"exchange_provider_unsupported": "¡${providerName} ya no es compatible!", "exchange_provider_unsupported": "¡${providerName} ya no es compatible!",
"exchange_result_confirm": "Al presionar confirmar, enviará ${fetchingLabel} ${from} desde su billetera llamada ${walletName} a la dirección que se muestra a continuación. O puede enviar desde su billetera externa a la siguiente dirección / código QR anterior.\n\nPresione confirmar para continuar o regrese para cambiar los montos.", "exchange_result_confirm": "Al presionar confirmar, enviará ${fetchingLabel} ${from} desde su billetera llamada ${walletName} a la dirección que se muestra a continuación. O puede enviar desde su billetera externa a la siguiente dirección / código QR anterior.\n\nPresione confirmar para continuar o regrese para cambiar los montos.",
@ -309,7 +309,7 @@
"fill_code": "Por favor complete el código de verificación proporcionado a su correo electrónico", "fill_code": "Por favor complete el código de verificación proporcionado a su correo electrónico",
"filter_by": "Filtrado por", "filter_by": "Filtrado por",
"first_wallet_text": "Impresionante billetera para Monero, Bitcoin, Ethereum, Litecoin, y Haven", "first_wallet_text": "Impresionante billetera para Monero, Bitcoin, Ethereum, Litecoin, y Haven",
"fixed_pair_not_supported": "Este par fijo no es compatible con los intercambios seleccionados", "fixed_pair_not_supported": "Este par fijo no es compatible con los servicios de intercambio seleccionados",
"fixed_rate": "Tipo de interés fijo", "fixed_rate": "Tipo de interés fijo",
"fixed_rate_alert": "Podrá ingresar la cantidad recibida cuando el modo de tarifa fija esté marcado. ¿Quieres cambiar al modo de tarifa fija?", "fixed_rate_alert": "Podrá ingresar la cantidad recibida cuando el modo de tarifa fija esté marcado. ¿Quieres cambiar al modo de tarifa fija?",
"forgot_password": "Olvidé mi contraseña", "forgot_password": "Olvidé mi contraseña",
@ -439,7 +439,7 @@
"node_test": "Prueba", "node_test": "Prueba",
"nodes": "Nodos", "nodes": "Nodos",
"nodes_list_reset_to_default_message": "¿Está seguro de que desea restablecer la configuración predeterminada?", "nodes_list_reset_to_default_message": "¿Está seguro de que desea restablecer la configuración predeterminada?",
"none_of_selected_providers_can_exchange": "Ninguno de los proveedores seleccionados puede realizar este intercambio", "none_of_selected_providers_can_exchange": "Ninguno de los proveedores seleccionados puede hacer este intercambio",
"noNFTYet": "Aún no hay NFT", "noNFTYet": "Aún no hay NFT",
"normal": "Normal", "normal": "Normal",
"note_optional": "Nota (opcional)", "note_optional": "Nota (opcional)",

View file

@ -114,7 +114,7 @@
"change_currency": "Changer de Devise", "change_currency": "Changer de Devise",
"change_current_node": "Êtes vous certain de vouloir changer le nœud actuel pour ${node} ?", "change_current_node": "Êtes vous certain de vouloir changer le nœud actuel pour ${node} ?",
"change_current_node_title": "Changer le nœud actuel", "change_current_node_title": "Changer le nœud actuel",
"change_exchange_provider": "Changer de Plateforme d'Échange", "change_exchange_provider": "Changer le fournisseur d'échange",
"change_language": "Changer de langue", "change_language": "Changer de langue",
"change_language_to": "Changer la langue vers ${language} ?", "change_language_to": "Changer la langue vers ${language} ?",
"change_password": "Changer le mot de passe", "change_password": "Changer le mot de passe",
@ -282,7 +282,7 @@
"event": "Événement", "event": "Événement",
"events": "Événements", "events": "Événements",
"exchange": "Échanger", "exchange": "Échanger",
"exchange_incorrect_current_wallet_for_xmr": "Si vous souhaitez échanger des XMR du solde Monero de votre Cake Wallet, merci de sélectionner votre portefeuille (wallet) Monero au préalable.", "exchange_incorrect_current_wallet_for_xmr": "Si vous souhaitez échanger XMR à partir de votre balance monero portefeuille de gâteau, veuillez d'abord passer à votre portefeuille Monero.",
"exchange_new_template": "Nouveau modèle d'échange", "exchange_new_template": "Nouveau modèle d'échange",
"exchange_provider_unsupported": "${providerName} n'est plus pris en charge !", "exchange_provider_unsupported": "${providerName} n'est plus pris en charge !",
"exchange_result_confirm": "En pressant confirmer, vous enverrez ${fetchingLabel} ${from} depuis votre portefeuille (wallet) nommé ${walletName} vers l'adresse ci-dessous. Vous pouvez aussi envoyer depuis votre portefeuille externe vers l'adresse/QR code ci-dessous.\n\nMerci d'appuyer sur confirmer pour continuer ou retournez en arrière pour modifier les montants.", "exchange_result_confirm": "En pressant confirmer, vous enverrez ${fetchingLabel} ${from} depuis votre portefeuille (wallet) nommé ${walletName} vers l'adresse ci-dessous. Vous pouvez aussi envoyer depuis votre portefeuille externe vers l'adresse/QR code ci-dessous.\n\nMerci d'appuyer sur confirmer pour continuer ou retournez en arrière pour modifier les montants.",
@ -309,7 +309,7 @@
"fill_code": "Veuillez remplir le code de vérification fourni sur votre e-mail", "fill_code": "Veuillez remplir le code de vérification fourni sur votre e-mail",
"filter_by": "Filtrer par", "filter_by": "Filtrer par",
"first_wallet_text": "Super portefeuille (wallet) pour Monero, Bitcoin, Ethereum, Litecoin et Haven", "first_wallet_text": "Super portefeuille (wallet) pour Monero, Bitcoin, Ethereum, Litecoin et Haven",
"fixed_pair_not_supported": "Cette paire fixe n'est pas prise en charge avec les échanges sélectionnés", "fixed_pair_not_supported": "Cette paire fixe n'est pas prise en charge avec les services d'échange sélectionnés",
"fixed_rate": "Taux fixe", "fixed_rate": "Taux fixe",
"fixed_rate_alert": "Vous aurez la possibilité de rentrer le montant reçu lorsque le mode taux fixe est sélectionné. Souhaitez vous basculer en mode taux fixe ?", "fixed_rate_alert": "Vous aurez la possibilité de rentrer le montant reçu lorsque le mode taux fixe est sélectionné. Souhaitez vous basculer en mode taux fixe ?",
"forgot_password": "Mot de passe oublié", "forgot_password": "Mot de passe oublié",
@ -439,7 +439,7 @@
"node_test": "Tester", "node_test": "Tester",
"nodes": "Nœuds", "nodes": "Nœuds",
"nodes_list_reset_to_default_message": "Êtes vous certain de vouloir revenir aux réglages par défaut ?", "nodes_list_reset_to_default_message": "Êtes vous certain de vouloir revenir aux réglages par défaut ?",
"none_of_selected_providers_can_exchange": "Aucun des fournisseurs sélectionnés ne peut effectuer cet échange", "none_of_selected_providers_can_exchange": "Aucun des fournisseurs sélectionnés ne peut faire cet échange",
"noNFTYet": "Pas encore de NFT", "noNFTYet": "Pas encore de NFT",
"normal": "Normal", "normal": "Normal",
"note_optional": "Note (optionnelle)", "note_optional": "Note (optionnelle)",

View file

@ -114,7 +114,7 @@
"change_currency": "Canja Kuɗi", "change_currency": "Canja Kuɗi",
"change_current_node": "Kuna tabbatar kuna so ku canja node yanzu zuwa ${node}?", "change_current_node": "Kuna tabbatar kuna so ku canja node yanzu zuwa ${node}?",
"change_current_node_title": "Canja node yanzu", "change_current_node_title": "Canja node yanzu",
"change_exchange_provider": "Canza Mai Bayar da Musanya", "change_exchange_provider": "Canza mai canzawa",
"change_language": "canja harshen", "change_language": "canja harshen",
"change_language_to": "canja harshen zuwa ${language}?", "change_language_to": "canja harshen zuwa ${language}?",
"change_password": "Canza kalmar shiga", "change_password": "Canza kalmar shiga",
@ -281,8 +281,8 @@
"etherscan_history": "Etherscan tarihin kowane zamani", "etherscan_history": "Etherscan tarihin kowane zamani",
"event": "Lamarin", "event": "Lamarin",
"events": "Abubuwan da suka faru", "events": "Abubuwan da suka faru",
"exchange": "Exchange", "exchange": "Musya",
"exchange_incorrect_current_wallet_for_xmr": "Idan kana son musanya XMR daga ma'aunin Cake Wallet Monero, da fatan za a fara canza wallet ɗin Monero ɗin ku.", "exchange_incorrect_current_wallet_for_xmr": "Idan kana son canza XMR daga walat ɗin Bed Wallet ɗinka, da fatan za a canza zuwa walat ɗinku na Monero.",
"exchange_new_template": "Sabon template", "exchange_new_template": "Sabon template",
"exchange_provider_unsupported": "${providerName}", "exchange_provider_unsupported": "${providerName}",
"exchange_result_confirm": "Ta danna tabbatarwa, zaku aika ${fetchingLabel} ${from} daga walat ɗin ku mai suna ${walletName} zuwa address dake kasa. Ko zaka iya aika daga kwalinku na external zuwa address/QR code dake kasa.\n\nDon Allah shigar da confirm don ci gaba ko dawo ka canja adadinku.", "exchange_result_confirm": "Ta danna tabbatarwa, zaku aika ${fetchingLabel} ${from} daga walat ɗin ku mai suna ${walletName} zuwa address dake kasa. Ko zaka iya aika daga kwalinku na external zuwa address/QR code dake kasa.\n\nDon Allah shigar da confirm don ci gaba ko dawo ka canja adadinku.",
@ -309,7 +309,7 @@
"fill_code": "Da fatan za a cika lambar tabbatarwa da aka bayar zuwa imel ɗin ku", "fill_code": "Da fatan za a cika lambar tabbatarwa da aka bayar zuwa imel ɗin ku",
"filter_by": "Tace ta", "filter_by": "Tace ta",
"first_wallet_text": "Aikace-aikacen e-wallet ga Monero, Bitcoin, Ethereum, Litecoin, da kuma Haven", "first_wallet_text": "Aikace-aikacen e-wallet ga Monero, Bitcoin, Ethereum, Litecoin, da kuma Haven",
"fixed_pair_not_supported": "Wannan kafaffen guda biyu ba shi da tallafi tare da zaɓaɓɓun musayar", "fixed_pair_not_supported": "Ba a tallafa wa wannan adireshin da aka zaɓi tare da zaɓin siye ba",
"fixed_rate": "Kafaffen ƙima", "fixed_rate": "Kafaffen ƙima",
"fixed_rate_alert": "Za ku iya shigar da adadin karɓa lokacin da aka duba ƙayyadadden zaɓin ƙimar kuɗi. Kuna so ku canza zuwa ƙayyadadden yanayin ƙimar kuɗi?", "fixed_rate_alert": "Za ku iya shigar da adadin karɓa lokacin da aka duba ƙayyadadden zaɓin ƙimar kuɗi. Kuna so ku canza zuwa ƙayyadadden yanayin ƙimar kuɗi?",
"forgot_password": "Manta Kalmar wucewa", "forgot_password": "Manta Kalmar wucewa",
@ -439,7 +439,7 @@
"node_test": "Gwaji", "node_test": "Gwaji",
"nodes": "Nodes", "nodes": "Nodes",
"nodes_list_reset_to_default_message": "Kuna tabbatar kuna so ku sake saitunan zuwa default?", "nodes_list_reset_to_default_message": "Kuna tabbatar kuna so ku sake saitunan zuwa default?",
"none_of_selected_providers_can_exchange": "Babu ɗaya daga cikin zaɓaɓɓun masu samarwa da zai iya yin wannan musayar", "none_of_selected_providers_can_exchange": "Babu wani daga cikin masu siye da aka zaɓa na iya yin wannan musan",
"noNFTYet": "Babu NFTs tukuna", "noNFTYet": "Babu NFTs tukuna",
"normal": "Na al'ada", "normal": "Na al'ada",
"note_optional": "Bayani (optional)", "note_optional": "Bayani (optional)",

View file

@ -114,7 +114,7 @@
"change_currency": "मुद्रा परिवर्तन करें", "change_currency": "मुद्रा परिवर्तन करें",
"change_current_node": "क्या आप वर्तमान नोड को बदलना सुनिश्चित करते हैं ${node}?", "change_current_node": "क्या आप वर्तमान नोड को बदलना सुनिश्चित करते हैं ${node}?",
"change_current_node_title": "वर्तमान नोड बदलें", "change_current_node_title": "वर्तमान नोड बदलें",
"change_exchange_provider": "एक्सचेंज प्रदाता बदलें", "change_exchange_provider": "स्वैप प्रदाता बदलें",
"change_language": "भाषा बदलो", "change_language": "भाषा बदलो",
"change_language_to": "को भाषा बदलें ${language}?", "change_language_to": "को भाषा बदलें ${language}?",
"change_password": "पासवर्ड बदलें", "change_password": "पासवर्ड बदलें",
@ -281,8 +281,8 @@
"etherscan_history": "इथरस्कैन इतिहास", "etherscan_history": "इथरस्कैन इतिहास",
"event": "आयोजन", "event": "आयोजन",
"events": "आयोजन", "events": "आयोजन",
"exchange": "अदला बदली", "exchange": "बदलना",
"exchange_incorrect_current_wallet_for_xmr": "यदि आप अपने केक वॉलेट मोनेरो बैलेंस से एक्सएमआर का आदान-प्रदान करना चाहते हैं, तो कृपया अपने मोनेरो वॉलेट में जाएं।", "exchange_incorrect_current_wallet_for_xmr": "यदि आप अपने केक वॉलेट मोनेरो बैलेंस से XMR को स्वैप करना चाहते हैं, तो कृपया पहले अपने मोनेरो वॉलेट पर स्विच करें।",
"exchange_new_template": "नया टेम्पलेट", "exchange_new_template": "नया टेम्पलेट",
"exchange_provider_unsupported": "${providerName} अब समर्थित नहीं है!", "exchange_provider_unsupported": "${providerName} अब समर्थित नहीं है!",
"exchange_result_confirm": "पुष्टि दबाकर, आप भेज रहे होंगे ${fetchingLabel} ${from} अपने बटुए से ${walletName} नीचे दिखाए गए पते पर। या आप अपने बाहरी वॉलेट से नीचे के पते पर भेज सकते हैं / क्यूआर कोड पर भेज सकते हैं।\n\nकृपया जारी रखने या राशि बदलने के लिए वापस जाने के लिए पुष्टि करें दबाएं.", "exchange_result_confirm": "पुष्टि दबाकर, आप भेज रहे होंगे ${fetchingLabel} ${from} अपने बटुए से ${walletName} नीचे दिखाए गए पते पर। या आप अपने बाहरी वॉलेट से नीचे के पते पर भेज सकते हैं / क्यूआर कोड पर भेज सकते हैं।\n\nकृपया जारी रखने या राशि बदलने के लिए वापस जाने के लिए पुष्टि करें दबाएं.",
@ -309,7 +309,7 @@
"fill_code": "कृपया अपने ईमेल पर प्रदान किया गया सत्यापन कोड भरें", "fill_code": "कृपया अपने ईमेल पर प्रदान किया गया सत्यापन कोड भरें",
"filter_by": "के द्वारा छनित", "filter_by": "के द्वारा छनित",
"first_wallet_text": "Monero, Bitcoin, Ethereum, Litecoin, और Haven के लिए बहुत बढ़िया बटुआ", "first_wallet_text": "Monero, Bitcoin, Ethereum, Litecoin, और Haven के लिए बहुत बढ़िया बटुआ",
"fixed_pair_not_supported": "यह निश्चित जोड़ी चयनित एक्सचेंजों के साथ समर्थित नहीं है", "fixed_pair_not_supported": "यह निश्चित जोड़ी चयनित स्वैप सेवाओं के साथ समर्थित नहीं है",
"fixed_rate": "निर्धारित दर", "fixed_rate": "निर्धारित दर",
"fixed_rate_alert": "फिक्स्ड रेट मोड की जांच करने पर आप प्राप्त राशि दर्ज कर पाएंगे। क्या आप निश्चित दर मोड पर स्विच करना चाहते हैं?", "fixed_rate_alert": "फिक्स्ड रेट मोड की जांच करने पर आप प्राप्त राशि दर्ज कर पाएंगे। क्या आप निश्चित दर मोड पर स्विच करना चाहते हैं?",
"forgot_password": "पासवर्ड भूल गए", "forgot_password": "पासवर्ड भूल गए",
@ -439,7 +439,7 @@
"node_test": "परीक्षा", "node_test": "परीक्षा",
"nodes": "नोड्स", "nodes": "नोड्स",
"nodes_list_reset_to_default_message": "क्या आप वाकई सेटिंग को डिफ़ॉल्ट पर रीसेट करना चाहते हैं?", "nodes_list_reset_to_default_message": "क्या आप वाकई सेटिंग को डिफ़ॉल्ट पर रीसेट करना चाहते हैं?",
"none_of_selected_providers_can_exchange": "चयनित प्रदाताओं में से कोई भी इस एक्सचेंज को नहीं बना सकता", "none_of_selected_providers_can_exchange": "चयनित प्रदाता में से कोई भी यह स्वैप नहीं कर सकता है",
"noNFTYet": "अभी तक कोई एनएफटी नहीं", "noNFTYet": "अभी तक कोई एनएफटी नहीं",
"normal": "सामान्य", "normal": "सामान्य",
"note_optional": "नोट (वैकल्पिक)", "note_optional": "नोट (वैकल्पिक)",

View file

@ -114,7 +114,7 @@
"change_currency": "Promijenite valutu", "change_currency": "Promijenite valutu",
"change_current_node": "Jeste li sigurni da želite promijeniti trenutni node na ${node}?", "change_current_node": "Jeste li sigurni da želite promijeniti trenutni node na ${node}?",
"change_current_node_title": "Promijeni trenutni node", "change_current_node_title": "Promijeni trenutni node",
"change_exchange_provider": "Promjena davatelja usluge razmjene", "change_exchange_provider": "Promijenite davatelja zamjene",
"change_language": "Promijeni jezik", "change_language": "Promijeni jezik",
"change_language_to": "Promijeni jezik u ${language}?", "change_language_to": "Promijeni jezik u ${language}?",
"change_password": "Promijeni lozinku", "change_password": "Promijeni lozinku",
@ -281,8 +281,8 @@
"etherscan_history": "Etherscan povijest", "etherscan_history": "Etherscan povijest",
"event": "Događaj", "event": "Događaj",
"events": "Događaji", "events": "Događaji",
"exchange": "Razmijeni", "exchange": "Zamjena",
"exchange_incorrect_current_wallet_for_xmr": "Ako želite razmijeniti XMR s vlastitog Monero računa na Cake Wallet novčaniku, molimo prvo se prebacite na svoj Monero novčanik.", "exchange_incorrect_current_wallet_for_xmr": "Ako želite zamijeniti XMR iz vašeg novčanika za kolač Monero, prvo se prebacite na svoj novčanik Monero.",
"exchange_new_template": "Novi predložak", "exchange_new_template": "Novi predložak",
"exchange_provider_unsupported": "${providerName} više nije podržan!", "exchange_provider_unsupported": "${providerName} više nije podržan!",
"exchange_result_confirm": "Pritiskom na potvrdi, poslat ćete ${fetchingLabel} ${from} sa svog novčanika pod nazivom ${walletName} na adresu prikazanu ispod ili iznos možete poslati s vanjskog novčanika na niže navedenu adresu. /QR code.\n\nMolimo potvrdite za nastavak ili se vratite natrag za promjenu iznosa.", "exchange_result_confirm": "Pritiskom na potvrdi, poslat ćete ${fetchingLabel} ${from} sa svog novčanika pod nazivom ${walletName} na adresu prikazanu ispod ili iznos možete poslati s vanjskog novčanika na niže navedenu adresu. /QR code.\n\nMolimo potvrdite za nastavak ili se vratite natrag za promjenu iznosa.",
@ -309,7 +309,7 @@
"fill_code": "Molimo vas da ispunite kontrolni kod koji ste dobili na svojoj e-pošti", "fill_code": "Molimo vas da ispunite kontrolni kod koji ste dobili na svojoj e-pošti",
"filter_by": "Filtrirati po", "filter_by": "Filtrirati po",
"first_wallet_text": "Odličan novčanik za Monero, Bitcoin, Ethereum, Litecoin, i Haven", "first_wallet_text": "Odličan novčanik za Monero, Bitcoin, Ethereum, Litecoin, i Haven",
"fixed_pair_not_supported": "Ovaj fiksni par nije podržan s odabranim burzama", "fixed_pair_not_supported": "Ovaj fiksni par nije podržan s odabranim SWAP uslugama",
"fixed_rate": "Fiksna stopa", "fixed_rate": "Fiksna stopa",
"fixed_rate_alert": "Moći ćete unijeti iznos koji želite primiti nakon što označite način rada fiksne stope. Želite li se prebaciti na način rada fiksne stope?", "fixed_rate_alert": "Moći ćete unijeti iznos koji želite primiti nakon što označite način rada fiksne stope. Želite li se prebaciti na način rada fiksne stope?",
"forgot_password": "Zaboravljena lozinka", "forgot_password": "Zaboravljena lozinka",
@ -439,7 +439,7 @@
"node_test": "Provjeri", "node_test": "Provjeri",
"nodes": "Nodes", "nodes": "Nodes",
"nodes_list_reset_to_default_message": "Jeste li sigurni da se želite vratiti na početne postavke?", "nodes_list_reset_to_default_message": "Jeste li sigurni da se želite vratiti na početne postavke?",
"none_of_selected_providers_can_exchange": "Niti jedan od odabranih pružatelja usluga ne može izvršiti ovu razmjenu", "none_of_selected_providers_can_exchange": "Nijedan od odabranih pružatelja usluga ne može napraviti ovu zamjenu",
"noNFTYet": "Još nema NFT-ova", "noNFTYet": "Još nema NFT-ova",
"normal": "Normalno", "normal": "Normalno",
"note_optional": "Poruka (nije obvezno)", "note_optional": "Poruka (nije obvezno)",

View file

@ -114,7 +114,7 @@
"change_currency": "Փոխել արժույթը", "change_currency": "Փոխել արժույթը",
"change_current_node": "Վստահ եք, որ ցանկանում եք փոխել ընթացիկ հանգույցը ${node}?", "change_current_node": "Վստահ եք, որ ցանկանում եք փոխել ընթացիկ հանգույցը ${node}?",
"change_current_node_title": "Փոխել ընթացիկ հանգույցը", "change_current_node_title": "Փոխել ընթացիկ հանգույցը",
"change_exchange_provider": "Փոխել փոխանակման մատակարարին", "change_exchange_provider": "Փոխեք փոխանակման մատակարարը",
"change_language": "Փոխել լեզուն", "change_language": "Փոխել լեզուն",
"change_language_to": "Փոխել լեզուն ${language}?", "change_language_to": "Փոխել լեզուն ${language}?",
"change_password": "Փոխել գաղտնաբառը", "change_password": "Փոխել գաղտնաբառը",
@ -281,8 +281,8 @@
"etherscan_history": "Etherscan պատմություն", "etherscan_history": "Etherscan պատմություն",
"event": "Իրադարձություն", "event": "Իրադարձություն",
"events": "Իրադարձություններ", "events": "Իրադարձություններ",
"exchange": "Փոխել", "exchange": "Փոխանակել",
"exchange_incorrect_current_wallet_for_xmr": "Եթե դուք ցանկանում եք փոխանակել XMR ձեր Cake Wallet Monero հաշվեհամարից, խնդրում ենք անցնել ձեր Monero հաշվեհամարին", "exchange_incorrect_current_wallet_for_xmr": "Եթե ​​ցանկանում եք փոխանակել XMR ձեր տորթի դրամապանակից Monero Relandal- ից, խնդրում ենք նախ անցնել ձեր Monero դրամապանակին:",
"exchange_new_template": "Նոր տեսակ", "exchange_new_template": "Նոր տեսակ",
"exchange_provider_unsupported": "${providerName} այլևս չի ապահովվում", "exchange_provider_unsupported": "${providerName} այլևս չի ապահովվում",
"exchange_result_confirm": "Սեղմելով հաստատել, դուք կուղարկեք ${fetchingLabel} ${from} ձեր հաշվեհամարից ${walletName} հետևյալ հասցեին: Կամ կարող եք ուղարկել ձեր արտաքին հաշվեհամարից հետևյալ հասցեին/QR կոդին:\n\nԽնդրում ենք սեղմել հաստատել կամ վերադառնալ գումարը փոխելու համար", "exchange_result_confirm": "Սեղմելով հաստատել, դուք կուղարկեք ${fetchingLabel} ${from} ձեր հաշվեհամարից ${walletName} հետևյալ հասցեին: Կամ կարող եք ուղարկել ձեր արտաքին հաշվեհամարից հետևյալ հասցեին/QR կոդին:\n\nԽնդրում ենք սեղմել հաստատել կամ վերադառնալ գումարը փոխելու համար",
@ -309,7 +309,7 @@
"fill_code": "Խնդրում ենք լրացնել հաստատման կոդը ձեր էլեկտրոնային փոստում", "fill_code": "Խնդրում ենք լրացնել հաստատման կոդը ձեր էլեկտրոնային փոստում",
"filter_by": "Ֆիլտրել ըստ", "filter_by": "Ֆիլտրել ըստ",
"first_wallet_text": "Հիանալի հաշվեհամար Monero, Bitcoin, Ethereum, Litecoin և Haven արժույթների համար", "first_wallet_text": "Հիանալի հաշվեհամար Monero, Bitcoin, Ethereum, Litecoin և Haven արժույթների համար",
"fixed_pair_not_supported": "Այս ֆիքսված զույգը չի ապահովվում ընտրված փոխանակման կետերում", "fixed_pair_not_supported": "Այս ֆիքսված զույգը չի ապահովվում ընտրված փոխանակման ծառայություններ",
"fixed_rate": "Ֆիքսված փոխարժեք", "fixed_rate": "Ֆիքսված փոխարժեք",
"fixed_rate_alert": "Դուք կկարողանաք մուտքագրել ստացվող գումարը, երբ ֆիքսված փոխարժեքի ռեժիմը միացված է: Դուք ցանկանում եք անցնել ֆիքսված փոխարժեքի ռեժիմին?", "fixed_rate_alert": "Դուք կկարողանաք մուտքագրել ստացվող գումարը, երբ ֆիքսված փոխարժեքի ռեժիմը միացված է: Դուք ցանկանում եք անցնել ֆիքսված փոխարժեքի ռեժիմին?",
"forgot_password": "Մոռացել եմ գաղտնաբառը", "forgot_password": "Մոռացել եմ գաղտնաբառը",
@ -431,7 +431,7 @@
"node_test": "Փորձարկում", "node_test": "Փորձարկում",
"nodes": "Հանգույցներ", "nodes": "Հանգույցներ",
"nodes_list_reset_to_default_message": "Վերակայվում եք կարգավորումները լռությամբ?", "nodes_list_reset_to_default_message": "Վերակայվում եք կարգավորումները լռությամբ?",
"none_of_selected_providers_can_exchange": "Ոչ մի ընտրված մատակարար չի կարող այս փոխանակումը կատարել", "none_of_selected_providers_can_exchange": "Ընտրված մատակարարներից ոչ մեկը չի կարող կատարել այս փոխանակումը",
"noNFTYet": "Դեռ ոչ մի NFT", "noNFTYet": "Դեռ ոչ մի NFT",
"normal": "Նորմալ", "normal": "Նորմալ",
"note_optional": "Նշում (ոչ պարտադիր)", "note_optional": "Նշում (ոչ պարտադիր)",

View file

@ -114,7 +114,7 @@
"change_currency": "Ganti Mata Uang", "change_currency": "Ganti Mata Uang",
"change_current_node": "Apakah Anda yakin ingin mengubah node saat ini menjadi ${node}?", "change_current_node": "Apakah Anda yakin ingin mengubah node saat ini menjadi ${node}?",
"change_current_node_title": "Ubah node saat ini", "change_current_node_title": "Ubah node saat ini",
"change_exchange_provider": "Ganti Penyedia Tukar", "change_exchange_provider": "Ubah penyedia swap",
"change_language": "Ganti bahasa", "change_language": "Ganti bahasa",
"change_language_to": "Ganti bahasa ke ${language}?", "change_language_to": "Ganti bahasa ke ${language}?",
"change_password": "Ubah kata sandi", "change_password": "Ubah kata sandi",
@ -281,8 +281,8 @@
"etherscan_history": "Sejarah Etherscan", "etherscan_history": "Sejarah Etherscan",
"event": "Peristiwa", "event": "Peristiwa",
"events": "Acara", "events": "Acara",
"exchange": "Tukar", "exchange": "Menukar",
"exchange_incorrect_current_wallet_for_xmr": "Jika Anda ingin menukar XMR dari saldo Monero Cake Wallet Anda, silakan beralih ke dompet Monero Anda terlebih dahulu.", "exchange_incorrect_current_wallet_for_xmr": "Jika Anda ingin bertukar XMR dari Saldo Monero Dompet Kue Anda, silakan beralih ke Monero Wallet Anda terlebih dahulu.",
"exchange_new_template": "Template baru", "exchange_new_template": "Template baru",
"exchange_provider_unsupported": "${providerName} tidak lagi didukung!", "exchange_provider_unsupported": "${providerName} tidak lagi didukung!",
"exchange_result_confirm": "Dengan menekan tombol konfirmasi, Anda akan mengirimkan ${fetchingLabel} ${from} dari dompet Anda yang disebut ${walletName} ke alamat yang ditampilkan di bawah. Anda juga dapat mengirim dari dompet eksternal Anda ke alamat/QR code di bawah.\n\nSilakan tekan konfirmasi untuk melanjutkan atau kembali untuk mengubah jumlah.", "exchange_result_confirm": "Dengan menekan tombol konfirmasi, Anda akan mengirimkan ${fetchingLabel} ${from} dari dompet Anda yang disebut ${walletName} ke alamat yang ditampilkan di bawah. Anda juga dapat mengirim dari dompet eksternal Anda ke alamat/QR code di bawah.\n\nSilakan tekan konfirmasi untuk melanjutkan atau kembali untuk mengubah jumlah.",
@ -309,7 +309,7 @@
"fill_code": "Silakan isi kode verifikasi yang diterima di email Anda", "fill_code": "Silakan isi kode verifikasi yang diterima di email Anda",
"filter_by": "Filter berdasarkan", "filter_by": "Filter berdasarkan",
"first_wallet_text": "Dompet luar biasa untuk Monero, Bitcoin, Ethereum, Litecoin, dan Haven", "first_wallet_text": "Dompet luar biasa untuk Monero, Bitcoin, Ethereum, Litecoin, dan Haven",
"fixed_pair_not_supported": "Pasangan tetap ini tidak didukung dengan bursa yang dipilih", "fixed_pair_not_supported": "Pasangan tetap ini tidak didukung dengan layanan swap yang dipilih",
"fixed_rate": "Rate tetap", "fixed_rate": "Rate tetap",
"fixed_rate_alert": "Anda akan dapat memasukkan jumlah penerimaan saat mode rate tetap dicentang. Apakah Anda ingin beralih ke mode rate tetap?", "fixed_rate_alert": "Anda akan dapat memasukkan jumlah penerimaan saat mode rate tetap dicentang. Apakah Anda ingin beralih ke mode rate tetap?",
"forgot_password": "Lupa Kata Sandi", "forgot_password": "Lupa Kata Sandi",
@ -439,7 +439,7 @@
"node_test": "Uji", "node_test": "Uji",
"nodes": "Node", "nodes": "Node",
"nodes_list_reset_to_default_message": "Apakah Anda yakin ingin mengatur ulang pengaturan ke default?", "nodes_list_reset_to_default_message": "Apakah Anda yakin ingin mengatur ulang pengaturan ke default?",
"none_of_selected_providers_can_exchange": "Tidak ada dari penyedia yang dipilih yang dapat melakukan pertukaran ini", "none_of_selected_providers_can_exchange": "Tak satu pun dari penyedia yang dipilih dapat melakukan pertukaran ini",
"noNFTYet": "Belum ada NFT", "noNFTYet": "Belum ada NFT",
"normal": "Normal", "normal": "Normal",
"note_optional": "Catatan (opsional)", "note_optional": "Catatan (opsional)",

View file

@ -114,7 +114,7 @@
"change_currency": "Cambia Valuta", "change_currency": "Cambia Valuta",
"change_current_node": "Sei sicuro di voler cambiare il nodo corrente con ${node}?", "change_current_node": "Sei sicuro di voler cambiare il nodo corrente con ${node}?",
"change_current_node_title": "Cambia nodo corrente", "change_current_node_title": "Cambia nodo corrente",
"change_exchange_provider": "Cambia Exchange", "change_exchange_provider": "Provider di swap di cambiamento",
"change_language": "Cambia lingua", "change_language": "Cambia lingua",
"change_language_to": "Cambiare lingua in ${language}?", "change_language_to": "Cambiare lingua in ${language}?",
"change_password": "Cambia password", "change_password": "Cambia password",
@ -282,8 +282,8 @@
"etherscan_history": "Storia Etherscan", "etherscan_history": "Storia Etherscan",
"event": "Evento", "event": "Evento",
"events": "Eventi", "events": "Eventi",
"exchange": "Scambia", "exchange": "Scambio",
"exchange_incorrect_current_wallet_for_xmr": "Se vuoi scambiare XMR dal tuo saldo Cake Wallet Monero, gentilmente passa al tuo portafoglio Monero.", "exchange_incorrect_current_wallet_for_xmr": "Se vuoi scambiare XMR dal tuo portafoglio di torta Monero Balance, si prega di passare prima al portafoglio Monero.",
"exchange_new_template": "Nuovo modello", "exchange_new_template": "Nuovo modello",
"exchange_provider_unsupported": "${providerName} non è più supportato!", "exchange_provider_unsupported": "${providerName} non è più supportato!",
"exchange_result_confirm": "Cliccando su Conferma, invierai ${fetchingLabel} ${from} dal tuo portafoglio chiamato ${walletName} all'indirizzo mostrato qui in basso. O puoi inviare dal tuo portafoglio esterno all'indirizzo/codice QR mostrato in basso.\n\nGentilmente clicca su Conferma per continuare o torna indietro per cambiare l'ammontare.", "exchange_result_confirm": "Cliccando su Conferma, invierai ${fetchingLabel} ${from} dal tuo portafoglio chiamato ${walletName} all'indirizzo mostrato qui in basso. O puoi inviare dal tuo portafoglio esterno all'indirizzo/codice QR mostrato in basso.\n\nGentilmente clicca su Conferma per continuare o torna indietro per cambiare l'ammontare.",
@ -310,7 +310,7 @@
"fill_code": "Compila il codice di verifica fornito alla tua email", "fill_code": "Compila il codice di verifica fornito alla tua email",
"filter_by": "Filtrirati po", "filter_by": "Filtrirati po",
"first_wallet_text": "Portafoglio fantastico per Monero, Bitcoin, Ethereum, Litecoin, e Haven", "first_wallet_text": "Portafoglio fantastico per Monero, Bitcoin, Ethereum, Litecoin, e Haven",
"fixed_pair_not_supported": "Questa coppia fissa non è supportata con gli scambi selezionati", "fixed_pair_not_supported": "Questa coppia fissa non è supportata con i servizi di swap selezionati",
"fixed_rate": "Tasso fisso", "fixed_rate": "Tasso fisso",
"fixed_rate_alert": "Potrai inserire l'ammontare da ricevere quando il tasso è fisso. Vuoi cambiare alla modalità tasso fisso?", "fixed_rate_alert": "Potrai inserire l'ammontare da ricevere quando il tasso è fisso. Vuoi cambiare alla modalità tasso fisso?",
"forgot_password": "Password dimenticata", "forgot_password": "Password dimenticata",
@ -440,7 +440,7 @@
"node_test": "Test", "node_test": "Test",
"nodes": "Nodi", "nodes": "Nodi",
"nodes_list_reset_to_default_message": "Sei sicuro di voler ripristinare le impostazioni predefinite?", "nodes_list_reset_to_default_message": "Sei sicuro di voler ripristinare le impostazioni predefinite?",
"none_of_selected_providers_can_exchange": "Nessuno dei fornitori selezionati può effettuare questo scambio", "none_of_selected_providers_can_exchange": "Nessuno dei provider selezionati può fare questo scambio",
"noNFTYet": "Nessun NFT ancora", "noNFTYet": "Nessun NFT ancora",
"normal": "Normale", "normal": "Normale",
"note_optional": "Nota (opzionale)", "note_optional": "Nota (opzionale)",

View file

@ -114,7 +114,7 @@
"change_currency": "通貨を変更する", "change_currency": "通貨を変更する",
"change_current_node": "現在のノードを変更してよろしいですか ${node}?", "change_current_node": "現在のノードを変更してよろしいですか ${node}?",
"change_current_node_title": "現在のノードを変更する", "change_current_node_title": "現在のノードを変更する",
"change_exchange_provider": "Exchangeプロバイダーの変更", "change_exchange_provider": "スワッププロバイダーを変更します",
"change_language": "言語を変えてください", "change_language": "言語を変えてください",
"change_language_to": "言語を変更 ${language}?", "change_language_to": "言語を変更 ${language}?",
"change_password": "パスワードを変更する", "change_password": "パスワードを変更する",
@ -281,8 +281,8 @@
"etherscan_history": "イーサスキャンの歴史", "etherscan_history": "イーサスキャンの歴史",
"event": "イベント", "event": "イベント",
"events": "イベント", "events": "イベント",
"exchange": "交換する", "exchange": "スワップ",
"exchange_incorrect_current_wallet_for_xmr": "Cake Wallet Moneroの残高からXMRを交換する場合は、最初にMoneroウォレットに切り替えてください。", "exchange_incorrect_current_wallet_for_xmr": "XMRをケーキウォレットモネロバランスから交換したい場合は、最初にMoneroウォレットに切り替えてください。",
"exchange_new_template": "新しいテンプレート", "exchange_new_template": "新しいテンプレート",
"exchange_provider_unsupported": "${providerName}はサポートされなくなりました!", "exchange_provider_unsupported": "${providerName}はサポートされなくなりました!",
"exchange_result_confirm": "確認を押すと、送信されます ${fetchingLabel} ${from} と呼ばれるあなたの財布から ${walletName} 下記の住所へ。 または、外部ウォレットから以下のアドレスに送信することもできます/ QRコードに送信できます.\n\n確認を押して続行するか、戻って金額を変更してください.", "exchange_result_confirm": "確認を押すと、送信されます ${fetchingLabel} ${from} と呼ばれるあなたの財布から ${walletName} 下記の住所へ。 または、外部ウォレットから以下のアドレスに送信することもできます/ QRコードに送信できます.\n\n確認を押して続行するか、戻って金額を変更してください.",
@ -309,7 +309,7 @@
"fill_code": "メールアドレスに記載されている確認コードを入力してください", "fill_code": "メールアドレスに記載されている確認コードを入力してください",
"filter_by": "でフィルタリング", "filter_by": "でフィルタリング",
"first_wallet_text": "Monero、Bitcoin、Ethereum、Litecoin、Haven用の素晴らしいウォレット", "first_wallet_text": "Monero、Bitcoin、Ethereum、Litecoin、Haven用の素晴らしいウォレット",
"fixed_pair_not_supported": "この固定ペアは、選択したエクスチェンジではサポートされていません", "fixed_pair_not_supported": "この固定ペアは、選択したスワップサービスではサポートされていません",
"fixed_rate": "固定金利", "fixed_rate": "固定金利",
"fixed_rate_alert": "固定金利モードにチェックを入れると、受取額を入力できるようになります。 固定金利モードに切り替えますか?", "fixed_rate_alert": "固定金利モードにチェックを入れると、受取額を入力できるようになります。 固定金利モードに切り替えますか?",
"forgot_password": "パスワードを忘れた", "forgot_password": "パスワードを忘れた",
@ -440,7 +440,7 @@
"node_test": "テスト", "node_test": "テスト",
"nodes": "ノード", "nodes": "ノード",
"nodes_list_reset_to_default_message": "設定をデフォルトにリセットしてもよろしいですか?", "nodes_list_reset_to_default_message": "設定をデフォルトにリセットしてもよろしいですか?",
"none_of_selected_providers_can_exchange": "選択したプロバイダーはいずれもこの交換を行うことができません", "none_of_selected_providers_can_exchange": "選択したプロバイダーはいずれもこのスワップを作成できません",
"noNFTYet": "NFTはまだありません", "noNFTYet": "NFTはまだありません",
"normal": "普通", "normal": "普通",
"note_optional": "注(オプション)", "note_optional": "注(オプション)",

View file

@ -114,7 +114,7 @@
"change_currency": "통화 변경", "change_currency": "통화 변경",
"change_current_node": "현재 노드를 다음으로 변경 하시겠습니까 ${node}?", "change_current_node": "현재 노드를 다음으로 변경 하시겠습니까 ${node}?",
"change_current_node_title": "현재 노드 변경", "change_current_node_title": "현재 노드 변경",
"change_exchange_provider": "교환 공급자 변경", "change_exchange_provider": "스왑 제공 업체를 변경하십시오",
"change_language": "언어 변경", "change_language": "언어 변경",
"change_language_to": "언어를로 변경 ${language}?", "change_language_to": "언어를로 변경 ${language}?",
"change_password": "비밀번호 변경", "change_password": "비밀번호 변경",
@ -282,7 +282,7 @@
"event": "이벤트", "event": "이벤트",
"events": "이벤트", "events": "이벤트",
"exchange": "교환", "exchange": "교환",
"exchange_incorrect_current_wallet_for_xmr": "Cake Wallet Monero 잔액에서 XMR을 교환하려면 먼저 Monero 지갑으로 전환하십시오.", "exchange_incorrect_current_wallet_for_xmr": "케이크 지갑 Monero Balance에서 XMR을 교체하려면 먼저 Monero 지갑으로 전환하십시오.",
"exchange_new_template": "새 템플릿", "exchange_new_template": "새 템플릿",
"exchange_provider_unsupported": "${providerName}은 더 이상 지원되지 않습니다!", "exchange_provider_unsupported": "${providerName}은 더 이상 지원되지 않습니다!",
"exchange_result_confirm": "확인을 누르면 전송됩니다 ${fetchingLabel} ${from} 지갑에서 ${walletName} 아래 주소로. 또는 외부 지갑에서 아래 주소로 보낼 수 있습니다 / QR 코드로 보낼 수 있습니다.\n\n확인을 눌러 계속하거나 금액을 변경하려면 돌아가십시오.", "exchange_result_confirm": "확인을 누르면 전송됩니다 ${fetchingLabel} ${from} 지갑에서 ${walletName} 아래 주소로. 또는 외부 지갑에서 아래 주소로 보낼 수 있습니다 / QR 코드로 보낼 수 있습니다.\n\n확인을 눌러 계속하거나 금액을 변경하려면 돌아가십시오.",
@ -309,7 +309,7 @@
"fill_code": "이메일에 제공된 인증 코드를 입력하세요.", "fill_code": "이메일에 제공된 인증 코드를 입력하세요.",
"filter_by": "필터링 기준", "filter_by": "필터링 기준",
"first_wallet_text": "Monero, Bitcoin, Ethereum, Litecoin 및 Haven을 위한 멋진 지갑", "first_wallet_text": "Monero, Bitcoin, Ethereum, Litecoin 및 Haven을 위한 멋진 지갑",
"fixed_pair_not_supported": "이 고정 쌍은 선택한 교환에서 지원되지 않습니다.", "fixed_pair_not_supported": "이 고정 쌍은 선택한 스왑 서비스에서 지원되지 않습니다.",
"fixed_rate": "고정 비율", "fixed_rate": "고정 비율",
"fixed_rate_alert": "고정 금리 모드 체크시 수취 금액 입력이 가능합니다. 고정 속도 모드로 전환 하시겠습니까?", "fixed_rate_alert": "고정 금리 모드 체크시 수취 금액 입력이 가능합니다. 고정 속도 모드로 전환 하시겠습니까?",
"forgot_password": "비밀번호 찾기", "forgot_password": "비밀번호 찾기",
@ -439,7 +439,7 @@
"node_test": "테스트", "node_test": "테스트",
"nodes": "노드", "nodes": "노드",
"nodes_list_reset_to_default_message": "설정을 기본값으로 재설정 하시겠습니까?", "nodes_list_reset_to_default_message": "설정을 기본값으로 재설정 하시겠습니까?",
"none_of_selected_providers_can_exchange": "선택한 공급자 중 누구도 이 교환을 할 수 없습니다.", "none_of_selected_providers_can_exchange": "선택한 공급자 중 어느 것도이 교환을 할 수 없습니다",
"noNFTYet": "아직 NFT가 없습니다", "noNFTYet": "아직 NFT가 없습니다",
"normal": "정상", "normal": "정상",
"note_optional": "참고 (선택 사항)", "note_optional": "참고 (선택 사항)",

View file

@ -114,7 +114,7 @@
"change_currency": "ငွေကြေးကိုပြောင်းပါ။", "change_currency": "ငွေကြေးကိုပြောင်းပါ။",
"change_current_node": "လက်ရှိ နှာခေါင်း ကို ${node} သို့ ပြောင်းရန် သေချာပါသလား။", "change_current_node": "လက်ရှိ နှာခေါင်း ကို ${node} သို့ ပြောင်းရန် သေချာပါသလား။",
"change_current_node_title": "လက်ရှိ နှာခေါင်း ကိုပြောင်းပါ။", "change_current_node_title": "လက်ရှိ နှာခေါင်း ကိုပြောင်းပါ။",
"change_exchange_provider": "အပြန်အလှန် လဲလှယ်ရေး ထောက်ပံ့ပေးသူကို ပြောင်းလဲပါ", "change_exchange_provider": "SWAP ပံ့ပိုးသူပြောင်းလဲပါ",
"change_language": "ဘာသာစကားပြောင်းပါ။", "change_language": "ဘာသာစကားပြောင်းပါ။",
"change_language_to": "ဘာသာစကားကို ${language} သို့ ပြောင်းမလား။", "change_language_to": "ဘာသာစကားကို ${language} သို့ ပြောင်းမလား။",
"change_password": "စကားဝှက်ကိုပြောင်းရန်", "change_password": "စကားဝှက်ကိုပြောင်းရန်",
@ -281,8 +281,8 @@
"etherscan_history": "Etherscan သမိုင်း", "etherscan_history": "Etherscan သမိုင်း",
"event": "ပွဲ", "event": "ပွဲ",
"events": "အဲ့ဒါနဲ့", "events": "အဲ့ဒါနဲ့",
"exchange": "ချိန်းတယ်။", "exchange": "လဲလှယ်",
"exchange_incorrect_current_wallet_for_xmr": "သင်၏ Cake Wallet Monero လက်ကျန်မှ XMR ကိုလဲလှယ်လိုပါက၊ သင်၏ Monero ပိုက်ဆံအိတ်သို့ ဦးစွာပြောင်းပါ။", "exchange_incorrect_current_wallet_for_xmr": "အကယ်. သင်သည် XMR ကိုသင်၏ကိတ်မုန့် Monero Balance မှ Swap ကိုလဲလှယ်လိုပါကသင်၏ Monero Wallet ကိုပထမဆုံးအကြိမ်ပြောင်းပါ။",
"exchange_new_template": "ပုံစံအသစ်", "exchange_new_template": "ပုံစံအသစ်",
"exchange_provider_unsupported": "${providerName} မရှိတော့ပါ!", "exchange_provider_unsupported": "${providerName} မရှိတော့ပါ!",
"exchange_result_confirm": "အတည်ပြုချက်ကို နှိပ်ခြင်းဖြင့်၊ သင်သည် ${fetchingLabel} ${from} ဟုခေါ်သော သင့်ပိုက်ဆံအိတ်မှ ${walletName} ကို အောက်ဖော်ပြပါလိပ်စာသို့ ပေးပို့မည်ဖြစ်ပါသည်။ သို့မဟုတ် သင့်ပြင်ပပိုက်ဆံအိတ်မှ အောက်ပါလိပ်စာ/QR ကုဒ်သို့ ပေးပို့နိုင်ပါသည်။\n\nပမာဏများကို ပြောင်းလဲရန် ဆက်လက်လုပ်ဆောင်ရန် သို့မဟုတ် ပြန်သွားရန် အတည်ပြုချက်ကို နှိပ်ပါ။", "exchange_result_confirm": "အတည်ပြုချက်ကို နှိပ်ခြင်းဖြင့်၊ သင်သည် ${fetchingLabel} ${from} ဟုခေါ်သော သင့်ပိုက်ဆံအိတ်မှ ${walletName} ကို အောက်ဖော်ပြပါလိပ်စာသို့ ပေးပို့မည်ဖြစ်ပါသည်။ သို့မဟုတ် သင့်ပြင်ပပိုက်ဆံအိတ်မှ အောက်ပါလိပ်စာ/QR ကုဒ်သို့ ပေးပို့နိုင်ပါသည်။\n\nပမာဏများကို ပြောင်းလဲရန် ဆက်လက်လုပ်ဆောင်ရန် သို့မဟုတ် ပြန်သွားရန် အတည်ပြုချက်ကို နှိပ်ပါ။",
@ -309,7 +309,7 @@
"fill_code": "သင့်အီးမေးလ်သို့ ပေးထားသည့် အတည်ပြုကုဒ်ကို ဖြည့်ပါ။", "fill_code": "သင့်အီးမေးလ်သို့ ပေးထားသည့် အတည်ပြုကုဒ်ကို ဖြည့်ပါ။",
"filter_by": "အလိုက် စစ်ထုတ်ပါ။", "filter_by": "အလိုက် စစ်ထုတ်ပါ။",
"first_wallet_text": "Monero၊ Bitcoin၊ Ethereum၊ Litecoin နှင့် Haven အတွက် အလွန်ကောင်းမွန်သော ပိုက်ဆံအိတ်", "first_wallet_text": "Monero၊ Bitcoin၊ Ethereum၊ Litecoin နှင့် Haven အတွက် အလွန်ကောင်းမွန်သော ပိုက်ဆံအိတ်",
"fixed_pair_not_supported": "ရွေးချယ်ထားသော ဖလှယ်မှုများဖြင့် ဤပုံသေအတွဲကို ပံ့ပိုးမထားပါ။", "fixed_pair_not_supported": "ဤပုံသေစုံတွဲသည်ရွေးချယ်ထားသောလဲလှယ်ရေးအစီအစဉ်များဖြင့်မထောက်ပံ့ပါ",
"fixed_rate": "ပုံသေနှုန်း", "fixed_rate": "ပုံသေနှုန်း",
"fixed_rate_alert": "ပုံသေနှုန်းထားမုဒ်ကို စစ်ဆေးသည့်အခါ လက်ခံပမာဏကို ထည့်သွင်းနိုင်မည်ဖြစ်သည်။ ပုံသေနှုန်းမုဒ်သို့ ပြောင်းလိုပါသလား။", "fixed_rate_alert": "ပုံသေနှုန်းထားမုဒ်ကို စစ်ဆေးသည့်အခါ လက်ခံပမာဏကို ထည့်သွင်းနိုင်မည်ဖြစ်သည်။ ပုံသေနှုန်းမုဒ်သို့ ပြောင်းလိုပါသလား။",
"forgot_password": "စကားဝှက်မေ့နေပါသလား", "forgot_password": "စကားဝှက်မေ့နေပါသလား",
@ -439,7 +439,7 @@
"node_test": "စမ်း", "node_test": "စမ်း",
"nodes": "ဆုံမှတ်များ", "nodes": "ဆုံမှတ်များ",
"nodes_list_reset_to_default_message": "ဆက်တင်များကို မူရင်းအတိုင်း ပြန်လည်သတ်မှတ်လိုသည်မှာ သေချာပါသလား။", "nodes_list_reset_to_default_message": "ဆက်တင်များကို မူရင်းအတိုင်း ပြန်လည်သတ်မှတ်လိုသည်မှာ သေချာပါသလား။",
"none_of_selected_providers_can_exchange": "ရွေးချယ်ထားသော ဝန်ဆောင်မှုပေးသူများမှ ဤလဲလှယ်မှုကို ပြုလုပ်၍မရပါ။", "none_of_selected_providers_can_exchange": "ရွေးချယ်ထားသောပံ့ပိုးပေးသူတစ် ဦး တစ်ယောက်မှဤအစီအစဉ်ကိုလုပ်နိုင်သည်",
"noNFTYet": "NFTs မရှိသေးပါ။", "noNFTYet": "NFTs မရှိသေးပါ။",
"normal": "ပုံမှန်", "normal": "ပုံမှန်",
"note_optional": "မှတ်ချက် (ချန်လှပ်ထားနိုင်သည်)", "note_optional": "မှတ်ချက် (ချန်လှပ်ထားနိုင်သည်)",

View file

@ -114,7 +114,7 @@
"change_currency": "Verander valuta", "change_currency": "Verander valuta",
"change_current_node": "Weet u zeker dat u het huidige knooppunt wilt wijzigen in ${node}?", "change_current_node": "Weet u zeker dat u het huidige knooppunt wilt wijzigen in ${node}?",
"change_current_node_title": "Wijzig het huidige knooppunt", "change_current_node_title": "Wijzig het huidige knooppunt",
"change_exchange_provider": "Wijzig Exchange Provider", "change_exchange_provider": "Verander Swap Provider",
"change_language": "Verander de taal", "change_language": "Verander de taal",
"change_language_to": "Verander de taal in ${language}?", "change_language_to": "Verander de taal in ${language}?",
"change_password": "Wachtwoord wijzigen", "change_password": "Wachtwoord wijzigen",
@ -281,8 +281,8 @@
"etherscan_history": "Etherscan-geschiedenis", "etherscan_history": "Etherscan-geschiedenis",
"event": "Evenement", "event": "Evenement",
"events": "Evenementen", "events": "Evenementen",
"exchange": "Uitwisseling", "exchange": "Ruil",
"exchange_incorrect_current_wallet_for_xmr": "Als u XMR wilt omwisselen van uw Cake Wallet Monero-saldo, moet u eerst overschakelen naar uw Monero-portemonnee.", "exchange_incorrect_current_wallet_for_xmr": "Als je XMR uit je cake -portemonnee Monero -balans wilt ruilen, schakel dan eerst over naar je Monero -portemonnee.",
"exchange_new_template": "Nieuwe sjabloon", "exchange_new_template": "Nieuwe sjabloon",
"exchange_provider_unsupported": "${providerName} wordt niet langer ondersteund!", "exchange_provider_unsupported": "${providerName} wordt niet langer ondersteund!",
"exchange_result_confirm": "Door op bevestigen te drukken, wordt u verzonden ${fetchingLabel} ${from} uit je portemonnee genoemd ${walletName} naar het onderstaande adres. Of u kunt vanuit uw externe portemonnee naar het onderstaande adres verzenden / QR-code sturen.\n\nDruk op bevestigen om door te gaan of terug te gaan om de bedragen te wijzigen.", "exchange_result_confirm": "Door op bevestigen te drukken, wordt u verzonden ${fetchingLabel} ${from} uit je portemonnee genoemd ${walletName} naar het onderstaande adres. Of u kunt vanuit uw externe portemonnee naar het onderstaande adres verzenden / QR-code sturen.\n\nDruk op bevestigen om door te gaan of terug te gaan om de bedragen te wijzigen.",
@ -309,7 +309,7 @@
"fill_code": "Vul de verificatiecode in die u in uw e-mail hebt ontvangen", "fill_code": "Vul de verificatiecode in die u in uw e-mail hebt ontvangen",
"filter_by": "Filteren op", "filter_by": "Filteren op",
"first_wallet_text": "Geweldige portemonnee voor Monero, Bitcoin, Ethereum, Litecoin, en Haven", "first_wallet_text": "Geweldige portemonnee voor Monero, Bitcoin, Ethereum, Litecoin, en Haven",
"fixed_pair_not_supported": "Dit vaste paar wordt niet ondersteund bij de geselecteerde exchanges", "fixed_pair_not_supported": "Dit vaste paar wordt niet ondersteund met de geselecteerde swap -services",
"fixed_rate": "Vast tarief", "fixed_rate": "Vast tarief",
"fixed_rate_alert": "U kunt het ontvangen bedrag invoeren wanneer de modus voor vaste tarieven is aangevinkt. Wilt u overschakelen naar de vaste-tariefmodus?", "fixed_rate_alert": "U kunt het ontvangen bedrag invoeren wanneer de modus voor vaste tarieven is aangevinkt. Wilt u overschakelen naar de vaste-tariefmodus?",
"forgot_password": "Wachtwoord vergeten", "forgot_password": "Wachtwoord vergeten",
@ -439,7 +439,7 @@
"node_test": "Test", "node_test": "Test",
"nodes": "Knooppunten", "nodes": "Knooppunten",
"nodes_list_reset_to_default_message": "Weet u zeker dat u de standaardinstellingen wilt herstellen?", "nodes_list_reset_to_default_message": "Weet u zeker dat u de standaardinstellingen wilt herstellen?",
"none_of_selected_providers_can_exchange": "Geen van de geselecteerde providers kan deze uitwisseling maken", "none_of_selected_providers_can_exchange": "Geen van de geselecteerde providers kan deze swap maken",
"noNFTYet": "Nog geen NFT's", "noNFTYet": "Nog geen NFT's",
"normal": "Normaal", "normal": "Normaal",
"note_optional": "Opmerking (optioneel)", "note_optional": "Opmerking (optioneel)",

View file

@ -114,7 +114,7 @@
"change_currency": "Zmień walutę", "change_currency": "Zmień walutę",
"change_current_node": "Czy na pewno chcesz wybrać ten węzeł? ${node}?", "change_current_node": "Czy na pewno chcesz wybrać ten węzeł? ${node}?",
"change_current_node_title": "Zmień bieżący węzeł", "change_current_node_title": "Zmień bieżący węzeł",
"change_exchange_provider": "Zmień dostawcę wymiany", "change_exchange_provider": "Zmień dostawca zamiany",
"change_language": "Zmień język", "change_language": "Zmień język",
"change_language_to": "Zmień język na ${language}?", "change_language_to": "Zmień język na ${language}?",
"change_password": "Zmień hasło", "change_password": "Zmień hasło",
@ -281,8 +281,8 @@
"etherscan_history": "Historia Etherscanu", "etherscan_history": "Historia Etherscanu",
"event": "Wydarzenie", "event": "Wydarzenie",
"events": "Wydarzenia", "events": "Wydarzenia",
"exchange": "Wymień", "exchange": "Zamieniać",
"exchange_incorrect_current_wallet_for_xmr": "Jeśli chcesz wymienić XMR z salda Cake Wallet Monero, najpierw przełącz się na portfel Monero.", "exchange_incorrect_current_wallet_for_xmr": "Jeśli chcesz zamienić XMR z salda Monero Portfer, najpierw przejdź na portfel Monero.",
"exchange_new_template": "Nowy szablon wymiany", "exchange_new_template": "Nowy szablon wymiany",
"exchange_provider_unsupported": "${providerName} nie jest już obsługiwany!", "exchange_provider_unsupported": "${providerName} nie jest już obsługiwany!",
"exchange_result_confirm": "Naciskając Potwierdź, wyślesz ${fetchingLabel} ${from} z twojego portfela ${walletName} na adres podany poniżej. Lub możesz wysłać z zewnętrznego portfela na poniższy adres / kod QR.\n\nNaciśnij Potwierdź, aby kontynuować lub wróć, aby zmienić kwoty.", "exchange_result_confirm": "Naciskając Potwierdź, wyślesz ${fetchingLabel} ${from} z twojego portfela ${walletName} na adres podany poniżej. Lub możesz wysłać z zewnętrznego portfela na poniższy adres / kod QR.\n\nNaciśnij Potwierdź, aby kontynuować lub wróć, aby zmienić kwoty.",
@ -309,7 +309,7 @@
"fill_code": "Proszę wpisać kod weryfikacyjny który otrzymałeś w wiadomości e-mail", "fill_code": "Proszę wpisać kod weryfikacyjny który otrzymałeś w wiadomości e-mail",
"filter_by": "Filtruj według", "filter_by": "Filtruj według",
"first_wallet_text": "Świetny portfel na Monero, Bitcoin, Ethereum, Litecoin, i Haven", "first_wallet_text": "Świetny portfel na Monero, Bitcoin, Ethereum, Litecoin, i Haven",
"fixed_pair_not_supported": "Ta stała para nie jest obsługiwana na wybranych giełdach", "fixed_pair_not_supported": "Ta stała para nie jest obsługiwana z wybranymi usługami swap",
"fixed_rate": "Stała stawka", "fixed_rate": "Stała stawka",
"fixed_rate_alert": "Będziesz mógł wprowadzić kwotę do otrzymania, gdy wybrany bedzie tryb stałego przeliczenia. Czy chcesz przejść do trybu stałej stawki?", "fixed_rate_alert": "Będziesz mógł wprowadzić kwotę do otrzymania, gdy wybrany bedzie tryb stałego przeliczenia. Czy chcesz przejść do trybu stałej stawki?",
"forgot_password": "Zapomniałem hasła", "forgot_password": "Zapomniałem hasła",

View file

@ -114,7 +114,7 @@
"change_currency": "Alterar moeda", "change_currency": "Alterar moeda",
"change_current_node": "Você realmente deseja alterar o nó atual para ${node}?", "change_current_node": "Você realmente deseja alterar o nó atual para ${node}?",
"change_current_node_title": "Mudar o nó atual", "change_current_node_title": "Mudar o nó atual",
"change_exchange_provider": "Alterar o provedor de troca", "change_exchange_provider": "Provedor de troca de alteração",
"change_language": "Mudar idioma", "change_language": "Mudar idioma",
"change_language_to": "Alterar idioma para ${language}?", "change_language_to": "Alterar idioma para ${language}?",
"change_password": "Mudar senha", "change_password": "Mudar senha",
@ -282,7 +282,7 @@
"event": "Evento", "event": "Evento",
"events": "Eventos", "events": "Eventos",
"exchange": "Trocar", "exchange": "Trocar",
"exchange_incorrect_current_wallet_for_xmr": "Se você deseja trocar o XMR de seu saldo da Carteira Monero Cake, troque primeiro para sua carteira Monero.", "exchange_incorrect_current_wallet_for_xmr": "Se você deseja trocar o XMR do balanço da carteira de bolo, mude para a sua carteira Monero primeiro.",
"exchange_new_template": "Novo modelo", "exchange_new_template": "Novo modelo",
"exchange_provider_unsupported": "${providerName} não é mais suportado!", "exchange_provider_unsupported": "${providerName} não é mais suportado!",
"exchange_result_confirm": "Ao confirmar, você enviará ${fetchingLabel} ${from} da sua carteira ${walletName} para o endereço mostrado abaixo. Ou você pode enviar de sua carteira externa para o endereço abaixo/código QR acima.\n\nPressione Confirmar para continuar ou volte para alterar os valores.", "exchange_result_confirm": "Ao confirmar, você enviará ${fetchingLabel} ${from} da sua carteira ${walletName} para o endereço mostrado abaixo. Ou você pode enviar de sua carteira externa para o endereço abaixo/código QR acima.\n\nPressione Confirmar para continuar ou volte para alterar os valores.",
@ -309,7 +309,7 @@
"fill_code": "Por favor, preencha o código de verificação fornecido ao seu e-mail", "fill_code": "Por favor, preencha o código de verificação fornecido ao seu e-mail",
"filter_by": "Filtrar por", "filter_by": "Filtrar por",
"first_wallet_text": "Carteira incrível para Monero, Bitcoin, Ethereum, Litecoin, e Haven", "first_wallet_text": "Carteira incrível para Monero, Bitcoin, Ethereum, Litecoin, e Haven",
"fixed_pair_not_supported": "Este par fixo não é compatível com as exchanges selecionadas", "fixed_pair_not_supported": "Este par fixo não é suportado com os serviços de troca selecionados",
"fixed_rate": "Taxa fixa", "fixed_rate": "Taxa fixa",
"fixed_rate_alert": "Você poderá inserir a quantia recebida quando o modo de taxa fixa estiver marcado. Quer mudar para o modo de taxa fixa?", "fixed_rate_alert": "Você poderá inserir a quantia recebida quando o modo de taxa fixa estiver marcado. Quer mudar para o modo de taxa fixa?",
"forgot_password": "Esqueci a senha", "forgot_password": "Esqueci a senha",
@ -440,7 +440,7 @@
"node_test": "Teste", "node_test": "Teste",
"nodes": "Nós", "nodes": "Nós",
"nodes_list_reset_to_default_message": "Você realmente deseja redefinir as configurações para o padrão?", "nodes_list_reset_to_default_message": "Você realmente deseja redefinir as configurações para o padrão?",
"none_of_selected_providers_can_exchange": "Nenhum dos provedores selecionados pode fazer esta troca", "none_of_selected_providers_can_exchange": "Nenhum dos fornecedores selecionados pode fazer essa troca",
"noNFTYet": "Ainda não há NFT", "noNFTYet": "Ainda não há NFT",
"normal": "Normal", "normal": "Normal",
"note_optional": "Nota (opcional)", "note_optional": "Nota (opcional)",

View file

@ -114,7 +114,7 @@
"change_currency": "Изменить валюту", "change_currency": "Изменить валюту",
"change_current_node": "Вы уверены, что хотите изменить текущую ноду на ${node}?", "change_current_node": "Вы уверены, что хотите изменить текущую ноду на ${node}?",
"change_current_node_title": "Изменить текущую ноду", "change_current_node_title": "Изменить текущую ноду",
"change_exchange_provider": "Изменить провайдера обмена", "change_exchange_provider": "Изменить поставщика свопа",
"change_language": "Изменить язык", "change_language": "Изменить язык",
"change_language_to": "Изменить язык на ${language}?", "change_language_to": "Изменить язык на ${language}?",
"change_password": "Изменить пароль", "change_password": "Изменить пароль",
@ -281,8 +281,8 @@
"etherscan_history": "История Эфириума", "etherscan_history": "История Эфириума",
"event": "Событие", "event": "Событие",
"events": "События", "events": "События",
"exchange": "Обмен", "exchange": "Менять",
"exchange_incorrect_current_wallet_for_xmr": "Если вы хотите обменять XMR со своего баланса Monero в Cake Wallet, сначала переключитесь на свой кошелек Monero.", "exchange_incorrect_current_wallet_for_xmr": "Если вы хотите поменять XMR с баланса с кошельком для торта Monero, сначала переключитесь на свой кошелек Monero.",
"exchange_new_template": "Новый шаблон", "exchange_new_template": "Новый шаблон",
"exchange_provider_unsupported": "${providerName} больше не поддерживается!", "exchange_provider_unsupported": "${providerName} больше не поддерживается!",
"exchange_result_confirm": "Нажимая подтвердить, вы отправите ${fetchingLabel} ${from} с вашего кошелька ${walletName} на адрес указанный ниже. Или вы можете отправить со своего внешнего кошелька на нижеуказанный адрес/QR-код.\n\nПожалуйста, нажмите подтвердить для продолжения, или вернитесь назад для изменения суммы.", "exchange_result_confirm": "Нажимая подтвердить, вы отправите ${fetchingLabel} ${from} с вашего кошелька ${walletName} на адрес указанный ниже. Или вы можете отправить со своего внешнего кошелька на нижеуказанный адрес/QR-код.\n\nПожалуйста, нажмите подтвердить для продолжения, или вернитесь назад для изменения суммы.",
@ -309,7 +309,7 @@
"fill_code": "Пожалуйста, введите код подтверждения, отправленный на вашу электронную почту", "fill_code": "Пожалуйста, введите код подтверждения, отправленный на вашу электронную почту",
"filter_by": "Фильтровать по", "filter_by": "Фильтровать по",
"first_wallet_text": "В самом удобном кошельке для Monero, Bitcoin, Ethereum, Litecoin, и Haven", "first_wallet_text": "В самом удобном кошельке для Monero, Bitcoin, Ethereum, Litecoin, и Haven",
"fixed_pair_not_supported": "Эта фиксированная пара не поддерживается выбранными биржами.", "fixed_pair_not_supported": "Эта фиксированная пара не поддерживается выбранными службами свопа",
"fixed_rate": "Фиксированная ставка", "fixed_rate": "Фиксированная ставка",
"fixed_rate_alert": "Вы сможете ввести сумму получения тогда, когда будет установлен режим фиксированной ставки. Вы хотите перейти в режим фиксированной ставки?", "fixed_rate_alert": "Вы сможете ввести сумму получения тогда, когда будет установлен режим фиксированной ставки. Вы хотите перейти в режим фиксированной ставки?",
"forgot_password": "Забыли пароль", "forgot_password": "Забыли пароль",
@ -439,7 +439,7 @@
"node_test": "Тест", "node_test": "Тест",
"nodes": "Ноды", "nodes": "Ноды",
"nodes_list_reset_to_default_message": "Вы уверены, что хотите сбросить настройки до значений по умолчанию?", "nodes_list_reset_to_default_message": "Вы уверены, что хотите сбросить настройки до значений по умолчанию?",
"none_of_selected_providers_can_exchange": "Ни один из выбранных провайдеров не может совершить этот обмен", "none_of_selected_providers_can_exchange": "Ни один из выбранных провайдеров не может сделать это обмен",
"noNFTYet": "NFT пока нет", "noNFTYet": "NFT пока нет",
"normal": "Нормальный", "normal": "Нормальный",
"note_optional": "Примечание (необязательно)", "note_optional": "Примечание (необязательно)",

View file

@ -282,7 +282,7 @@
"event": "เหตุการณ์", "event": "เหตุการณ์",
"events": "กิจกรรม", "events": "กิจกรรม",
"exchange": "แลกเปลี่ยน", "exchange": "แลกเปลี่ยน",
"exchange_incorrect_current_wallet_for_xmr": "หากคุณต้องการแลกเปลี่ยน XMR จากยอดคงเหลือ Monero ใน Cake Wallet ของคุณ กรุณาเปลี่ยนเป็นกระเป๋า Monero ก่อน", "exchange_incorrect_current_wallet_for_xmr": "หากคุณต้องการสลับ XMR จาก Cake Wallet Monero Balance โปรดเปลี่ยนไปใช้กระเป๋าเงิน Monero ก่อน",
"exchange_new_template": "เทมเพลทใหม่", "exchange_new_template": "เทมเพลทใหม่",
"exchange_provider_unsupported": "${providerName} ไม่ได้รับการสนับสนุนอีกต่อไป!", "exchange_provider_unsupported": "${providerName} ไม่ได้รับการสนับสนุนอีกต่อไป!",
"exchange_result_confirm": "โดยกดปุ่มยืนยัน, คุณจะส่ง ${fetchingLabel} ${from} จากกระเป๋าของคุณที่เรียกว่า ${walletName} ไปยังที่อยู่ที่แสดงข้างล่าง หรือคุณสามารถส่งจากกระเป๋าภายนอกไปยังที่อยู่/รหัส QR ด้านล่าง\n\nโปรดกดปุ่มยืนยันเพื่อดำเนินการต่อหรือกลับไปเปลี่ยนจำนวน", "exchange_result_confirm": "โดยกดปุ่มยืนยัน, คุณจะส่ง ${fetchingLabel} ${from} จากกระเป๋าของคุณที่เรียกว่า ${walletName} ไปยังที่อยู่ที่แสดงข้างล่าง หรือคุณสามารถส่งจากกระเป๋าภายนอกไปยังที่อยู่/รหัส QR ด้านล่าง\n\nโปรดกดปุ่มยืนยันเพื่อดำเนินการต่อหรือกลับไปเปลี่ยนจำนวน",
@ -309,7 +309,7 @@
"fill_code": "โปรดกรอกรหัสยืนยันที่ส่งไปยังอีเมลของคุณ", "fill_code": "โปรดกรอกรหัสยืนยันที่ส่งไปยังอีเมลของคุณ",
"filter_by": "กรองตาม", "filter_by": "กรองตาม",
"first_wallet_text": "กระเป๋าสตางค์ที่สวยงามสำหรับ Monero, Bitcoin, Ethereum, Litecoin และ Haven", "first_wallet_text": "กระเป๋าสตางค์ที่สวยงามสำหรับ Monero, Bitcoin, Ethereum, Litecoin และ Haven",
"fixed_pair_not_supported": "คู่ความสัมพันธ์ที่ถูกกำหนดไว้นี้ไม่สนับสนุนกับหุ้นที่เลือก", "fixed_pair_not_supported": "คู่คงที่นี้ไม่ได้รับการสนับสนุนด้วยบริการแลกเปลี่ยนที่เลือก",
"fixed_rate": "อัตราคงที่", "fixed_rate": "อัตราคงที่",
"fixed_rate_alert": "คุณจะสามารถป้อนจำนวนที่ได้รับเมื่อเลือกโหมดอัตราคงที่ คุณต้องการสลับไปที่โหมดอัตราคงที่?", "fixed_rate_alert": "คุณจะสามารถป้อนจำนวนที่ได้รับเมื่อเลือกโหมดอัตราคงที่ คุณต้องการสลับไปที่โหมดอัตราคงที่?",
"forgot_password": "ลืมรหัสผ่าน", "forgot_password": "ลืมรหัสผ่าน",
@ -439,7 +439,7 @@
"node_test": "ทดสอบ", "node_test": "ทดสอบ",
"nodes": "โหนด", "nodes": "โหนด",
"nodes_list_reset_to_default_message": "คุณแน่ใจหรือว่าต้องการรีเซ็ตการตั้งค่าเป็นค่าเริ่มต้น?", "nodes_list_reset_to_default_message": "คุณแน่ใจหรือว่าต้องการรีเซ็ตการตั้งค่าเป็นค่าเริ่มต้น?",
"none_of_selected_providers_can_exchange": "ไม่มีผู้ให้บริการที่เลือกที่สามารถแลกเปลี่ยนนี้ได้", "none_of_selected_providers_can_exchange": "ผู้ให้บริการที่เลือกไม่สามารถทำการแลกเปลี่ยนนี้ได้",
"noNFTYet": "ยังไม่มี NFT", "noNFTYet": "ยังไม่มี NFT",
"normal": "ปกติ", "normal": "ปกติ",
"note_optional": "บันทึก (ไม่จำเป็น)", "note_optional": "บันทึก (ไม่จำเป็น)",

View file

@ -114,7 +114,7 @@
"change_currency": "Baguhin ang pera", "change_currency": "Baguhin ang pera",
"change_current_node": "Sigurado ka bang baguhin ang kasalukuyang node sa ${node}?", "change_current_node": "Sigurado ka bang baguhin ang kasalukuyang node sa ${node}?",
"change_current_node_title": "Baguhin ang kasalukuyang node", "change_current_node_title": "Baguhin ang kasalukuyang node",
"change_exchange_provider": "Baguhin ang exchange provider", "change_exchange_provider": "Baguhin ang Swap Provider",
"change_language": "Baguhin ang wika", "change_language": "Baguhin ang wika",
"change_language_to": "Baguhin ang wika sa ${language}?", "change_language_to": "Baguhin ang wika sa ${language}?",
"change_password": "Baguhin ang password", "change_password": "Baguhin ang password",
@ -282,7 +282,7 @@
"event": "Kaganapan", "event": "Kaganapan",
"events": "Mga kaganapan", "events": "Mga kaganapan",
"exchange": "Palitan", "exchange": "Palitan",
"exchange_incorrect_current_wallet_for_xmr": "Kung gusto mong palitan ang XMR mula sa iyong balanse ng Monero ng Cake Wallet, mangyaring lumipat muna sa iyong Monero wallet.", "exchange_incorrect_current_wallet_for_xmr": "Kung nais mong magpalit ng XMR mula sa iyong balanse ng Wallet Monero, mangyaring lumipat sa iyong Monero Wallet muna.",
"exchange_new_template": "Bagong template", "exchange_new_template": "Bagong template",
"exchange_provider_unsupported": "Ang ${providerName} ay hindi na suportado!", "exchange_provider_unsupported": "Ang ${providerName} ay hindi na suportado!",
"exchange_result_confirm": "Sa pamamagitan ng pagpindot sa kumpirmahin, ikaw ay magpapadala ${fetchingLabel} ${from} mula sa inyong wallet na tinatawag ${walletName} sa wallet na ipinapakita sa ibaba. O pwede kang magpadala sa inyong external wallet sa ibabang address/QR code.\n\nPara magpatuloy, mangyaring pindutin upang kumpirmahin o bumalik para baguhin ang halaga.", "exchange_result_confirm": "Sa pamamagitan ng pagpindot sa kumpirmahin, ikaw ay magpapadala ${fetchingLabel} ${from} mula sa inyong wallet na tinatawag ${walletName} sa wallet na ipinapakita sa ibaba. O pwede kang magpadala sa inyong external wallet sa ibabang address/QR code.\n\nPara magpatuloy, mangyaring pindutin upang kumpirmahin o bumalik para baguhin ang halaga.",
@ -309,7 +309,7 @@
"fill_code": "Mangyaring ilagay ang verfification code na ibinigay sa iyong email", "fill_code": "Mangyaring ilagay ang verfification code na ibinigay sa iyong email",
"filter_by": "Filter ni", "filter_by": "Filter ni",
"first_wallet_text": "Kahanga-hangang wallet para sa Monero, Bitcoin, Litecoin, Ethereum, at Haven", "first_wallet_text": "Kahanga-hangang wallet para sa Monero, Bitcoin, Litecoin, Ethereum, at Haven",
"fixed_pair_not_supported": "Ang nakapirming pares na ito ay hindi suportado sa mga napiling palitan", "fixed_pair_not_supported": "Ang nakapirming pares na ito ay hindi suportado sa mga napiling serbisyo ng pagpapalit",
"fixed_rate": "Fixed rate", "fixed_rate": "Fixed rate",
"fixed_rate_alert": "Makakapagpasok ka ng halaga ng pagtanggap kapag nasuri ang fixed rate mode. Gusto mo bang lumipat sa fixed rate mode?", "fixed_rate_alert": "Makakapagpasok ka ng halaga ng pagtanggap kapag nasuri ang fixed rate mode. Gusto mo bang lumipat sa fixed rate mode?",
"forgot_password": "Nakalimutan ang Password", "forgot_password": "Nakalimutan ang Password",
@ -439,7 +439,7 @@
"node_test": "Test", "node_test": "Test",
"nodes": "Mga node", "nodes": "Mga node",
"nodes_list_reset_to_default_message": "Sigurado ka bang gusto mo bang i-reset ang mga settings sa default?", "nodes_list_reset_to_default_message": "Sigurado ka bang gusto mo bang i-reset ang mga settings sa default?",
"none_of_selected_providers_can_exchange": "Wala sa mga napiling provider ang makakagawa ng palitan na ito", "none_of_selected_providers_can_exchange": "Wala sa mga napiling tagapagbigay ng serbisyo ang maaaring gumawa ng pagpapalit na ito",
"noNFTYet": "Wala pang NFT", "noNFTYet": "Wala pang NFT",
"normal": "Normal", "normal": "Normal",
"note_optional": "Tala (opsyonal)", "note_optional": "Tala (opsyonal)",

View file

@ -114,7 +114,7 @@
"change_currency": "Para Birimini Değiştir", "change_currency": "Para Birimini Değiştir",
"change_current_node": "Şimdiki düğümü ${node} düğümüne değiştirmek istediğinizden emin misin?", "change_current_node": "Şimdiki düğümü ${node} düğümüne değiştirmek istediğinizden emin misin?",
"change_current_node_title": "Şimdiki düğümü değiştir", "change_current_node_title": "Şimdiki düğümü değiştir",
"change_exchange_provider": "Takas sağlayıcısını değiştir", "change_exchange_provider": "Takas Sağlayıcısı Değiştir",
"change_language": "Dili değiştir", "change_language": "Dili değiştir",
"change_language_to": "Dili şuna değiştir: ${language}?", "change_language_to": "Dili şuna değiştir: ${language}?",
"change_password": "Parolayı değiştir", "change_password": "Parolayı değiştir",
@ -282,7 +282,7 @@
"event": "Etkinlik", "event": "Etkinlik",
"events": "Olaylar", "events": "Olaylar",
"exchange": "Takas", "exchange": "Takas",
"exchange_incorrect_current_wallet_for_xmr": "Cake Wallet'daki Monero bakiyenizi kullanarak takas yapmak istiyorsan, lütfen önce Monero cüzdanına geç.", "exchange_incorrect_current_wallet_for_xmr": "XMR'yi kek cüzdanı Monero bakiyenizden değiştirmek istiyorsanız, lütfen önce Monero cüzdanınıza geçin.",
"exchange_new_template": "Yeni şablon", "exchange_new_template": "Yeni şablon",
"exchange_provider_unsupported": "${providerName} artık desteklenmiyor!", "exchange_provider_unsupported": "${providerName} artık desteklenmiyor!",
"exchange_result_confirm": "Onaylaya basarak, ${fetchingLabel} ${from} miktarında ${walletName} olarak adlandırılan cüzdanından aşağıda gösterilen adrese gönderilecek. Veya harici cüzdanından aşağıdaki adrese / QR koduna gönderebilirsin.\n\nLütfen devam etmek için onayla'ya bas veya tutarı değiştirmek için geri dön.", "exchange_result_confirm": "Onaylaya basarak, ${fetchingLabel} ${from} miktarında ${walletName} olarak adlandırılan cüzdanından aşağıda gösterilen adrese gönderilecek. Veya harici cüzdanından aşağıdaki adrese / QR koduna gönderebilirsin.\n\nLütfen devam etmek için onayla'ya bas veya tutarı değiştirmek için geri dön.",
@ -309,7 +309,7 @@
"fill_code": "Lütfen e-postanıza gelen doğrulama kodunu girin", "fill_code": "Lütfen e-postanıza gelen doğrulama kodunu girin",
"filter_by": "Şuna göre filtrele", "filter_by": "Şuna göre filtrele",
"first_wallet_text": "Monero, Bitcoin, Ethereum, Litecoin ve Haven için harika cüzdan", "first_wallet_text": "Monero, Bitcoin, Ethereum, Litecoin ve Haven için harika cüzdan",
"fixed_pair_not_supported": "Bu sabit paritesi seçilen borsalarda desteklenmemekte", "fixed_pair_not_supported": "Bu sabit çift seçilen takas hizmetleri ile desteklenmez",
"fixed_rate": "Sabit oran", "fixed_rate": "Sabit oran",
"fixed_rate_alert": "Sabit oran modunu işaretlersen alım tutarını girebilirsin. Sabit oran moduna geçmek ister misin?", "fixed_rate_alert": "Sabit oran modunu işaretlersen alım tutarını girebilirsin. Sabit oran moduna geçmek ister misin?",
"forgot_password": "Parolamı unuttum", "forgot_password": "Parolamı unuttum",
@ -439,7 +439,7 @@
"node_test": "Test Et", "node_test": "Test Et",
"nodes": "Düğümler", "nodes": "Düğümler",
"nodes_list_reset_to_default_message": "Ayarları varsayılana sıfırlamak istediğinizden emin misin?", "nodes_list_reset_to_default_message": "Ayarları varsayılana sıfırlamak istediğinizden emin misin?",
"none_of_selected_providers_can_exchange": "Seçilen sağlayıcılardan hiçbiri bu takası yapamaz", "none_of_selected_providers_can_exchange": "Seçilen sağlayıcıların hiçbiri bu takas yapamaz",
"noNFTYet": "Henüz NFT yok", "noNFTYet": "Henüz NFT yok",
"normal": "Normal", "normal": "Normal",
"note_optional": "Not (isteğe bağlı)", "note_optional": "Not (isteğe bağlı)",

View file

@ -114,7 +114,7 @@
"change_currency": "Змінити валюту", "change_currency": "Змінити валюту",
"change_current_node": "Ви впевнені, що хочете змінити поточний вузол на ${node}?", "change_current_node": "Ви впевнені, що хочете змінити поточний вузол на ${node}?",
"change_current_node_title": "Змінити поточний вузол", "change_current_node_title": "Змінити поточний вузол",
"change_exchange_provider": "Змінити провайдера обміну", "change_exchange_provider": "Змінити постачальник свопів",
"change_language": "Змінити мову", "change_language": "Змінити мову",
"change_language_to": "Змінити мову на ${language}?", "change_language_to": "Змінити мову на ${language}?",
"change_password": "Змінити пароль", "change_password": "Змінити пароль",
@ -281,8 +281,8 @@
"etherscan_history": "Історія Etherscan", "etherscan_history": "Історія Etherscan",
"event": "Подія", "event": "Подія",
"events": "Події", "events": "Події",
"exchange": "Обмін", "exchange": "Обміняти",
"exchange_incorrect_current_wallet_for_xmr": "Якщо ви хочете обміняти XMR із вашого балансу Cake Wallet Monero, спочатку перейдіть на свій гаманець Monero.", "exchange_incorrect_current_wallet_for_xmr": "Якщо ви хочете поміняти XMR зі свого балансу для тортів Monero Balance, спочатку перейдіть на свій гаманець Monero.",
"exchange_new_template": "Новий шаблон", "exchange_new_template": "Новий шаблон",
"exchange_provider_unsupported": "${providerName} більше не підтримується!", "exchange_provider_unsupported": "${providerName} більше не підтримується!",
"exchange_result_confirm": "Натиснувши підтвердити, ви відправите ${fetchingLabel} ${from} з вашого гаманця ${walletName} на адресу вказану нижче. Або ви можете відправити зі свого зовнішнього гаманця на нижчевказану адресу/QR-код.\n\nБудь ласка, натисніть підтвердити для продовження або поверніться назад щоб змінити суму.", "exchange_result_confirm": "Натиснувши підтвердити, ви відправите ${fetchingLabel} ${from} з вашого гаманця ${walletName} на адресу вказану нижче. Або ви можете відправити зі свого зовнішнього гаманця на нижчевказану адресу/QR-код.\n\nБудь ласка, натисніть підтвердити для продовження або поверніться назад щоб змінити суму.",
@ -309,7 +309,7 @@
"fill_code": "Будь ласка, введіть код підтвердження, надісланий на вашу електронну адресу", "fill_code": "Будь ласка, введіть код підтвердження, надісланий на вашу електронну адресу",
"filter_by": "Фільтрувати по", "filter_by": "Фільтрувати по",
"first_wallet_text": "В самому зручному гаманці для Monero, Bitcoin, Ethereum, Litecoin, та Haven", "first_wallet_text": "В самому зручному гаманці для Monero, Bitcoin, Ethereum, Litecoin, та Haven",
"fixed_pair_not_supported": "Ця фіксована пара не підтримується вибраними біржами", "fixed_pair_not_supported": "Ця фіксована пара не підтримується вибраними послугами Swap",
"fixed_rate": "Фіксована ставка", "fixed_rate": "Фіксована ставка",
"fixed_rate_alert": "Ви зможете ввести суму отримання тоді, коли буде встановлений режим фіксованої ставки. Ви хочете перейти в режим фіксованої ставки?", "fixed_rate_alert": "Ви зможете ввести суму отримання тоді, коли буде встановлений режим фіксованої ставки. Ви хочете перейти в режим фіксованої ставки?",
"forgot_password": "Забули пароль", "forgot_password": "Забули пароль",
@ -439,7 +439,7 @@
"node_test": "Тест", "node_test": "Тест",
"nodes": "Вузли", "nodes": "Вузли",
"nodes_list_reset_to_default_message": "Ви впевнені, що хочете скинути до налаштувань за замовченням?", "nodes_list_reset_to_default_message": "Ви впевнені, що хочете скинути до налаштувань за замовченням?",
"none_of_selected_providers_can_exchange": "Жоден із вибраних провайдерів не може здійснити цей обмін", "none_of_selected_providers_can_exchange": "Жоден із вибраних постачальників не може зробити цей своп",
"noNFTYet": "NFT ще немає", "noNFTYet": "NFT ще немає",
"normal": "нормальний", "normal": "нормальний",
"note_optional": "Примітка (необов’язково)", "note_optional": "Примітка (необов’язково)",

View file

@ -114,7 +114,7 @@
"change_currency": "کرنسی تبدیل کریں", "change_currency": "کرنسی تبدیل کریں",
"change_current_node": "کیا آپ یقینی طور پر موجودہ نوڈ کو ${node} میں تبدیل کرنا چاہتے ہیں؟", "change_current_node": "کیا آپ یقینی طور پر موجودہ نوڈ کو ${node} میں تبدیل کرنا چاہتے ہیں؟",
"change_current_node_title": "موجودہ نوڈ کو تبدیل کریں۔", "change_current_node_title": "موجودہ نوڈ کو تبدیل کریں۔",
"change_exchange_provider": "ایکسچینج فراہم کنندہ کو تبدیل کریں۔", "change_exchange_provider": "تبادلہ فراہم کرنے والے کو تبدیل کریں",
"change_language": "زبان تبدیل کریں", "change_language": "زبان تبدیل کریں",
"change_language_to": "زبان کو ${language} میں تبدیل کریں؟", "change_language_to": "زبان کو ${language} میں تبدیل کریں؟",
"change_password": "پاس ورڈ تبدیل کریں", "change_password": "پاس ورڈ تبدیل کریں",
@ -282,7 +282,7 @@
"event": "ﺐﯾﺮﻘﺗ", "event": "ﺐﯾﺮﻘﺗ",
"events": "ﺕﺎﺒﯾﺮﻘﺗ", "events": "ﺕﺎﺒﯾﺮﻘﺗ",
"exchange": "تبادلہ", "exchange": "تبادلہ",
"exchange_incorrect_current_wallet_for_xmr": "اگر آپ اپنے Cake والیٹ Monero بیلنس سے XMR کا تبادلہ کرنا چاہتے ہیں، تو براہ کرم پہلے اپنے Monero والیٹ پر جائیں۔", "exchange_incorrect_current_wallet_for_xmr": "اگر آپ اپنے کیک پرس مونیرو بیلنس سے XMR تبدیل کرنا چاہتے ہیں تو ، براہ کرم پہلے اپنے مونیرو پرس میں جائیں۔",
"exchange_new_template": "نیا سانچہ", "exchange_new_template": "نیا سانچہ",
"exchange_provider_unsupported": "${providerName} اب تعاون نہیں کیا جاتا ہے!", "exchange_provider_unsupported": "${providerName} اب تعاون نہیں کیا جاتا ہے!",
"exchange_result_confirm": "تصدیق کو دبانے سے، آپ اپنے بٹوے سے ${fetchingLabel} ${from} بھیجیں گے جسے ${walletName} کہتے ہیں نیچے دکھائے گئے پتے پر۔ یا آپ اپنے بیرونی والیٹ سے نیچے دیئے گئے پتے/QR کوڈ پر بھیج سکتے ہیں۔\\n\\nجاری رکھنے کے لیے براہ کرم تصدیق کو دبائیں یا رقم تبدیل کرنے کے لیے واپس جائیں۔", "exchange_result_confirm": "تصدیق کو دبانے سے، آپ اپنے بٹوے سے ${fetchingLabel} ${from} بھیجیں گے جسے ${walletName} کہتے ہیں نیچے دکھائے گئے پتے پر۔ یا آپ اپنے بیرونی والیٹ سے نیچے دیئے گئے پتے/QR کوڈ پر بھیج سکتے ہیں۔\\n\\nجاری رکھنے کے لیے براہ کرم تصدیق کو دبائیں یا رقم تبدیل کرنے کے لیے واپس جائیں۔",
@ -309,7 +309,7 @@
"fill_code": "براہ کرم اپنے ای میل پر فراہم کردہ تصدیقی کوڈ کو پُر کریں۔", "fill_code": "براہ کرم اپنے ای میل پر فراہم کردہ تصدیقی کوڈ کو پُر کریں۔",
"filter_by": "کی طرف سے فلٹر", "filter_by": "کی طرف سے فلٹر",
"first_wallet_text": "Monero، Bitcoin، Ethereum، Litecoin، اور Haven کے لیے زبردست پرس", "first_wallet_text": "Monero، Bitcoin، Ethereum، Litecoin، اور Haven کے لیے زبردست پرس",
"fixed_pair_not_supported": "یہ مقررہ جوڑا منتخب کردہ تبادلے کے ساتھ تعاون یافتہ نہیں ہے۔", "fixed_pair_not_supported": "یہ فکسڈ جوڑی منتخب شدہ تبادلہ خدمات کے ساتھ تعاون یافتہ نہیں ہے",
"fixed_rate": "مقررہ شرح", "fixed_rate": "مقررہ شرح",
"fixed_rate_alert": "فکسڈ ریٹ موڈ چیک ہونے پر آپ وصولی رقم درج کر سکیں گے۔ کیا آپ فکسڈ ریٹ موڈ پر سوئچ کرنا چاہتے ہیں؟", "fixed_rate_alert": "فکسڈ ریٹ موڈ چیک ہونے پر آپ وصولی رقم درج کر سکیں گے۔ کیا آپ فکسڈ ریٹ موڈ پر سوئچ کرنا چاہتے ہیں؟",
"forgot_password": "پاسورڈ بھول گے", "forgot_password": "پاسورڈ بھول گے",
@ -439,7 +439,7 @@
"node_test": "پرکھ", "node_test": "پرکھ",
"nodes": "نوڈس", "nodes": "نوڈس",
"nodes_list_reset_to_default_message": "کیا آپ واقعی ترتیبات کو ڈیفالٹ پر دوبارہ ترتیب دینا چاہتے ہیں؟", "nodes_list_reset_to_default_message": "کیا آپ واقعی ترتیبات کو ڈیفالٹ پر دوبارہ ترتیب دینا چاہتے ہیں؟",
"none_of_selected_providers_can_exchange": "منتخب فراہم کنندگان میں سے کوئی بھی یہ تبادلہ نہیں کر سکتا", "none_of_selected_providers_can_exchange": "منتخب کردہ کوئی بھی فراہم کنندہ یہ تبادلہ نہیں کرسکتا",
"noNFTYet": "۔ﮟﯿﮨ ﮟﯿﮩﻧ NFTs ﯽﺋﻮﮐ ﮏﺗ ﯽﮭﺑﺍ", "noNFTYet": "۔ﮟﯿﮨ ﮟﯿﮩﻧ NFTs ﯽﺋﻮﮐ ﮏﺗ ﯽﮭﺑﺍ",
"normal": "نارمل", "normal": "نارمل",
"note_optional": "نوٹ (اختیاری)", "note_optional": "نوٹ (اختیاری)",

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