This commit is contained in:
Matthew Fosse 2024-05-29 09:16:31 -07:00
commit ac1fe6b221
122 changed files with 3920 additions and 1259 deletions

View file

@ -42,7 +42,7 @@ jobs:
- name: Flutter action - name: Flutter action
uses: subosito/flutter-action@v1 uses: subosito/flutter-action@v1
with: with:
flutter-version: "3.19.5" flutter-version: "3.19.6"
channel: stable channel: stable
- name: Install package dependencies - name: Install package dependencies

65
assets/images/cards.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 158 KiB

BIN
assets/images/tbtc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View file

@ -1,3 +1,2 @@
Add Tron wallet Bitcoin Silent Payments
Hardware wallets enhancements Bug fixes and generic enhancements
Bug fixes

View file

@ -3,8 +3,8 @@ import 'dart:convert';
import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/script_hash.dart' as sh; import 'package:cw_bitcoin/script_hash.dart' as sh;
class BitcoinAddressRecord { abstract class BaseBitcoinAddressRecord {
BitcoinAddressRecord( BaseBitcoinAddressRecord(
this.address, { this.address, {
required this.index, required this.index,
this.isHidden = false, this.isHidden = false,
@ -13,15 +13,62 @@ class BitcoinAddressRecord {
String name = '', String name = '',
bool isUsed = false, bool isUsed = false,
required this.type, required this.type,
String? scriptHash,
required this.network, required this.network,
}) : _txCount = txCount, }) : _txCount = txCount,
_balance = balance, _balance = balance,
_name = name, _name = name,
_isUsed = isUsed, _isUsed = isUsed;
scriptHash = scriptHash ?? sh.scriptHash(address, network: network);
factory BitcoinAddressRecord.fromJSON(String jsonSource, BasedUtxoNetwork network) { @override
bool operator ==(Object o) => o is BaseBitcoinAddressRecord && address == o.address;
final String address;
bool isHidden;
final int index;
int _txCount;
int _balance;
String _name;
bool _isUsed;
BasedUtxoNetwork? network;
int get txCount => _txCount;
String get name => _name;
int get balance => _balance;
set txCount(int value) => _txCount = value;
set balance(int value) => _balance = value;
bool get isUsed => _isUsed;
void setAsUsed() => _isUsed = true;
void setNewName(String label) => _name = label;
int get hashCode => address.hashCode;
BitcoinAddressType type;
String toJSON();
}
class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
BitcoinAddressRecord(
super.address, {
required super.index,
super.isHidden = false,
super.txCount = 0,
super.balance = 0,
super.name = '',
super.isUsed = false,
required super.type,
String? scriptHash,
required super.network,
}) : scriptHash =
scriptHash ?? (network != null ? sh.scriptHash(address, network: network) : null);
factory BitcoinAddressRecord.fromJSON(String jsonSource, {BasedUtxoNetwork? network}) {
final decoded = json.decode(jsonSource) as Map; final decoded = json.decode(jsonSource) as Map;
return BitcoinAddressRecord( return BitcoinAddressRecord(
@ -41,44 +88,15 @@ class BitcoinAddressRecord {
); );
} }
@override
bool operator ==(Object o) => o is BitcoinAddressRecord && address == o.address;
final String address;
bool isHidden;
final int index;
int _txCount;
int _balance;
String _name;
bool _isUsed;
String? scriptHash; String? scriptHash;
BasedUtxoNetwork network;
int get txCount => _txCount; String getScriptHash(BasedUtxoNetwork network) {
if (scriptHash != null) return scriptHash!;
String get name => _name;
int get balance => _balance;
set txCount(int value) => _txCount = value;
set balance(int value) => _balance = value;
bool get isUsed => _isUsed;
void setAsUsed() => _isUsed = true;
void setNewName(String label) => _name = label;
@override
int get hashCode => address.hashCode;
BitcoinAddressType type;
String updateScriptHash(BasedUtxoNetwork network) {
scriptHash = sh.scriptHash(address, network: network); scriptHash = sh.scriptHash(address, network: network);
return scriptHash!; return scriptHash!;
} }
@override
String toJSON() => json.encode({ String toJSON() => json.encode({
'address': address, 'address': address,
'index': index, 'index': index,
@ -91,3 +109,57 @@ class BitcoinAddressRecord {
'scriptHash': scriptHash, 'scriptHash': scriptHash,
}); });
} }
class BitcoinSilentPaymentAddressRecord extends BaseBitcoinAddressRecord {
BitcoinSilentPaymentAddressRecord(
super.address, {
required super.index,
super.isHidden = false,
super.txCount = 0,
super.balance = 0,
super.name = '',
super.isUsed = false,
required this.silentPaymentTweak,
required super.network,
required super.type,
}) : super();
factory BitcoinSilentPaymentAddressRecord.fromJSON(String jsonSource,
{BasedUtxoNetwork? network}) {
final decoded = json.decode(jsonSource) as Map;
return BitcoinSilentPaymentAddressRecord(
decoded['address'] as String,
index: decoded['index'] as int,
isHidden: decoded['isHidden'] as bool? ?? false,
isUsed: decoded['isUsed'] as bool? ?? false,
txCount: decoded['txCount'] as int? ?? 0,
name: decoded['name'] as String? ?? '',
balance: decoded['balance'] as int? ?? 0,
network: (decoded['network'] as String?) == null
? network
: BasedUtxoNetwork.fromName(decoded['network'] as String),
silentPaymentTweak: decoded['silent_payment_tweak'] as String?,
type: decoded['type'] != null && decoded['type'] != ''
? BitcoinAddressType.values
.firstWhere((type) => type.toString() == decoded['type'] as String)
: SilentPaymentsAddresType.p2sp,
);
}
final String? silentPaymentTweak;
@override
String toJSON() => json.encode({
'address': address,
'index': index,
'isHidden': isHidden,
'isUsed': isUsed,
'txCount': txCount,
'name': name,
'balance': balance,
'type': type.toString(),
'network': network?.value,
'silent_payment_tweak': silentPaymentTweak,
});
}

View file

@ -9,6 +9,8 @@ class BitcoinReceivePageOption implements ReceivePageOption {
static const p2pkh = BitcoinReceivePageOption._('Legacy (P2PKH)'); static const p2pkh = BitcoinReceivePageOption._('Legacy (P2PKH)');
static const mweb = BitcoinReceivePageOption._('MWEB'); static const mweb = BitcoinReceivePageOption._('MWEB');
static const silent_payments = BitcoinReceivePageOption._('Silent Payments');
const BitcoinReceivePageOption._(this.value); const BitcoinReceivePageOption._(this.value);
final String value; final String value;
@ -18,6 +20,7 @@ class BitcoinReceivePageOption implements ReceivePageOption {
} }
static const all = [ static const all = [
BitcoinReceivePageOption.silent_payments,
BitcoinReceivePageOption.p2wpkh, BitcoinReceivePageOption.p2wpkh,
BitcoinReceivePageOption.p2tr, BitcoinReceivePageOption.p2tr,
BitcoinReceivePageOption.p2wsh, BitcoinReceivePageOption.p2wsh,
@ -29,6 +32,24 @@ class BitcoinReceivePageOption implements ReceivePageOption {
BitcoinReceivePageOption.p2wpkh, BitcoinReceivePageOption.p2wpkh,
BitcoinReceivePageOption.mweb BitcoinReceivePageOption.mweb
]; ];
BitcoinAddressType toType() {
switch (this) {
case BitcoinReceivePageOption.p2tr:
return SegwitAddresType.p2tr;
case BitcoinReceivePageOption.p2wsh:
return SegwitAddresType.p2wsh;
case BitcoinReceivePageOption.p2pkh:
return P2pkhAddressType.p2pkh;
case BitcoinReceivePageOption.p2sh:
return P2shAddressType.p2wpkhInP2sh;
case BitcoinReceivePageOption.silent_payments:
return SilentPaymentsAddresType.p2sp;
case BitcoinReceivePageOption.p2wpkh:
default:
return SegwitAddresType.p2wpkh;
}
}
factory BitcoinReceivePageOption.fromType(BitcoinAddressType type) { factory BitcoinReceivePageOption.fromType(BitcoinAddressType type) {
switch (type) { switch (type) {
@ -42,6 +63,8 @@ class BitcoinReceivePageOption implements ReceivePageOption {
return BitcoinReceivePageOption.p2pkh; return BitcoinReceivePageOption.p2pkh;
case P2shAddressType.p2wpkhInP2sh: case P2shAddressType.p2wpkhInP2sh:
return BitcoinReceivePageOption.p2sh; return BitcoinReceivePageOption.p2sh;
case SilentPaymentsAddresType.p2sp:
return BitcoinReceivePageOption.silent_payments;
case SegwitAddresType.p2wpkh: case SegwitAddresType.p2wpkh:
default: default:
return BitcoinReceivePageOption.p2wpkh; return BitcoinReceivePageOption.p2wpkh;

View file

@ -2,13 +2,66 @@ import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_core/unspent_transaction_output.dart'; import 'package:cw_core/unspent_transaction_output.dart';
class BitcoinUnspent extends Unspent { class BitcoinUnspent extends Unspent {
BitcoinUnspent(BitcoinAddressRecord addressRecord, String hash, int value, int vout) BitcoinUnspent(BaseBitcoinAddressRecord addressRecord, String hash, int value, int vout)
: bitcoinAddressRecord = addressRecord, : bitcoinAddressRecord = addressRecord,
super(addressRecord.address, hash, value, vout, null); super(addressRecord.address, hash, value, vout, null);
factory BitcoinUnspent.fromJSON(BitcoinAddressRecord address, Map<String, dynamic> json) => factory BitcoinUnspent.fromJSON(BaseBitcoinAddressRecord? address, Map<String, dynamic> json) =>
BitcoinUnspent( BitcoinUnspent(
address, json['tx_hash'] as String, json['value'] as int, json['tx_pos'] as int); address ?? BitcoinAddressRecord.fromJSON(json['address_record'].toString()),
json['tx_hash'] as String,
json['value'] as int,
json['tx_pos'] as int,
);
final BitcoinAddressRecord bitcoinAddressRecord; Map<String, dynamic> toJson() {
final json = <String, dynamic>{
'address_record': bitcoinAddressRecord.toJSON(),
'tx_hash': hash,
'value': value,
'tx_pos': vout,
};
return json;
}
final BaseBitcoinAddressRecord bitcoinAddressRecord;
}
class BitcoinSilentPaymentsUnspent extends BitcoinUnspent {
BitcoinSilentPaymentsUnspent(
BitcoinSilentPaymentAddressRecord addressRecord,
String hash,
int value,
int vout, {
required this.silentPaymentTweak,
required this.silentPaymentLabel,
}) : super(addressRecord, hash, value, vout);
@override
factory BitcoinSilentPaymentsUnspent.fromJSON(
BitcoinSilentPaymentAddressRecord? address, Map<String, dynamic> json) =>
BitcoinSilentPaymentsUnspent(
address ?? BitcoinSilentPaymentAddressRecord.fromJSON(json['address_record'].toString()),
json['tx_hash'] as String,
json['value'] as int,
json['tx_pos'] as int,
silentPaymentTweak: json['silent_payment_tweak'] as String?,
silentPaymentLabel: json['silent_payment_label'] as String?,
);
@override
Map<String, dynamic> toJson() {
final json = <String, dynamic>{
'address_record': bitcoinAddressRecord.toJSON(),
'tx_hash': hash,
'value': value,
'tx_pos': vout,
'silent_payment_tweak': silentPaymentTweak,
'silent_payment_label': silentPaymentLabel,
};
return json;
}
String? silentPaymentTweak;
String? silentPaymentLabel;
} }

View file

@ -39,22 +39,28 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
Map<String, int>? initialRegularAddressIndex, Map<String, int>? initialRegularAddressIndex,
Map<String, int>? initialChangeAddressIndex, Map<String, int>? initialChangeAddressIndex,
String? passphrase, String? passphrase,
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
int initialSilentAddressIndex = 0,
bool? alwaysScan,
}) : super( }) : super(
mnemonic: mnemonic, mnemonic: mnemonic,
passphrase: passphrase, passphrase: passphrase,
xpub: xpub, xpub: xpub,
password: password, password: password,
walletInfo: walletInfo, walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo, unspentCoinsInfo: unspentCoinsInfo,
networkType: networkParam == null networkType: networkParam == null
? bitcoin.bitcoin
: networkParam == BitcoinNetwork.mainnet
? bitcoin.bitcoin ? bitcoin.bitcoin
: bitcoin.testnet, : networkParam == BitcoinNetwork.mainnet
initialAddresses: initialAddresses, ? bitcoin.bitcoin
initialBalance: initialBalance, : bitcoin.testnet,
seedBytes: seedBytes, initialAddresses: initialAddresses,
currency: CryptoCurrency.btc) { initialBalance: initialBalance,
seedBytes: seedBytes,
currency:
networkParam == BitcoinNetwork.testnet ? CryptoCurrency.tbtc : CryptoCurrency.btc,
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)
// the sideHd derivation path = m/84'/0'/0'/1 (account 1, index unspecified here) // the sideHd derivation path = m/84'/0'/0'/1 (account 1, index unspecified here)
// String derivationPath = walletInfo.derivationInfo!.derivationPath!; // String derivationPath = walletInfo.derivationInfo!.derivationPath!;
@ -62,14 +68,18 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
// final hd = bitcoin.HDWallet.fromSeed(seedBytes, network: networkType); // final hd = bitcoin.HDWallet.fromSeed(seedBytes, network: networkType);
walletAddresses = BitcoinWalletAddresses( walletAddresses = BitcoinWalletAddresses(
walletInfo, walletInfo,
electrumClient: electrumClient,
initialAddresses: initialAddresses, initialAddresses: initialAddresses,
initialRegularAddressIndex: initialRegularAddressIndex, initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex, initialChangeAddressIndex: initialChangeAddressIndex,
initialSilentAddresses: initialSilentAddresses,
initialSilentAddressIndex: initialSilentAddressIndex,
mainHd: hd, mainHd: hd,
sideHd: accountHD.derive(1), sideHd: accountHD.derive(1),
network: networkParam ?? network, network: networkParam ?? network,
masterHd:
seedBytes != null ? bitcoin.HDWallet.fromSeed(seedBytes, network: networkType) : null,
); );
autorun((_) { autorun((_) {
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress; this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
}); });
@ -84,9 +94,11 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
String? addressPageType, String? addressPageType,
BasedUtxoNetwork? network, BasedUtxoNetwork? network,
List<BitcoinAddressRecord>? initialAddresses, List<BitcoinAddressRecord>? initialAddresses,
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
ElectrumBalance? initialBalance, ElectrumBalance? initialBalance,
Map<String, int>? initialRegularAddressIndex, Map<String, int>? initialRegularAddressIndex,
Map<String, int>? initialChangeAddressIndex, Map<String, int>? initialChangeAddressIndex,
int initialSilentAddressIndex = 0,
}) async { }) async {
late Uint8List seedBytes; late Uint8List seedBytes;
@ -109,6 +121,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
walletInfo: walletInfo, walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo, unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: initialAddresses, initialAddresses: initialAddresses,
initialSilentAddresses: initialSilentAddresses,
initialSilentAddressIndex: initialSilentAddressIndex,
initialBalance: initialBalance, initialBalance: initialBalance,
seedBytes: seedBytes, seedBytes: seedBytes,
initialRegularAddressIndex: initialRegularAddressIndex, initialRegularAddressIndex: initialRegularAddressIndex,
@ -123,6 +137,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
required WalletInfo walletInfo, required WalletInfo walletInfo,
required Box<UnspentCoinsInfo> unspentCoinsInfo, required Box<UnspentCoinsInfo> unspentCoinsInfo,
required String password, required String password,
required bool alwaysScan,
}) async { }) async {
final network = walletInfo.network != null final network = walletInfo.network != null
? BasedUtxoNetwork.fromName(walletInfo.network!) ? BasedUtxoNetwork.fromName(walletInfo.network!)
@ -163,12 +178,15 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
walletInfo: walletInfo, walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfo, unspentCoinsInfo: unspentCoinsInfo,
initialAddresses: snp.addresses, initialAddresses: snp.addresses,
initialSilentAddresses: snp.silentAddresses,
initialSilentAddressIndex: snp.silentAddressIndex,
initialBalance: snp.balance, initialBalance: snp.balance,
seedBytes: seedBytes, seedBytes: seedBytes,
initialRegularAddressIndex: snp.regularAddressIndex, initialRegularAddressIndex: snp.regularAddressIndex,
initialChangeAddressIndex: snp.changeAddressIndex, initialChangeAddressIndex: snp.changeAddressIndex,
addressPageType: snp.addressPageType, addressPageType: snp.addressPageType,
networkParam: network, networkParam: network,
alwaysScan: alwaysScan,
); );
} }
@ -179,7 +197,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
void setLedger(Ledger setLedger, LedgerDevice setLedgerDevice) { void setLedger(Ledger setLedger, LedgerDevice setLedgerDevice) {
_ledger = setLedger; _ledger = setLedger;
_ledgerDevice = setLedgerDevice; _ledgerDevice = setLedgerDevice;
_bitcoinLedgerApp = BitcoinLedgerApp(_ledger!, derivationPath: walletInfo.derivationInfo!.derivationPath!); _bitcoinLedgerApp =
BitcoinLedgerApp(_ledger!, derivationPath: walletInfo.derivationInfo!.derivationPath!);
} }
@override @override
@ -202,16 +221,17 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!; final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!;
psbtReadyInputs.add(PSBTReadyUtxoWithAddress( psbtReadyInputs.add(PSBTReadyUtxoWithAddress(
utxo: utxo.utxo, utxo: utxo.utxo,
rawTx: rawTx, rawTx: rawTx,
ownerDetails: utxo.ownerDetails, ownerDetails: utxo.ownerDetails,
ownerDerivationPath: publicKeyAndDerivationPath.derivationPath, ownerDerivationPath: publicKeyAndDerivationPath.derivationPath,
ownerMasterFingerprint: masterFingerprint, ownerMasterFingerprint: masterFingerprint,
ownerPublicKey: publicKeyAndDerivationPath.publicKey, ownerPublicKey: publicKeyAndDerivationPath.publicKey,
)); ));
} }
final psbt = PSBTTransactionBuild(inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF); final psbt =
PSBTTransactionBuild(inputs: psbtReadyInputs, outputs: outputs, enableRBF: enableRBF);
final rawHex = await _bitcoinLedgerApp!.signPsbt(_ledgerDevice!, psbt: psbt.psbt); final rawHex = await _bitcoinLedgerApp!.signPsbt(_ledgerDevice!, psbt: psbt.psbt);
return BtcTransaction.fromRaw(hex.encode(rawHex)); return BtcTransaction.fromRaw(hex.encode(rawHex));

View file

@ -15,10 +15,12 @@ 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.electrumClient,
super.initialAddresses, super.initialAddresses,
super.initialRegularAddressIndex, super.initialRegularAddressIndex,
super.initialChangeAddressIndex, super.initialChangeAddressIndex,
super.initialSilentAddresses,
super.initialSilentAddressIndex = 0,
super.masterHd,
}) : super(walletInfo); }) : super(walletInfo);
@override @override

View file

@ -19,10 +19,11 @@ class BitcoinWalletService extends WalletService<
BitcoinRestoreWalletFromSeedCredentials, BitcoinRestoreWalletFromSeedCredentials,
BitcoinRestoreWalletFromWIFCredentials, BitcoinRestoreWalletFromWIFCredentials,
BitcoinRestoreWalletFromHardware> { BitcoinRestoreWalletFromHardware> {
BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource); BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource, this.alwaysScan);
final Box<WalletInfo> walletInfoSource; final Box<WalletInfo> walletInfoSource;
final Box<UnspentCoinsInfo> unspentCoinsInfoSource; final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
final bool alwaysScan;
@override @override
WalletType getType() => WalletType.bitcoin; WalletType getType() => WalletType.bitcoin;
@ -55,20 +56,24 @@ class BitcoinWalletService extends WalletService<
.firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!; .firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!;
try { try {
final wallet = await BitcoinWalletBase.open( final wallet = await BitcoinWalletBase.open(
password: password, password: password,
name: name, name: name,
walletInfo: walletInfo, walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource); unspentCoinsInfo: unspentCoinsInfoSource,
alwaysScan: alwaysScan,
);
await wallet.init(); await wallet.init();
saveBackup(name); saveBackup(name);
return wallet; return wallet;
} catch (_) { } catch (_) {
await restoreWalletFilesFromBackup(name); await restoreWalletFilesFromBackup(name);
final wallet = await BitcoinWalletBase.open( final wallet = await BitcoinWalletBase.open(
password: password, password: password,
name: name, name: name,
walletInfo: walletInfo, walletInfo: walletInfo,
unspentCoinsInfo: unspentCoinsInfoSource); unspentCoinsInfo: unspentCoinsInfoSource,
alwaysScan: alwaysScan,
);
await wallet.init(); await wallet.init();
return wallet; return wallet;
} }
@ -87,10 +92,12 @@ class BitcoinWalletService extends WalletService<
final currentWalletInfo = walletInfoSource.values final currentWalletInfo = walletInfoSource.values
.firstWhereOrNull((info) => info.id == WalletBase.idFor(currentName, getType()))!; .firstWhereOrNull((info) => info.id == WalletBase.idFor(currentName, getType()))!;
final currentWallet = await BitcoinWalletBase.open( final currentWallet = await BitcoinWalletBase.open(
password: password, password: password,
name: currentName, name: currentName,
walletInfo: currentWalletInfo, walletInfo: currentWalletInfo,
unspentCoinsInfo: unspentCoinsInfoSource); unspentCoinsInfo: unspentCoinsInfoSource,
alwaysScan: alwaysScan,
);
await currentWallet.renameWalletFiles(newName); await currentWallet.renameWalletFiles(newName);
await saveBackup(newName); await saveBackup(newName);
@ -105,12 +112,13 @@ class BitcoinWalletService extends WalletService<
@override @override
Future<BitcoinWallet> restoreFromHardwareWallet(BitcoinRestoreWalletFromHardware credentials, Future<BitcoinWallet> restoreFromHardwareWallet(BitcoinRestoreWalletFromHardware credentials,
{bool? isTestnet}) async { {bool? isTestnet}) async {
final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet; final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet;
credentials.walletInfo?.network = network.value; credentials.walletInfo?.network = network.value;
credentials.walletInfo?.derivationInfo?.derivationPath = credentials.hwAccountData.derivationPath; credentials.walletInfo?.derivationInfo?.derivationPath =
credentials.hwAccountData.derivationPath;
final wallet = await BitcoinWallet(password: credentials.password!, final wallet = await BitcoinWallet(
password: credentials.password!,
xpub: credentials.hwAccountData.xpub, xpub: credentials.hwAccountData.xpub,
walletInfo: credentials.walletInfo!, walletInfo: credentials.walletInfo!,
unspentCoinsInfo: unspentCoinsInfoSource, unspentCoinsInfo: unspentCoinsInfoSource,
@ -123,7 +131,7 @@ class BitcoinWalletService extends WalletService<
@override @override
Future<BitcoinWallet> restoreFromKeys(BitcoinRestoreWalletFromWIFCredentials credentials, Future<BitcoinWallet> restoreFromKeys(BitcoinRestoreWalletFromWIFCredentials credentials,
{bool? isTestnet}) async => {bool? isTestnet}) async =>
throw UnimplementedError(); throw UnimplementedError();
@override @override

View file

@ -41,23 +41,35 @@ class ElectrumClient {
bool get isConnected => _isConnected; bool get isConnected => _isConnected;
Socket? socket; Socket? socket;
void Function(bool)? onConnectionStatusChange; void Function(bool?)? onConnectionStatusChange;
int _id; int _id;
final Map<String, SocketTask> _tasks; final Map<String, SocketTask> _tasks;
Map<String, SocketTask> get tasks => _tasks;
final Map<String, String> _errors; final Map<String, String> _errors;
bool _isConnected; bool _isConnected;
Timer? _aliveTimer; Timer? _aliveTimer;
String unterminatedString; String unterminatedString;
Future<void> connectToUri(Uri uri) async => await connect(host: uri.host, port: uri.port); Uri? uri;
bool? useSSL;
Future<void> connect({required String host, required int port}) async { Future<void> connectToUri(Uri uri, {bool? useSSL}) async {
this.uri = uri;
this.useSSL = useSSL;
await connect(host: uri.host, port: uri.port, useSSL: useSSL);
}
Future<void> connect({required String host, required int port, bool? useSSL}) async {
try { try {
await socket?.close(); await socket?.close();
} catch (_) {} } catch (_) {}
socket = await SecureSocket.connect(host, port, if (useSSL == false) {
timeout: connectionTimeout, onBadCertificate: (_) => true); socket = await Socket.connect(host, port, timeout: connectionTimeout);
} else {
socket = await SecureSocket.connect(host, port,
timeout: connectionTimeout, onBadCertificate: (_) => true);
}
_setIsConnected(true); _setIsConnected(true);
socket!.listen((Uint8List event) { socket!.listen((Uint8List event) {
@ -79,7 +91,7 @@ class ElectrumClient {
_setIsConnected(false); _setIsConnected(false);
}, onDone: () { }, onDone: () {
unterminatedString = ''; unterminatedString = '';
_setIsConnected(false); _setIsConnected(null);
}); });
keepAlive(); keepAlive();
} }
@ -134,11 +146,12 @@ class ElectrumClient {
await callWithTimeout(method: 'server.ping'); await callWithTimeout(method: 'server.ping');
_setIsConnected(true); _setIsConnected(true);
} on RequestFailedTimeoutException catch (_) { } on RequestFailedTimeoutException catch (_) {
_setIsConnected(false); _setIsConnected(null);
} }
} }
Future<List<String>> version() => call(method: 'server.version').then((dynamic result) { Future<List<String>> version() =>
call(method: 'server.version', params: ["", "1.4"]).then((dynamic result) {
if (result is List) { if (result is List) {
return result.map((dynamic val) => val.toString()).toList(); return result.map((dynamic val) => val.toString()).toList();
} }
@ -266,6 +279,18 @@ class ElectrumClient {
Future<Map<String, dynamic>> getHeader({required int height}) async => Future<Map<String, dynamic>> getHeader({required int height}) async =>
await call(method: 'blockchain.block.get_header', params: [height]) as Map<String, dynamic>; await call(method: 'blockchain.block.get_header', params: [height]) as Map<String, dynamic>;
BehaviorSubject<Object>? tweaksSubscribe({required int height, required int count}) {
_id += 1;
return subscribe<Object>(
id: 'blockchain.tweaks.subscribe:${height + count}',
method: 'blockchain.tweaks.subscribe',
params: [height, count, false],
);
}
Future<dynamic> getTweaks({required int height}) async =>
await callWithTimeout(method: 'blockchain.tweaks.subscribe', params: [height, 1, false]);
Future<double> estimatefee({required int p}) => Future<double> estimatefee({required int p}) =>
call(method: 'blockchain.estimatefee', params: [p]).then((dynamic result) { call(method: 'blockchain.estimatefee', params: [p]).then((dynamic result) {
if (result is double) { if (result is double) {
@ -308,9 +333,6 @@ class ElectrumClient {
}); });
Future<List<int>> feeRates({BasedUtxoNetwork? network}) async { Future<List<int>> feeRates({BasedUtxoNetwork? network}) async {
if (network == BitcoinNetwork.testnet) {
return [1, 1, 1];
}
try { try {
final topDoubleString = await estimatefee(p: 1); final topDoubleString = await estimatefee(p: 1);
final middleDoubleString = await estimatefee(p: 5); final middleDoubleString = await estimatefee(p: 5);
@ -333,6 +355,11 @@ class ElectrumClient {
// } // }
BehaviorSubject<Map<String, dynamic>>? tipListener; BehaviorSubject<Map<String, dynamic>>? tipListener;
int? currentTip; int? currentTip;
Future<int?> getCurrentBlockChainTip() =>
callWithTimeout(method: 'blockchain.headers.subscribe').then((result) {
if (result is Map<String, dynamic>) {
return result["height"] as int;
}
Future<int?> getCurrentBlockChainTip() async { Future<int?> getCurrentBlockChainTip() async {
final method = 'blockchain.headers.subscribe'; final method = 'blockchain.headers.subscribe';
@ -345,6 +372,12 @@ class ElectrumClient {
return currentTip; return currentTip;
} }
BehaviorSubject<Object>? chainTipSubscribe() {
_id += 1;
return subscribe<Object>(
id: 'blockchain.headers.subscribe', method: 'blockchain.headers.subscribe');
}
BehaviorSubject<Object>? scripthashUpdate(String scripthash) { BehaviorSubject<Object>? scripthashUpdate(String scripthash) {
_id += 1; _id += 1;
return subscribe<Object>( return subscribe<Object>(
@ -401,7 +434,9 @@ class ElectrumClient {
Future<void> close() async { Future<void> close() async {
_aliveTimer?.cancel(); _aliveTimer?.cancel();
await socket?.close(); try {
await socket?.close();
} catch (_) {}
onConnectionStatusChange = null; onConnectionStatusChange = null;
} }
@ -442,17 +477,25 @@ class ElectrumClient {
_tasks[id]?.subject?.add(params.last); _tasks[id]?.subject?.add(params.last);
break; break;
case 'blockchain.headers.subscribe':
final params = request['params'] as List<dynamic>;
_tasks[method]?.subject?.add(params.last);
break;
case 'blockchain.tweaks.subscribe':
final params = request['params'] as List<dynamic>;
_tasks[_tasks.keys.first]?.subject?.add(params.last);
break;
default: default:
break; break;
} }
} }
void _setIsConnected(bool isConnected) { void _setIsConnected(bool? isConnected) {
if (_isConnected != isConnected) { if (_isConnected != isConnected) {
onConnectionStatusChange?.call(isConnected); onConnectionStatusChange?.call(isConnected);
} }
_isConnected = isConnected; _isConnected = isConnected ?? false;
} }
void _handleResponse(Map<String, dynamic> response) { void _handleResponse(Map<String, dynamic> response) {

View file

@ -3,8 +3,11 @@ import 'package:cw_bitcoin/bitcoin_amount_format.dart';
import 'package:cw_core/balance.dart'; import 'package:cw_core/balance.dart';
class ElectrumBalance extends Balance { class ElectrumBalance extends Balance {
const ElectrumBalance({required this.confirmed, required this.unconfirmed, required this.frozen}) ElectrumBalance({
: super(confirmed, unconfirmed); required this.confirmed,
required this.unconfirmed,
required this.frozen,
}) : super(confirmed, unconfirmed);
static ElectrumBalance? fromJSON(String? jsonSource) { static ElectrumBalance? fromJSON(String? jsonSource) {
if (jsonSource == null) { if (jsonSource == null) {
@ -19,8 +22,8 @@ class ElectrumBalance extends Balance {
frozen: decoded['frozen'] as int? ?? 0); frozen: decoded['frozen'] as int? ?? 0);
} }
final int confirmed; int confirmed;
final int unconfirmed; int unconfirmed;
final int frozen; final int frozen;
@override @override

View file

@ -11,13 +11,11 @@ part 'electrum_transaction_history.g.dart';
const transactionsHistoryFileName = 'transactions.json'; const transactionsHistoryFileName = 'transactions.json';
class ElectrumTransactionHistory = ElectrumTransactionHistoryBase class ElectrumTransactionHistory = ElectrumTransactionHistoryBase with _$ElectrumTransactionHistory;
with _$ElectrumTransactionHistory;
abstract class ElectrumTransactionHistoryBase abstract class ElectrumTransactionHistoryBase
extends TransactionHistoryBase<ElectrumTransactionInfo> with Store { extends TransactionHistoryBase<ElectrumTransactionInfo> with Store {
ElectrumTransactionHistoryBase( ElectrumTransactionHistoryBase({required this.walletInfo, required String password})
{required this.walletInfo, required String password})
: _password = password, : _password = password,
_height = 0 { _height = 0 {
transactions = ObservableMap<String, ElectrumTransactionInfo>(); transactions = ObservableMap<String, ElectrumTransactionInfo>();
@ -30,8 +28,7 @@ abstract class ElectrumTransactionHistoryBase
Future<void> init() async => await _load(); Future<void> init() async => await _load();
@override @override
void addOne(ElectrumTransactionInfo transaction) => void addOne(ElectrumTransactionInfo transaction) => transactions[transaction.id] = transaction;
transactions[transaction.id] = transaction;
@override @override
void addMany(Map<String, ElectrumTransactionInfo> transactions) => void addMany(Map<String, ElectrumTransactionInfo> transactions) =>
@ -40,11 +37,13 @@ abstract class ElectrumTransactionHistoryBase
@override @override
Future<void> save() async { Future<void> save() async {
try { try {
final dirPath = final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
final path = '$dirPath/$transactionsHistoryFileName'; final path = '$dirPath/$transactionsHistoryFileName';
final data = final txjson = {};
json.encode({'height': _height, 'transactions': transactions}); for (final tx in transactions.entries) {
txjson[tx.key] = tx.value.toJson();
}
final data = json.encode({'height': _height, 'transactions': txjson});
await writeData(path: path, password: _password, data: data); await writeData(path: path, password: _password, data: data);
} catch (e) { } catch (e) {
print('Error while save bitcoin transaction history: ${e.toString()}'); print('Error while save bitcoin transaction history: ${e.toString()}');
@ -57,8 +56,7 @@ abstract class ElectrumTransactionHistoryBase
} }
Future<Map<String, dynamic>> _read() async { Future<Map<String, dynamic>> _read() async {
final dirPath = final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
final path = '$dirPath/$transactionsHistoryFileName'; final path = '$dirPath/$transactionsHistoryFileName';
final content = await read(path: path, password: _password); final content = await read(path: path, password: _password);
return json.decode(content) as Map<String, dynamic>; return json.decode(content) as Map<String, dynamic>;
@ -84,7 +82,5 @@ abstract class ElectrumTransactionHistoryBase
} }
} }
void _update(ElectrumTransactionInfo transaction) => void _update(ElectrumTransactionInfo transaction) => transactions[transaction.id] = transaction;
transactions[transaction.id] = transaction;
} }

View file

@ -1,9 +1,8 @@
import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
import 'package:cw_bitcoin/address_from_output.dart'; import 'package:cw_bitcoin/address_from_output.dart';
import 'package:cw_bitcoin/bitcoin_address_record.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart';
import 'package:cw_bitcoin/bitcoin_amount_format.dart'; import 'package:cw_bitcoin/bitcoin_amount_format.dart';
import 'package:cw_bitcoin/bitcoin_unspent.dart';
import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_direction.dart';
import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/transaction_info.dart';
import 'package:cw_core/format_amount.dart'; import 'package:cw_core/format_amount.dart';
@ -19,6 +18,8 @@ class ElectrumTransactionBundle {
} }
class ElectrumTransactionInfo extends TransactionInfo { class ElectrumTransactionInfo extends TransactionInfo {
List<BitcoinSilentPaymentsUnspent>? unspents;
ElectrumTransactionInfo(this.type, ElectrumTransactionInfo(this.type,
{required String id, {required String id,
required int height, required int height,
@ -29,7 +30,9 @@ class ElectrumTransactionInfo extends TransactionInfo {
required TransactionDirection direction, required TransactionDirection direction,
required bool isPending, required bool isPending,
required DateTime date, required DateTime date,
required int confirmations}) { required int confirmations,
String? to,
this.unspents}) {
this.id = id; this.id = id;
this.height = height; this.height = height;
this.amount = amount; this.amount = amount;
@ -40,6 +43,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
this.date = date; this.date = date;
this.isPending = isPending; this.isPending = isPending;
this.confirmations = confirmations; this.confirmations = confirmations;
this.to = to;
} }
factory ElectrumTransactionInfo.fromElectrumVerbose(Map<String, Object> obj, WalletType type, factory ElectrumTransactionInfo.fromElectrumVerbose(Map<String, Object> obj, WalletType type,
@ -153,52 +157,31 @@ class ElectrumTransactionInfo extends TransactionInfo {
confirmations: bundle.confirmations); confirmations: bundle.confirmations);
} }
factory ElectrumTransactionInfo.fromHexAndHeader(WalletType type, String hex,
{List<String>? addresses, required int height, int? timestamp, required int confirmations}) {
final tx = bitcoin.Transaction.fromHex(hex);
var exist = false;
var amount = 0;
if (addresses != null) {
tx.outs.forEach((out) {
try {
final p2pkh =
bitcoin.P2PKH(data: PaymentData(output: out.script), network: bitcoin.bitcoin);
exist = addresses.contains(p2pkh.data.address);
if (exist) {
amount += out.value!;
}
} catch (_) {}
});
}
final date =
timestamp != null ? DateTime.fromMillisecondsSinceEpoch(timestamp * 1000) : DateTime.now();
return ElectrumTransactionInfo(type,
id: tx.getId(),
height: height,
isPending: false,
fee: null,
direction: TransactionDirection.incoming,
amount: amount,
date: date,
confirmations: confirmations);
}
factory ElectrumTransactionInfo.fromJson(Map<String, dynamic> data, WalletType type) { factory ElectrumTransactionInfo.fromJson(Map<String, dynamic> data, WalletType type) {
return ElectrumTransactionInfo(type, final inputAddresses = data['inputAddresses'] as List<dynamic>? ?? [];
id: data['id'] as String, final outputAddresses = data['outputAddresses'] as List<dynamic>? ?? [];
height: data['height'] as int, final unspents = data['unspents'] as List<dynamic>? ?? [];
amount: data['amount'] as int,
fee: data['fee'] as int, return ElectrumTransactionInfo(
direction: parseTransactionDirectionFromInt(data['direction'] as int), type,
date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int), id: data['id'] as String,
isPending: data['isPending'] as bool, height: data['height'] as int,
inputAddresses: List<String>.from(data['inputAddresses'] as List), amount: data['amount'] as int,
outputAddresses: List<String>.from(data['outputAddresses'] as List), fee: data['fee'] as int,
confirmations: data['confirmations'] as int); direction: parseTransactionDirectionFromInt(data['direction'] as int),
date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
isPending: data['isPending'] as bool,
confirmations: data['confirmations'] as int,
inputAddresses:
inputAddresses.isEmpty ? [] : inputAddresses.map((e) => e.toString()).toList(),
outputAddresses:
outputAddresses.isEmpty ? [] : outputAddresses.map((e) => e.toString()).toList(),
to: data['to'] as String?,
unspents: unspents
.map((unspent) =>
BitcoinSilentPaymentsUnspent.fromJSON(null, unspent as Map<String, dynamic>))
.toList(),
);
} }
final WalletType type; final WalletType type;
@ -244,8 +227,14 @@ class ElectrumTransactionInfo extends TransactionInfo {
m['isPending'] = isPending; m['isPending'] = isPending;
m['confirmations'] = confirmations; m['confirmations'] = confirmations;
m['fee'] = fee; m['fee'] = fee;
m['to'] = to;
m['unspents'] = unspents?.map((e) => e.toJson()).toList() ?? [];
m['inputAddresses'] = inputAddresses; m['inputAddresses'] = inputAddresses;
m['outputAddresses'] = outputAddresses; m['outputAddresses'] = outputAddresses;
return m; return m;
} }
String toString() {
return 'ElectrumTransactionInfo(id: $id, height: $height, amount: $amount, fee: $fee, direction: $direction, date: $date, isPending: $isPending, confirmations: $confirmations, to: $to, unspent: $unspents)';
}
} }

File diff suppressed because it is too large Load diff

View file

@ -1,7 +1,7 @@
import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin; import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
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.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';
@ -25,15 +25,17 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
WalletInfo walletInfo, { WalletInfo walletInfo, {
required this.mainHd, required this.mainHd,
required this.sideHd, required this.sideHd,
required this.electrumClient,
required this.network, required this.network,
List<BitcoinAddressRecord>? initialAddresses, List<BitcoinAddressRecord>? initialAddresses,
Map<String, int>? initialRegularAddressIndex, Map<String, int>? initialRegularAddressIndex,
Map<String, int>? initialChangeAddressIndex, Map<String, int>? initialChangeAddressIndex,
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
int initialSilentAddressIndex = 0,
bitcoin.HDWallet? masterHd,
BitcoinAddressType? initialAddressPageType, BitcoinAddressType? initialAddressPageType,
}) : _addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()), }) : _addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()),
addressesByReceiveType = addressesByReceiveType =
ObservableList<BitcoinAddressRecord>.of((<BitcoinAddressRecord>[]).toSet()), ObservableList<BaseBitcoinAddressRecord>.of((<BitcoinAddressRecord>[]).toSet()),
receiveAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []) receiveAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? [])
.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed) .where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed)
.toSet()), .toSet()),
@ -46,7 +48,38 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
(walletInfo.addressPageType != null (walletInfo.addressPageType != null
? BitcoinAddressType.fromValue(walletInfo.addressPageType!) ? BitcoinAddressType.fromValue(walletInfo.addressPageType!)
: SegwitAddresType.p2wpkh), : SegwitAddresType.p2wpkh),
silentAddresses = ObservableList<BitcoinSilentPaymentAddressRecord>.of(
(initialSilentAddresses ?? []).toSet()),
currentSilentAddressIndex = initialSilentAddressIndex,
super(walletInfo) { super(walletInfo) {
if (masterHd != null) {
silentAddress = SilentPaymentOwner.fromPrivateKeys(
b_scan: ECPrivate.fromHex(masterHd.derivePath(SCAN_PATH).privKey!),
b_spend: ECPrivate.fromHex(masterHd.derivePath(SPEND_PATH).privKey!),
hrp: network == BitcoinNetwork.testnet ? 'tsp' : 'sp');
if (silentAddresses.length == 0) {
silentAddresses.add(BitcoinSilentPaymentAddressRecord(
silentAddress.toString(),
index: 0,
isHidden: false,
name: "",
silentPaymentTweak: null,
network: network,
type: SilentPaymentsAddresType.p2sp,
));
silentAddresses.add(BitcoinSilentPaymentAddressRecord(
silentAddress!.toLabeledSilentPaymentAddress(0).toString(),
index: 0,
isHidden: true,
name: "",
silentPaymentTweak: BytesUtils.toHexString(silentAddress!.generateLabel(0)),
network: network,
type: SilentPaymentsAddresType.p2sp,
));
}
}
updateAddressesByMatch(); updateAddressesByMatch();
} }
@ -55,27 +88,40 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
static const gap = 20; static const gap = 20;
final ObservableList<BitcoinAddressRecord> _addresses; final ObservableList<BitcoinAddressRecord> _addresses;
// Matched by addressPageType late ObservableList<BaseBitcoinAddressRecord> addressesByReceiveType;
late ObservableList<BitcoinAddressRecord> addressesByReceiveType;
final ObservableList<BitcoinAddressRecord> receiveAddresses; final ObservableList<BitcoinAddressRecord> receiveAddresses;
final ObservableList<BitcoinAddressRecord> changeAddresses; final ObservableList<BitcoinAddressRecord> changeAddresses;
final ElectrumClient electrumClient; final ObservableList<BitcoinSilentPaymentAddressRecord> silentAddresses;
final BasedUtxoNetwork network; final BasedUtxoNetwork network;
final bitcoin.HDWallet mainHd; final bitcoin.HDWallet mainHd;
final bitcoin.HDWallet sideHd; final bitcoin.HDWallet sideHd;
@observable
SilentPaymentOwner? silentAddress;
@observable @observable
late BitcoinAddressType _addressPageType; late BitcoinAddressType _addressPageType;
@computed @computed
BitcoinAddressType get addressPageType => _addressPageType; BitcoinAddressType get addressPageType => _addressPageType;
@observable
String? activeSilentAddress;
@computed @computed
List<BitcoinAddressRecord> get allAddresses => _addresses; List<BitcoinAddressRecord> get allAddresses => _addresses;
@override @override
@computed @computed
String get address { String get address {
if (addressPageType == SilentPaymentsAddresType.p2sp) {
if (activeSilentAddress != null) {
return activeSilentAddress!;
}
return silentAddress.toString();
}
String receiveAddress; String receiveAddress;
final typeMatchingReceiveAddresses = final typeMatchingReceiveAddresses =
@ -104,6 +150,18 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
@override @override
set address(String addr) { set address(String addr) {
if (addressPageType == SilentPaymentsAddresType.p2sp) {
final selected = silentAddresses.firstWhere((addressRecord) => addressRecord.address == addr);
if (selected.silentPaymentTweak != null && silentAddress != null) {
activeSilentAddress =
silentAddress!.toLabeledSilentPaymentAddress(selected.index).toString();
} else {
activeSilentAddress = silentAddress!.toString();
}
return;
}
final addressRecord = _addresses.firstWhere((addressRecord) => addressRecord.address == addr); final addressRecord = _addresses.firstWhere((addressRecord) => addressRecord.address == addr);
previousAddressRecord = addressRecord; previousAddressRecord = addressRecord;
@ -130,6 +188,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
void set currentChangeAddressIndex(int index) => void set currentChangeAddressIndex(int index) =>
currentChangeAddressIndexByType[_addressPageType.toString()] = index; currentChangeAddressIndexByType[_addressPageType.toString()] = index;
int currentSilentAddressIndex;
@observable @observable
BitcoinAddressRecord? previousAddressRecord; BitcoinAddressRecord? previousAddressRecord;
@ -198,7 +258,50 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
return address; return address;
} }
BitcoinAddressRecord generateNewAddress({String label = ''}) { Map<String, String> get labels {
final G = ECPublic.fromBytes(BigintUtils.toBytes(Curves.generatorSecp256k1.x, length: 32));
final labels = <String, String>{};
for (int i = 0; i < silentAddresses.length; i++) {
final silentAddressRecord = silentAddresses[i];
final silentPaymentTweak = silentAddressRecord.silentPaymentTweak;
if (silentPaymentTweak != null &&
SilentPaymentAddress.regex.hasMatch(silentAddressRecord.address)) {
labels[G
.tweakMul(BigintUtils.fromBytes(BytesUtils.fromHexString(silentPaymentTweak)))
.toHex()] = silentPaymentTweak;
}
}
return labels;
}
@action
BaseBitcoinAddressRecord generateNewAddress({String label = ''}) {
if (addressPageType == SilentPaymentsAddresType.p2sp && silentAddress != null) {
final currentSilentAddressIndex = silentAddresses
.where((addressRecord) => addressRecord.type != SegwitAddresType.p2tr)
.length -
1;
this.currentSilentAddressIndex = currentSilentAddressIndex;
final address = BitcoinSilentPaymentAddressRecord(
silentAddress!.toLabeledSilentPaymentAddress(currentSilentAddressIndex).toString(),
index: currentSilentAddressIndex,
isHidden: false,
name: label,
silentPaymentTweak:
BytesUtils.toHexString(silentAddress!.generateLabel(currentSilentAddressIndex)),
network: network,
type: SilentPaymentsAddresType.p2sp,
);
silentAddresses.add(address);
updateAddressesByMatch();
return address;
}
final newAddressIndex = addressesByReceiveType.fold( final newAddressIndex = addressesByReceiveType.fold(
0, (int acc, addressRecord) => addressRecord.isHidden == false ? acc + 1 : acc); 0, (int acc, addressRecord) => addressRecord.isHidden == false ? acc + 1 : acc);
@ -227,12 +330,70 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
Future<void> updateAddressesInBox() async { Future<void> updateAddressesInBox() async {
try { try {
addressesMap.clear(); addressesMap.clear();
addressesMap[address] = ''; addressesMap[address] = 'Active';
allAddressesMap.clear(); allAddressesMap.clear();
_addresses.forEach((addressRecord) { _addresses.forEach((addressRecord) {
allAddressesMap[addressRecord.address] = addressRecord.name; allAddressesMap[addressRecord.address] = addressRecord.name;
}); });
final lastP2wpkh = _addresses
.where((addressRecord) =>
_isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2wpkh))
.toList()
.last;
if (lastP2wpkh.address != address) {
addressesMap[lastP2wpkh.address] = 'P2WPKH';
} else {
addressesMap[address] = 'Active - P2WPKH';
}
final lastP2pkh = _addresses.firstWhere(
(addressRecord) => _isUnusedReceiveAddressByType(addressRecord, P2pkhAddressType.p2pkh));
if (lastP2pkh.address != address) {
addressesMap[lastP2pkh.address] = 'P2PKH';
} else {
addressesMap[address] = 'Active - P2PKH';
}
final lastP2sh = _addresses.firstWhere((addressRecord) =>
_isUnusedReceiveAddressByType(addressRecord, P2shAddressType.p2wpkhInP2sh));
if (lastP2sh.address != address) {
addressesMap[lastP2sh.address] = 'P2SH';
} else {
addressesMap[address] = 'Active - P2SH';
}
final lastP2tr = _addresses.firstWhere(
(addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2tr));
if (lastP2tr.address != address) {
addressesMap[lastP2tr.address] = 'P2TR';
} else {
addressesMap[address] = 'Active - P2TR';
}
final lastP2wsh = _addresses.firstWhere(
(addressRecord) => _isUnusedReceiveAddressByType(addressRecord, SegwitAddresType.p2wsh));
if (lastP2wsh.address != address) {
addressesMap[lastP2wsh.address] = 'P2WSH';
} else {
addressesMap[address] = 'Active - P2WSH';
}
silentAddresses.forEach((addressRecord) {
if (addressRecord.type != SilentPaymentsAddresType.p2sp || addressRecord.isHidden) {
return;
}
if (addressRecord.address != address) {
addressesMap[addressRecord.address] = addressRecord.name.isEmpty
? "Silent Payments"
: "Silent Payments - " + addressRecord.name;
} else {
addressesMap[address] = 'Active - Silent Payments';
}
});
await saveAddressesInBox(); await saveAddressesInBox();
} catch (e) { } catch (e) {
print(e.toString()); print(e.toString());
@ -241,18 +402,41 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
@action @action
void updateAddress(String address, String label) { void updateAddress(String address, String label) {
final addressRecord = BaseBitcoinAddressRecord? foundAddress;
_addresses.firstWhere((addressRecord) => addressRecord.address == address); _addresses.forEach((addressRecord) {
addressRecord.setNewName(label); if (addressRecord.address == address) {
final index = _addresses.indexOf(addressRecord); foundAddress = addressRecord;
_addresses.remove(addressRecord); }
_addresses.insert(index, addressRecord); });
silentAddresses.forEach((addressRecord) {
if (addressRecord.address == address) {
foundAddress = addressRecord;
}
});
updateAddressesByMatch(); if (foundAddress != null) {
foundAddress!.setNewName(label);
if (foundAddress is BitcoinAddressRecord) {
final index = _addresses.indexOf(foundAddress);
_addresses.remove(foundAddress);
_addresses.insert(index, foundAddress as BitcoinAddressRecord);
} else {
final index = silentAddresses.indexOf(foundAddress as BitcoinSilentPaymentAddressRecord);
silentAddresses.remove(foundAddress);
silentAddresses.insert(index, foundAddress as BitcoinSilentPaymentAddressRecord);
}
}
} }
@action @action
void updateAddressesByMatch() { void updateAddressesByMatch() {
if (addressPageType == SilentPaymentsAddresType.p2sp) {
addressesByReceiveType.clear();
addressesByReceiveType.addAll(silentAddresses);
return;
}
addressesByReceiveType.clear(); addressesByReceiveType.clear();
addressesByReceiveType.addAll(_addresses.where(_isAddressPageTypeMatch).toList()); addressesByReceiveType.addAll(_addresses.where(_isAddressPageTypeMatch).toList());
} }
@ -278,7 +462,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
@action @action
Future<void> discoverAddresses(List<BitcoinAddressRecord> addressList, bool isHidden, Future<void> discoverAddresses(List<BitcoinAddressRecord> addressList, bool isHidden,
Future<String?> Function(BitcoinAddressRecord, Set<String>) getAddressHistory, Future<String?> Function(BitcoinAddressRecord) getAddressHistory,
{BitcoinAddressType type = SegwitAddresType.p2wpkh}) async { {BitcoinAddressType type = SegwitAddresType.p2wpkh}) async {
if (!isHidden) { if (!isHidden) {
_validateSideHdAddresses(addressList.toList()); _validateSideHdAddresses(addressList.toList());
@ -288,8 +472,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
startIndex: addressList.length, isHidden: isHidden, type: type); startIndex: addressList.length, isHidden: isHidden, type: type);
addAddresses(newAddresses); addAddresses(newAddresses);
final addressesWithHistory = await Future.wait(newAddresses final addressesWithHistory = await Future.wait(newAddresses.map(getAddressHistory));
.map((addr) => getAddressHistory(addr, _addresses.map((e) => e.address).toSet())));
final isLastAddressUsed = addressesWithHistory.last == addressList.last.address; final isLastAddressUsed = addressesWithHistory.last == addressList.last.address;
if (isLastAddressUsed) { if (isLastAddressUsed) {
@ -355,6 +538,15 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
updateAddressesByMatch(); updateAddressesByMatch();
} }
@action
void addSilentAddresses(Iterable<BitcoinSilentPaymentAddressRecord> addresses) {
final addressesSet = this.silentAddresses.toSet();
addressesSet.addAll(addresses);
this.silentAddresses.clear();
this.silentAddresses.addAll(addressesSet);
updateAddressesByMatch();
}
void _validateSideHdAddresses(List<BitcoinAddressRecord> addrWithTransactions) { void _validateSideHdAddresses(List<BitcoinAddressRecord> addrWithTransactions) {
addrWithTransactions.forEach((element) { addrWithTransactions.forEach((element) {
if (element.address != if (element.address !=
@ -377,4 +569,15 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
bitcoin.HDWallet _getHd(bool isHidden) => isHidden ? sideHd : mainHd; bitcoin.HDWallet _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) =>
!addr.isHidden && !addr.isUsed && addr.type == type;
@action
void deleteSilentPaymentAddress(String address) {
final addressRecord = silentAddresses.firstWhere((addressRecord) =>
addressRecord.type == SilentPaymentsAddresType.p2sp && addressRecord.address == address);
silentAddresses.remove(addressRecord);
updateAddressesByMatch();
}
} }

View file

@ -19,6 +19,8 @@ class ElectrumWalletSnapshot {
required this.regularAddressIndex, required this.regularAddressIndex,
required this.changeAddressIndex, required this.changeAddressIndex,
required this.addressPageType, required this.addressPageType,
required this.silentAddresses,
required this.silentAddressIndex,
this.passphrase, this.passphrase,
this.derivationType, this.derivationType,
this.derivationPath, this.derivationPath,
@ -32,9 +34,11 @@ class ElectrumWalletSnapshot {
String? mnemonic; String? mnemonic;
String? xpub; String? xpub;
List<BitcoinAddressRecord> addresses; List<BitcoinAddressRecord> addresses;
List<BitcoinSilentPaymentAddressRecord> silentAddresses;
ElectrumBalance balance; ElectrumBalance balance;
Map<String, int> regularAddressIndex; Map<String, int> regularAddressIndex;
Map<String, int> changeAddressIndex; Map<String, int> changeAddressIndex;
int silentAddressIndex;
String? passphrase; String? passphrase;
DerivationType? derivationType; DerivationType? derivationType;
String? derivationPath; String? derivationPath;
@ -50,15 +54,23 @@ class ElectrumWalletSnapshot {
final passphrase = data['passphrase'] as String? ?? ''; final passphrase = data['passphrase'] as String? ?? '';
final addresses = addressesTmp final addresses = addressesTmp
.whereType<String>() .whereType<String>()
.map((addr) => BitcoinAddressRecord.fromJSON(addr, network)) .map((addr) => BitcoinAddressRecord.fromJSON(addr, network: network))
.toList(); .toList();
final balance = ElectrumBalance.fromJSON(data['balance'] as String) ??
final silentAddressesTmp = data['silent_addresses'] as List? ?? <Object>[];
final silentAddresses = silentAddressesTmp
.whereType<String>()
.map((addr) => BitcoinSilentPaymentAddressRecord.fromJSON(addr, network: network))
.toList();
final balance = ElectrumBalance.fromJSON(data['balance'] as String?) ??
ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0); ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0);
var regularAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0}; var regularAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0};
var changeAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0}; var changeAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0};
var silentAddressIndex = 0;
final derivationType = final derivationType = DerivationType
DerivationType.values[(data['derivationTypeIndex'] as int?) ?? DerivationType.electrum.index]; .values[(data['derivationTypeIndex'] as int?) ?? DerivationType.electrum.index];
final derivationPath = data['derivationPath'] as String? ?? "m/0'/0"; final derivationPath = data['derivationPath'] as String? ?? "m/0'/0";
try { try {
@ -69,6 +81,7 @@ class ElectrumWalletSnapshot {
SegwitAddresType.p2wpkh.toString(): SegwitAddresType.p2wpkh.toString():
int.parse(data['change_address_index'] as String? ?? '0') int.parse(data['change_address_index'] as String? ?? '0')
}; };
silentAddressIndex = int.parse(data['silent_address_index'] as String? ?? '0');
} catch (_) { } catch (_) {
try { try {
regularAddressIndexByType = data["account_index"] as Map<String, int>? ?? {}; regularAddressIndexByType = data["account_index"] as Map<String, int>? ?? {};
@ -90,6 +103,8 @@ class ElectrumWalletSnapshot {
addressPageType: data['address_page_type'] as String?, addressPageType: data['address_page_type'] as String?,
derivationType: derivationType, derivationType: derivationType,
derivationPath: derivationPath, derivationPath: derivationPath,
silentAddresses: silentAddresses,
silentAddressIndex: silentAddressIndex,
); );
} }
} }

View file

@ -2,7 +2,7 @@ import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/exceptions.dart'; import 'package:cw_core/exceptions.dart';
class BitcoinTransactionWrongBalanceException extends TransactionWrongBalanceException { class BitcoinTransactionWrongBalanceException extends TransactionWrongBalanceException {
BitcoinTransactionWrongBalanceException() : super(CryptoCurrency.btc); BitcoinTransactionWrongBalanceException({super.amount}) : super(CryptoCurrency.btc);
} }
class BitcoinTransactionNoInputsException extends TransactionNoInputsException {} class BitcoinTransactionNoInputsException extends TransactionNoInputsException {}
@ -27,3 +27,7 @@ class BitcoinTransactionCommitFailedDustOutputSendAll
extends TransactionCommitFailedDustOutputSendAll {} extends TransactionCommitFailedDustOutputSendAll {}
class BitcoinTransactionCommitFailedVoutNegative extends TransactionCommitFailedVoutNegative {} class BitcoinTransactionCommitFailedVoutNegative extends TransactionCommitFailedVoutNegative {}
class BitcoinTransactionCommitFailedBIP68Final extends TransactionCommitFailedBIP68Final {}
class BitcoinTransactionSilentPaymentsNotSupported extends TransactionInputNotSupported {}

View file

@ -63,7 +63,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
currency: CryptoCurrency.ltc) { currency: CryptoCurrency.ltc) {
walletAddresses = LitecoinWalletAddresses( walletAddresses = LitecoinWalletAddresses(
walletInfo, walletInfo,
electrumClient: electrumClient,
initialAddresses: initialAddresses, initialAddresses: initialAddresses,
initialRegularAddressIndex: initialRegularAddressIndex, initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex, initialChangeAddressIndex: initialChangeAddressIndex,

View file

@ -19,7 +19,6 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
required super.sideHd, required super.sideHd,
required this.mwebHd, required this.mwebHd,
required super.network, required super.network,
required super.electrumClient,
super.initialAddresses, super.initialAddresses,
super.initialRegularAddressIndex, super.initialRegularAddressIndex,
super.initialChangeAddressIndex, super.initialChangeAddressIndex,

View file

@ -79,6 +79,11 @@ class PendingBitcoinTransaction with PendingTransaction {
if (error.contains("bad-txns-vout-negative")) { if (error.contains("bad-txns-vout-negative")) {
throw BitcoinTransactionCommitFailedVoutNegative(); throw BitcoinTransactionCommitFailedVoutNegative();
} }
if (error.contains("non-BIP68-final")) {
throw BitcoinTransactionCommitFailedBIP68Final();
}
throw BitcoinTransactionCommitFailed(errorMessage: error); throw BitcoinTransactionCommitFailed(errorMessage: error);
} }

View file

@ -37,10 +37,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: asn1lib name: asn1lib
sha256: c9c85fedbe2188b95133cbe960e16f5f448860f7133330e272edbbca5893ddc6 sha256: "58082b3f0dca697204dbab0ef9ff208bfaea7767ea771076af9a343488428dda"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.5.2" version: "1.5.3"
async: async:
dependency: transitive dependency: transitive
description: description:
@ -87,11 +87,11 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
path: "." path: "."
ref: cake-mweb ref: cake-update-v3
resolved-ref: "9389210f4c0376ac6f5dfe5aeabc042adc76449c" resolved-ref: cc99eedb1d28ee9376dda0465ef72aa627ac6149
url: "https://github.com/cake-tech/bitcoin_base.git" url: "https://github.com/cake-tech/bitcoin_base"
source: git source: git
version: "4.2.0" version: "4.2.1"
bitcoin_flutter: bitcoin_flutter:
dependency: "direct main" dependency: "direct main"
description: description:
@ -104,11 +104,12 @@ packages:
blockchain_utils: blockchain_utils:
dependency: "direct main" dependency: "direct main"
description: description:
name: blockchain_utils path: "."
sha256: "38ef5f4a22441ac4370aed9071dc71c460acffc37c79b344533f67d15f24c13c" ref: cake-update-v1
url: "https://pub.dev" resolved-ref: cabd7e0e16c4da9920338c76eff3aeb8af0211f3
source: hosted url: "https://github.com/cake-tech/blockchain_utils"
version: "2.1.1" source: git
version: "2.1.2"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -205,6 +206,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.3" version: "2.0.3"
cli_util:
dependency: transitive
description:
name: cli_util
sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19
url: "https://pub.dev"
source: hosted
version: "0.4.1"
clock: clock:
dependency: transitive dependency: transitive
description: description:
@ -249,10 +258,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: cryptography name: cryptography
sha256: df156c5109286340817d21fa7b62f9140f17915077127dd70f8bd7a2a0997a35 sha256: d146b76d33d94548cf035233fbc2f4338c1242fa119013bead807d033fc4ae05
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.0" version: "2.7.0"
cw_core: cw_core:
dependency: "direct main" dependency: "direct main"
description: description:
@ -303,10 +312,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: ffi name: ffi
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.0" version: "2.1.2"
ffigen:
dependency: transitive
description:
name: ffigen
sha256: d3e76c2ad48a4e7f93a29a162006f00eba46ce7c08194a77bb5c5e97d1b5ff0a
url: "https://pub.dev"
source: hosted
version: "8.0.2"
file: file:
dependency: transitive dependency: transitive
description: description:
@ -361,10 +378,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: functional_data name: functional_data
sha256: aefdec4365452283b2a7cf420a3169654d51d3e9553069a22d76680d7a9d7c3d sha256: "76d17dc707c40e552014f5a49c0afcc3f1e3f05e800cd6b7872940bfe41a5039"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.1" version: "1.2.0"
glob: glob:
dependency: transitive dependency: transitive
description: description:
@ -425,18 +442,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: http name: http
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.0" version: "1.2.1"
http2:
dependency: transitive
description:
name: http2
sha256: "9ced024a160b77aba8fb8674e38f70875e321d319e6f303ec18e87bd5a4b0c1d"
url: "https://pub.dev"
source: hosted
version: "2.3.0"
http_multi_server: http_multi_server:
dependency: transitive dependency: transitive
description: description:
@ -481,10 +490,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: json_annotation name: json_annotation
sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.8.1" version: "4.9.0"
leak_tracker: leak_tracker:
dependency: transitive dependency: transitive
description: description:
@ -570,10 +579,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: mime name: mime
sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.4" version: "1.0.5"
mobx: mobx:
dependency: "direct main" dependency: "direct main"
description: description:
@ -618,26 +627,26 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: path_provider name: path_provider
sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.2" version: "2.1.3"
path_provider_android: path_provider_android:
dependency: transitive dependency: transitive
description: description:
name: path_provider_android name: path_provider_android
sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.2" version: "2.2.4"
path_provider_foundation: path_provider_foundation:
dependency: transitive dependency: transitive
description: description:
name: path_provider_foundation name: path_provider_foundation
sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.2" version: "2.4.0"
path_provider_linux: path_provider_linux:
dependency: transitive dependency: transitive
description: description:
@ -682,10 +691,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: pointycastle name: pointycastle
sha256: "70fe966348fe08c34bf929582f1d8247d9d9408130723206472b4687227e4333" sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.8.0" version: "3.9.1"
pool: pool:
dependency: transitive dependency: transitive
description: description:
@ -726,6 +735,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.3" version: "1.2.3"
quiver:
dependency: transitive
description:
name: quiver
sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47
url: "https://pub.dev"
source: hosted
version: "3.2.1"
reactive_ble_mobile: reactive_ble_mobile:
dependency: transitive dependency: transitive
description: description:
@ -775,10 +792,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: socks5_proxy name: socks5_proxy
sha256: "1d21b5606169654bbf4cfb904e8e6ed897e9f763358709f87310c757096d909a" sha256: "045cbba84f6e2b01c1c77634a63e926352bf110ef5f07fc462c6d43bbd4b6a83"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.4" version: "1.0.5+dev.2"
source_gen: source_gen:
dependency: transitive dependency: transitive
description: description:
@ -803,6 +820,15 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.10.0" version: "1.10.0"
sp_scanner:
dependency: "direct main"
description:
path: "."
ref: "sp_v1.0.0"
resolved-ref: a9a4c6d051f37a15a3a52cc2a0094f24c68b62c5
url: "https://github.com/cake-tech/sp_scanner"
source: git
version: "0.0.1"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -899,22 +925,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.0" version: "1.1.0"
web:
dependency: transitive
description:
name: web
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
url: "https://pub.dev"
source: hosted
version: "0.5.1"
web_socket_channel: web_socket_channel:
dependency: transitive dependency: transitive
description: description:
name: web_socket_channel name: web_socket_channel
sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.4.0" version: "2.4.5"
win32: win32:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3" sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.9" version: "5.5.0"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
@ -931,6 +965,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.2" version: "3.1.2"
yaml_edit:
dependency: transitive
description:
name: yaml_edit
sha256: e9c1a3543d2da0db3e90270dbb1e4eebc985ee5e3ffe468d83224472b2194a5f
url: "https://pub.dev"
source: hosted
version: "2.2.1"
sdks: sdks:
dart: ">=3.2.0-0 <4.0.0" dart: ">=3.3.0 <4.0.0"
flutter: ">=3.10.0" flutter: ">=3.16.6"

View file

@ -32,9 +32,12 @@ dependencies:
cryptography: ^2.0.5 cryptography: ^2.0.5
bitcoin_base: bitcoin_base:
git: git:
url: https://github.com/cake-tech/bitcoin_base.git url: https://github.com/cake-tech/bitcoin_base
ref: cake-mweb ref: cake-mweb
blockchain_utils: ^2.1.1 blockchain_utils:
git:
url: https://github.com/cake-tech/blockchain_utils
ref: cake-update-v1
ledger_flutter: ^1.0.1 ledger_flutter: ^1.0.1
ledger_bitcoin: ledger_bitcoin:
git: git:
@ -42,6 +45,10 @@ dependencies:
cw_mweb: cw_mweb:
path: ../cw_mweb path: ../cw_mweb
grpc: ^3.2.4 grpc: ^3.2.4
sp_scanner:
git:
url: https://github.com/cake-tech/sp_scanner
ref: sp_v2.0.0
dev_dependencies: dev_dependencies:

View file

@ -46,7 +46,6 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
currency: CryptoCurrency.bch) { currency: CryptoCurrency.bch) {
walletAddresses = BitcoinCashWalletAddresses( walletAddresses = BitcoinCashWalletAddresses(
walletInfo, walletInfo,
electrumClient: electrumClient,
initialAddresses: initialAddresses, initialAddresses: initialAddresses,
initialRegularAddressIndex: initialRegularAddressIndex, initialRegularAddressIndex: initialRegularAddressIndex,
initialChangeAddressIndex: initialChangeAddressIndex, initialChangeAddressIndex: initialChangeAddressIndex,

View file

@ -15,7 +15,6 @@ 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.electrumClient,
super.initialAddresses, super.initialAddresses,
super.initialRegularAddressIndex, super.initialRegularAddressIndex,
super.initialChangeAddressIndex, super.initialChangeAddressIndex,

View file

@ -31,10 +31,12 @@ dependencies:
ref: Add-Support-For-OP-Return-data ref: Add-Support-For-OP-Return-data
bitcoin_base: bitcoin_base:
git: git:
url: https://github.com/cake-tech/bitcoin_base.git url: https://github.com/cake-tech/bitcoin_base
ref: cake-mweb ref: cake-mweb
blockchain_utils:
git:
url: https://github.com/cake-tech/blockchain_utils
ref: cake-update-v1
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View file

@ -104,6 +104,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
CryptoCurrency.digibyte, CryptoCurrency.digibyte,
CryptoCurrency.usdtSol, CryptoCurrency.usdtSol,
CryptoCurrency.usdcTrc20, CryptoCurrency.usdcTrc20,
CryptoCurrency.tbtc,
]; ];
static const havenCurrencies = [ static const havenCurrencies = [
@ -218,7 +219,8 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
static const kaspa = CryptoCurrency(title: 'KAS', fullName: 'Kaspa', raw: 89, name: 'kas', iconPath: 'assets/images/kaspa_icon.png', decimals: 8); static const kaspa = CryptoCurrency(title: 'KAS', fullName: 'Kaspa', raw: 89, name: 'kas', iconPath: 'assets/images/kaspa_icon.png', decimals: 8);
static const digibyte = CryptoCurrency(title: 'DGB', fullName: 'DigiByte', raw: 90, name: 'dgb', iconPath: 'assets/images/digibyte.png', decimals: 8); static const digibyte = CryptoCurrency(title: 'DGB', fullName: 'DigiByte', raw: 90, name: 'dgb', iconPath: 'assets/images/digibyte.png', decimals: 8);
static const usdtSol = CryptoCurrency(title: 'USDT', tag: 'SOL', fullName: 'USDT Tether', raw: 91, name: 'usdtsol', iconPath: 'assets/images/usdt_icon.png', decimals: 6); static const usdtSol = CryptoCurrency(title: 'USDT', tag: 'SOL', fullName: 'USDT Tether', raw: 91, name: 'usdtsol', iconPath: 'assets/images/usdt_icon.png', decimals: 6);
static const usdcTrc20 = CryptoCurrency(title: 'USDC', tag: 'TRX', fullName: 'USDC Coin', raw: 92, name: 'usdctrc20', iconPath: 'assets/images/usdc_icon.png', decimals: 6); static const usdcTrc20 = CryptoCurrency(title: 'USDC', tag: 'TRX', fullName: 'USDC Coin', raw: 92, name: 'usdctrc20', iconPath: 'assets/images/usdc_icon.png', decimals: 6);
static const tbtc = CryptoCurrency(title: 'tBTC', fullName: 'Testnet Bitcoin', raw: 93, name: 'tbtc', iconPath: 'assets/images/tbtc.png', decimals: 8);
static final Map<int, CryptoCurrency> _rawCurrencyMap = static final Map<int, CryptoCurrency> _rawCurrencyMap =

View file

@ -1,9 +1,12 @@
import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
CryptoCurrency currencyForWalletType(WalletType type) { CryptoCurrency currencyForWalletType(WalletType type, {bool? isTestnet}) {
switch (type) { switch (type) {
case WalletType.bitcoin: case WalletType.bitcoin:
if (isTestnet == true) {
return CryptoCurrency.tbtc;
}
return CryptoCurrency.btc; return CryptoCurrency.btc;
case WalletType.monero: case WalletType.monero:
return CryptoCurrency.xmr; return CryptoCurrency.xmr;

View file

@ -1,9 +1,10 @@
import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/crypto_currency.dart';
class TransactionWrongBalanceException implements Exception { class TransactionWrongBalanceException implements Exception {
TransactionWrongBalanceException(this.currency); TransactionWrongBalanceException(this.currency, {this.amount});
final CryptoCurrency currency; final CryptoCurrency currency;
final int? amount;
} }
class TransactionNoInputsException implements Exception {} class TransactionNoInputsException implements Exception {}
@ -32,3 +33,7 @@ class TransactionCommitFailedDustOutput implements Exception {}
class TransactionCommitFailedDustOutputSendAll implements Exception {} class TransactionCommitFailedDustOutputSendAll implements Exception {}
class TransactionCommitFailedVoutNegative implements Exception {} class TransactionCommitFailedVoutNegative implements Exception {}
class TransactionCommitFailedBIP68Final implements Exception {}
class TransactionInputNotSupported implements Exception {}

View file

@ -242,3 +242,57 @@ Future<int> getHavenCurrentHeight() async {
throw Exception('Failed to load current blockchain height'); throw Exception('Failed to load current blockchain height');
} }
} }
// Data taken from https://timechaincalendar.com/
const bitcoinDates = {
"2024-05": 841590,
"2024-04": 837182,
"2024-03": 832623,
"2024-02": 828319,
"2024-01": 823807,
"2023-12": 819206,
"2023-11": 814765,
"2023-10": 810098,
"2023-09": 805675,
"2023-08": 801140,
"2023-07": 796640,
"2023-06": 792330,
"2023-05": 787733,
"2023-04": 783403,
"2023-03": 778740,
"2023-02": 774525,
"2023-01": 769810,
};
int getBitcoinHeightByDate({required DateTime date}) {
String dateKey = '${date.year}-${date.month.toString().padLeft(2, '0')}';
final closestKey = bitcoinDates.keys
.firstWhere((key) => formatMapKey(key).isBefore(date), orElse: () => bitcoinDates.keys.last);
final beginningBlock = bitcoinDates[dateKey] ?? bitcoinDates[closestKey]!;
final startOfMonth = DateTime(date.year, date.month);
final daysDifference = date.difference(startOfMonth).inDays;
// approximately 6 blocks per hour, 24 hours per day
int estimatedBlocksSinceStartOfMonth = (daysDifference * 24 * 6);
return beginningBlock + estimatedBlocksSinceStartOfMonth;
}
DateTime getDateByBitcoinHeight(int height) {
final closestEntry = bitcoinDates.entries
.lastWhere((entry) => entry.value >= height, orElse: () => bitcoinDates.entries.first);
final beginningBlock = closestEntry.value;
final startOfMonth = formatMapKey(closestEntry.key);
final blocksDifference = height - beginningBlock;
final hoursDifference = blocksDifference / 5.5;
final estimatedDate = startOfMonth.add(Duration(hours: hoursDifference.ceil()));
if (estimatedDate.isAfter(DateTime.now())) {
return DateTime.now();
}
return estimatedDate;
}

View file

@ -244,8 +244,12 @@ class Node extends HiveObject with Keyable {
Future<bool> requestElectrumServer() async { Future<bool> requestElectrumServer() async {
try { try {
await SecureSocket.connect(uri.host, uri.port, if (useSSL == true) {
timeout: Duration(seconds: 5), onBadCertificate: (_) => true); await SecureSocket.connect(uri.host, uri.port,
timeout: Duration(seconds: 5), onBadCertificate: (_) => true);
} else {
await Socket.connect(uri.host, uri.port, timeout: Duration(seconds: 5));
}
return true; return true;
} catch (_) { } catch (_) {
return false; return false;

View file

@ -14,6 +14,16 @@ class SyncingSyncStatus extends SyncStatus {
@override @override
String toString() => '$blocksLeft'; String toString() => '$blocksLeft';
factory SyncingSyncStatus.fromHeightValues(int chainTip, int initialSyncHeight, int syncHeight) {
final track = chainTip - initialSyncHeight;
final diff = track - (chainTip - syncHeight);
final ptc = diff <= 0 ? 0.0 : diff / track;
final left = chainTip - syncHeight;
// sum 1 because if at the chain tip, will say "0 blocks left"
return SyncingSyncStatus(left + 1, ptc);
}
} }
class SyncedSyncStatus extends SyncStatus { class SyncedSyncStatus extends SyncStatus {
@ -21,6 +31,17 @@ class SyncedSyncStatus extends SyncStatus {
double progress() => 1.0; double progress() => 1.0;
} }
class SyncedTipSyncStatus extends SyncedSyncStatus {
SyncedTipSyncStatus(this.tip);
final int tip;
}
class SyncronizingSyncStatus extends SyncStatus {
@override
double progress() => 0.0;
}
class NotConnectedSyncStatus extends SyncStatus { class NotConnectedSyncStatus extends SyncStatus {
const NotConnectedSyncStatus(); const NotConnectedSyncStatus();
@ -33,10 +54,7 @@ class AttemptingSyncStatus extends SyncStatus {
double progress() => 0.0; double progress() => 0.0;
} }
class FailedSyncStatus extends SyncStatus { class FailedSyncStatus extends NotConnectedSyncStatus {}
@override
double progress() => 1.0;
}
class ConnectingSyncStatus extends SyncStatus { class ConnectingSyncStatus extends SyncStatus {
@override @override
@ -48,7 +66,14 @@ class ConnectedSyncStatus extends SyncStatus {
double progress() => 0.0; double progress() => 0.0;
} }
class LostConnectionSyncStatus extends SyncStatus { class UnsupportedSyncStatus extends NotConnectedSyncStatus {}
class TimedOutSyncStatus extends NotConnectedSyncStatus {
@override @override
double progress() => 1.0; String toString() => 'Timed out';
} }
class LostConnectionSyncStatus extends NotConnectedSyncStatus {
@override
String toString() => 'Reconnecting';
}

View file

@ -16,7 +16,8 @@ class UnspentCoinsInfo extends HiveObject {
required this.value, required this.value,
this.keyImage = null, this.keyImage = null,
this.isChange = false, this.isChange = false,
this.accountIndex = 0 this.accountIndex = 0,
this.isSilentPayment = false,
}); });
static const typeId = UNSPENT_COINS_INFO_TYPE_ID; static const typeId = UNSPENT_COINS_INFO_TYPE_ID;
@ -49,13 +50,16 @@ class UnspentCoinsInfo extends HiveObject {
@HiveField(8, defaultValue: null) @HiveField(8, defaultValue: null)
String? keyImage; String? keyImage;
@HiveField(9, defaultValue: false) @HiveField(9, defaultValue: false)
bool isChange; bool isChange;
@HiveField(10, defaultValue: 0) @HiveField(10, defaultValue: 0)
int accountIndex; int accountIndex;
@HiveField(11, defaultValue: false)
bool? isSilentPayment;
String get note => noteRaw ?? ''; String get note => noteRaw ?? '';
set note(String value) => noteRaw = value; set note(String value) => noteRaw = value;

View file

@ -17,5 +17,6 @@ class Unspent {
int? confirmations; int? confirmations;
String note; String note;
bool get isP2wpkh => address.startsWith('bc') || address.startsWith('ltc'); bool get isP2wpkh =>
address.startsWith('bc') || address.startsWith('tb') || address.startsWith('ltc');
} }

View file

@ -24,7 +24,7 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
WalletType get type => walletInfo.type; WalletType get type => walletInfo.type;
CryptoCurrency get currency => currencyForWalletType(type); CryptoCurrency get currency => currencyForWalletType(type, isTestnet: isTestnet);
String get id => walletInfo.id; String get id => walletInfo.id;

View file

@ -66,21 +66,21 @@ class DerivationInfo extends HiveObject {
@HiveType(typeId: WalletInfo.typeId) @HiveType(typeId: WalletInfo.typeId)
class WalletInfo extends HiveObject { class WalletInfo extends HiveObject {
WalletInfo( WalletInfo(
this.id, this.id,
this.name, this.name,
this.type, this.type,
this.isRecovery, this.isRecovery,
this.restoreHeight, this.restoreHeight,
this.timestamp, this.timestamp,
this.dirPath, this.dirPath,
this.path, this.path,
this.address, this.address,
this.yatEid, this.yatEid,
this.yatLastUsedAddressRaw, this.yatLastUsedAddressRaw,
this.showIntroCakePayCard, this.showIntroCakePayCard,
this.derivationInfo, this.derivationInfo,
this.hardwareWalletType, this.hardwareWalletType,
): _yatLastUsedAddressController = StreamController<String>.broadcast(); ) : _yatLastUsedAddressController = StreamController<String>.broadcast();
factory WalletInfo.external({ factory WalletInfo.external({
required String id, required String id,
@ -207,4 +207,9 @@ class WalletInfo extends HiveObject {
Stream<String> get yatLastUsedAddressStream => _yatLastUsedAddressController.stream; Stream<String> get yatLastUsedAddressStream => _yatLastUsedAddressController.stream;
StreamController<String> _yatLastUsedAddressController; StreamController<String> _yatLastUsedAddressController;
Future<void> updateRestoreHeight(int height) async {
restoreHeight = height;
await save();
}
} }

View file

@ -173,11 +173,14 @@ String walletTypeToDisplayName(WalletType type) {
} }
} }
CryptoCurrency walletTypeToCryptoCurrency(WalletType type) { CryptoCurrency walletTypeToCryptoCurrency(WalletType type, {bool isTestnet = false}) {
switch (type) { switch (type) {
case WalletType.monero: case WalletType.monero:
return CryptoCurrency.xmr; return CryptoCurrency.xmr;
case WalletType.bitcoin: case WalletType.bitcoin:
if (isTestnet) {
return CryptoCurrency.tbtc;
}
return CryptoCurrency.btc; return CryptoCurrency.btc;
case WalletType.litecoin: case WalletType.litecoin:
return CryptoCurrency.ltc; return CryptoCurrency.ltc;

View file

@ -1,147 +0,0 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
async:
dependency: transitive
description:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.5.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
characters:
dependency: transitive
description:
name: characters
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
clock:
dependency: transitive
description:
name: clock
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.dartlang.org"
source: hosted
version: "1.15.0"
fake_async:
dependency: transitive
description:
name: fake_async
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
matcher:
dependency: transitive
description:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.10"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
path:
dependency: transitive
description:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_span:
dependency: transitive
description:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.dartlang.org"
source: hosted
version: "1.10.0"
stream_channel:
dependency: transitive
description:
name: stream_channel
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
string_scanner:
dependency: transitive
description:
name: string_scanner
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.19"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
sdks:
dart: ">=2.12.0-0.0 <3.0.0"
flutter: ">=1.20.0"

View file

@ -15,8 +15,14 @@ dependencies:
path: ../cw_core path: ../cw_core
cw_evm: cw_evm:
path: ../cw_evm path: ../cw_evm
on_chain: ^3.0.1 on_chain:
blockchain_utils: ^2.1.1 git:
url: https://github.com/cake-tech/On_chain
ref: cake-update-v1
blockchain_utils:
git:
url: https://github.com/cake-tech/blockchain_utils
ref: cake-update-v1
mobx: ^2.3.0+1 mobx: ^2.3.0+1
bip39: ^1.0.6 bip39: ^1.0.6
hive: ^2.2.3 hive: ^2.2.3

View file

@ -142,27 +142,9 @@ Then we need to generate localization files.
`$ flutter packages pub run tool/generate_localization.dart` `$ flutter packages pub run tool/generate_localization.dart`
Lastly, we will generate mobx models for the project.
Generate mobx models for `cw_core`:
`cd cw_core && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..`
Generate mobx models for `cw_monero`:
`cd cw_monero && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..`
Generate mobx models for `cw_bitcoin`:
`cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..`
Generate mobx models for `cw_haven`:
`cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd ..`
Finally build mobx models for the app: Finally build mobx models for the app:
`$ flutter packages pub run build_runner build --delete-conflicting-outputs` `$ ./model_generator.sh`
### 9. Build! ### 9. Build!

View file

@ -147,6 +147,8 @@ PODS:
- shared_preferences_foundation (0.0.1): - shared_preferences_foundation (0.0.1):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
- sp_scanner (0.0.1):
- Flutter
- SwiftProtobuf (1.25.2) - SwiftProtobuf (1.25.2)
- SwiftyGif (5.4.4) - SwiftyGif (5.4.4)
- Toast (4.1.0) - Toast (4.1.0)
@ -188,6 +190,7 @@ DEPENDENCIES:
- sensitive_clipboard (from `.symlinks/plugins/sensitive_clipboard/ios`) - sensitive_clipboard (from `.symlinks/plugins/sensitive_clipboard/ios`)
- share_plus (from `.symlinks/plugins/share_plus/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- sp_scanner (from `.symlinks/plugins/sp_scanner/ios`)
- uni_links (from `.symlinks/plugins/uni_links/ios`) - uni_links (from `.symlinks/plugins/uni_links/ios`)
- UnstoppableDomainsResolution (~> 4.0.0) - UnstoppableDomainsResolution (~> 4.0.0)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
@ -259,6 +262,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/share_plus/ios" :path: ".symlinks/plugins/share_plus/ios"
shared_preferences_foundation: shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin" :path: ".symlinks/plugins/shared_preferences_foundation/darwin"
sp_scanner:
:path: ".symlinks/plugins/sp_scanner/ios"
uni_links: uni_links:
:path: ".symlinks/plugins/uni_links/ios" :path: ".symlinks/plugins/uni_links/ios"
url_launcher_ios: url_launcher_ios:
@ -302,6 +307,7 @@ SPEC CHECKSUMS:
sensitive_clipboard: d4866e5d176581536c27bb1618642ee83adca986 sensitive_clipboard: d4866e5d176581536c27bb1618642ee83adca986
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68 share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
sp_scanner: eaa617fa827396b967116b7f1f43549ca62e9a12
SwiftProtobuf: 407a385e97fd206c4fbe880cc84123989167e0d1 SwiftProtobuf: 407a385e97fd206c4fbe880cc84123989167e0d1
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
Toast: ec33c32b8688982cecc6348adeae667c1b9938da Toast: ec33c32b8688982cecc6348adeae667c1b9938da

View file

@ -121,20 +121,12 @@ class CWBitcoin extends Bitcoin {
priority: priority != null ? priority as BitcoinTransactionPriority : null, priority: priority != null ? priority as BitcoinTransactionPriority : null,
feeRate: feeRate); feeRate: feeRate);
@override
List<String> getAddresses(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet;
return bitcoinWallet.walletAddresses.addressesByReceiveType
.map((BitcoinAddressRecord addr) => addr.address)
.toList();
}
@override @override
@computed @computed
List<ElectrumSubAddress> getSubAddresses(Object wallet) { List<ElectrumSubAddress> getSubAddresses(Object wallet) {
final electrumWallet = wallet as ElectrumWallet; final electrumWallet = wallet as ElectrumWallet;
return electrumWallet.walletAddresses.addressesByReceiveType return electrumWallet.walletAddresses.addressesByReceiveType
.map((BitcoinAddressRecord addr) => ElectrumSubAddress( .map((BaseBitcoinAddressRecord addr) => ElectrumSubAddress(
id: addr.index, id: addr.index,
name: addr.name, name: addr.name,
address: addr.address, address: addr.address,
@ -207,12 +199,12 @@ class CWBitcoin extends Bitcoin {
Future<void> updateUnspents(Object wallet) async { Future<void> updateUnspents(Object wallet) async {
final bitcoinWallet = wallet as ElectrumWallet; final bitcoinWallet = wallet as ElectrumWallet;
await bitcoinWallet.updateUnspent(); await bitcoinWallet.updateAllUnspents();
} }
WalletService createBitcoinWalletService( WalletService createBitcoinWalletService(
Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource) { Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource, bool alwaysScan) {
return BitcoinWalletService(walletInfoSource, unspentCoinSource); return BitcoinWalletService(walletInfoSource, unspentCoinSource, alwaysScan);
} }
WalletService createLitecoinWalletService( WalletService createLitecoinWalletService(
@ -247,6 +239,12 @@ class CWBitcoin extends Bitcoin {
return BitcoinReceivePageOption.fromType(bitcoinWallet.walletAddresses.addressPageType); return BitcoinReceivePageOption.fromType(bitcoinWallet.walletAddresses.addressPageType);
} }
@override
bool hasSelectedSilentPayments(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet;
return bitcoinWallet.walletAddresses.addressPageType == SilentPaymentsAddresType.p2sp;
}
@override @override
List<ReceivePageOption> getBitcoinReceivePageOptions() => BitcoinReceivePageOption.all; List<ReceivePageOption> getBitcoinReceivePageOptions() => BitcoinReceivePageOption.all;
@ -467,4 +465,137 @@ class CWBitcoin extends Bitcoin {
throw err; throw err;
} }
} }
@override
List<ElectrumSubAddress> getSilentPaymentAddresses(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet;
return bitcoinWallet.walletAddresses.silentAddresses
.where((addr) => addr.type != SegwitAddresType.p2tr)
.map((addr) => ElectrumSubAddress(
id: addr.index,
name: addr.name,
address: addr.address,
txCount: addr.txCount,
balance: addr.balance,
isChange: addr.isHidden))
.toList();
}
@override
List<ElectrumSubAddress> getSilentPaymentReceivedAddresses(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet;
return bitcoinWallet.walletAddresses.silentAddresses
.where((addr) => addr.type == SegwitAddresType.p2tr)
.map((addr) => ElectrumSubAddress(
id: addr.index,
name: addr.name,
address: addr.address,
txCount: addr.txCount,
balance: addr.balance,
isChange: addr.isHidden))
.toList();
}
@override
bool isBitcoinReceivePageOption(ReceivePageOption option) {
return option is BitcoinReceivePageOption;
}
@override
BitcoinAddressType getOptionToType(ReceivePageOption option) {
return (option as BitcoinReceivePageOption).toType();
}
@override
@computed
bool getScanningActive(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet;
return bitcoinWallet.silentPaymentsScanningActive;
}
@override
Future<void> setScanningActive(Object wallet, bool active) async {
final bitcoinWallet = wallet as ElectrumWallet;
if (active && !(await getNodeIsElectrsSPEnabled(wallet))) {
final node = Node(
useSSL: false,
uri: 'electrs.cakewallet.com:${(wallet.network == BitcoinNetwork.testnet ? 50002 : 50001)}',
);
node.type = WalletType.bitcoin;
await bitcoinWallet.connectToNode(node: node);
}
bitcoinWallet.setSilentPaymentsScanning(active);
}
@override
bool isTestnet(Object wallet) {
final bitcoinWallet = wallet as ElectrumWallet;
return bitcoinWallet.isTestnet ?? false;
}
@override
int getHeightByDate({required DateTime date}) => getBitcoinHeightByDate(date: date);
@override
Future<void> rescan(Object wallet, {required int height, bool? doSingleScan}) async {
final bitcoinWallet = wallet as ElectrumWallet;
if (!(await getNodeIsElectrsSPEnabled(wallet))) {
final node = Node(
useSSL: false,
uri: 'electrs.cakewallet.com:${(wallet.network == BitcoinNetwork.testnet ? 50002 : 50001)}',
);
node.type = WalletType.bitcoin;
await bitcoinWallet.connectToNode(node: node);
}
bitcoinWallet.rescan(height: height, doSingleScan: doSingleScan);
}
Future<bool> getNodeIsElectrs(Object wallet) async {
final bitcoinWallet = wallet as ElectrumWallet;
final version = await bitcoinWallet.electrumClient.version();
if (version.isEmpty) {
return false;
}
final server = version[0];
if (server.toLowerCase().contains('electrs')) {
return true;
}
return false;
}
@override
Future<bool> getNodeIsElectrsSPEnabled(Object wallet) async {
if (!(await getNodeIsElectrs(wallet))) {
return false;
}
final bitcoinWallet = wallet as ElectrumWallet;
final tweaksResponse = await bitcoinWallet.electrumClient.getTweaks(height: 0);
if (tweaksResponse != null) {
return true;
}
return false;
}
@override
void deleteSilentPaymentAddress(Object wallet, String address) {
final bitcoinWallet = wallet as ElectrumWallet;
bitcoinWallet.walletAddresses.deleteSilentPaymentAddress(address);
}
@override
Future<void> updateFeeRates(Object wallet) async {
final bitcoinWallet = wallet as ElectrumWallet;
await bitcoinWallet.updateFeeRates();
}
} }

View file

@ -27,7 +27,7 @@ class AddressValidator extends TextValidator {
return '^[0-9a-zA-Z]{59}\$|^[0-9a-zA-Z]{92}\$|^[0-9a-zA-Z]{104}\$' return '^[0-9a-zA-Z]{59}\$|^[0-9a-zA-Z]{92}\$|^[0-9a-zA-Z]{104}\$'
'|^[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:
return '^${P2pkhAddress.regex.pattern}\$|^${P2shAddress.regex.pattern}\$|^${P2wpkhAddress.regex.pattern}\$|${P2trAddress.regex.pattern}\$|^${P2wshAddress.regex.pattern}\$'; return '^${P2pkhAddress.regex.pattern}\$|^${P2shAddress.regex.pattern}\$|^${P2wpkhAddress.regex.pattern}\$|${P2trAddress.regex.pattern}\$|^${P2wshAddress.regex.pattern}\$|^${SilentPaymentAddress.regex.pattern}\$';
case CryptoCurrency.ltc: case CryptoCurrency.ltc:
return '^${P2pkhAddress.regex.pattern}\$|^${P2shAddress.regex.pattern}\$|^${P2wpkhAddress.regex.pattern}\$|${P2trAddress.regex.pattern}\$|^${P2wshAddress.regex.pattern}\$|^${MwebAddress.regex.pattern}\$'; return '^${P2pkhAddress.regex.pattern}\$|^${P2shAddress.regex.pattern}\$|^${P2wpkhAddress.regex.pattern}\$|${P2trAddress.regex.pattern}\$|^${P2wshAddress.regex.pattern}\$|^${MwebAddress.regex.pattern}\$';
case CryptoCurrency.nano: case CryptoCurrency.nano:
@ -275,7 +275,9 @@ class AddressValidator extends TextValidator {
'|([^0-9a-zA-Z]|^)([23][a-km-zA-HJ-NP-Z1-9]{25,34})([^0-9a-zA-Z]|\$)' //P2shAddress type '|([^0-9a-zA-Z]|^)([23][a-km-zA-HJ-NP-Z1-9]{25,34})([^0-9a-zA-Z]|\$)' //P2shAddress type
'|([^0-9a-zA-Z]|^)((bc|tb)1q[ac-hj-np-z02-9]{25,39})([^0-9a-zA-Z]|\$)' //P2wpkhAddress type '|([^0-9a-zA-Z]|^)((bc|tb)1q[ac-hj-np-z02-9]{25,39})([^0-9a-zA-Z]|\$)' //P2wpkhAddress type
'|([^0-9a-zA-Z]|^)((bc|tb)1q[ac-hj-np-z02-9]{40,80})([^0-9a-zA-Z]|\$)' //P2wshAddress type '|([^0-9a-zA-Z]|^)((bc|tb)1q[ac-hj-np-z02-9]{40,80})([^0-9a-zA-Z]|\$)' //P2wshAddress type
'|([^0-9a-zA-Z]|^)((bc|tb)1p([ac-hj-np-z02-9]{39}|[ac-hj-np-z02-9]{59}|[ac-hj-np-z02-9]{8,89}))([^0-9a-zA-Z]|\$)'; //P2trAddress type '|([^0-9a-zA-Z]|^)((bc|tb)1p([ac-hj-np-z02-9]{39}|[ac-hj-np-z02-9]{59}|[ac-hj-np-z02-9]{8,89}))([^0-9a-zA-Z]|\$)' //P2trAddress type
'|${SilentPaymentAddress.regex.pattern}\$';
case CryptoCurrency.ltc: case CryptoCurrency.ltc:
return '([^0-9a-zA-Z]|^)^L[a-zA-Z0-9]{26,33}([^0-9a-zA-Z]|\$)' return '([^0-9a-zA-Z]|^)^L[a-zA-Z0-9]{26,33}([^0-9a-zA-Z]|\$)'
'|([^0-9a-zA-Z]|^)[LM][a-km-zA-HJ-NP-Z1-9]{26,33}([^0-9a-zA-Z]|\$)' '|([^0-9a-zA-Z]|^)[LM][a-km-zA-HJ-NP-Z1-9]{26,33}([^0-9a-zA-Z]|\$)'

View file

@ -3,7 +3,13 @@ import 'package:cw_core/sync_status.dart';
String syncStatusTitle(SyncStatus syncStatus) { String syncStatusTitle(SyncStatus syncStatus) {
if (syncStatus is SyncingSyncStatus) { if (syncStatus is SyncingSyncStatus) {
return S.current.Blocks_remaining('${syncStatus.blocksLeft}'); return syncStatus.blocksLeft == 1
? S.current.block_remaining
: S.current.Blocks_remaining('${syncStatus.blocksLeft}');
}
if (syncStatus is SyncedTipSyncStatus) {
return S.current.silent_payments_scanned_tip(syncStatus.tip.toString());
} }
if (syncStatus is SyncedSyncStatus) { if (syncStatus is SyncedSyncStatus) {
@ -34,5 +40,17 @@ String syncStatusTitle(SyncStatus syncStatus) {
return S.current.sync_status_failed_connect; return S.current.sync_status_failed_connect;
} }
if (syncStatus is UnsupportedSyncStatus) {
return S.current.sync_status_unsupported;
}
if (syncStatus is TimedOutSyncStatus) {
return S.current.sync_status_timed_out;
}
if (syncStatus is SyncronizingSyncStatus) {
return S.current.sync_status_syncronizing;
}
return ''; return '';
} }

View file

@ -6,6 +6,7 @@ import 'package:cake_wallet/anypay/any_pay_payment_committed_info.dart';
import 'package:cake_wallet/anypay/anypay_api.dart'; import 'package:cake_wallet/anypay/anypay_api.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/buy/dfx/dfx_buy_provider.dart';
import 'package:cake_wallet/buy/moonpay/moonpay_provider.dart'; import 'package:cake_wallet/buy/moonpay/moonpay_provider.dart';
import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart'; import 'package:cake_wallet/buy/onramper/onramper_buy_provider.dart';
import 'package:cake_wallet/buy/order.dart'; import 'package:cake_wallet/buy/order.dart';
@ -15,6 +16,7 @@ import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/core/backup_service.dart'; import 'package:cake_wallet/core/backup_service.dart';
import 'package:cake_wallet/core/key_service.dart'; import 'package:cake_wallet/core/key_service.dart';
import 'package:cake_wallet/core/secure_storage.dart'; import 'package:cake_wallet/core/secure_storage.dart';
import 'package:cake_wallet/core/totp_request_details.dart';
import 'package:cake_wallet/core/wallet_connect/wallet_connect_key_service.dart'; import 'package:cake_wallet/core/wallet_connect/wallet_connect_key_service.dart';
import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart'; import 'package:cake_wallet/core/wallet_connect/wc_bottom_sheet_service.dart';
import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart'; import 'package:cake_wallet/core/wallet_connect/web3wallet_service.dart';
@ -102,12 +104,14 @@ import 'package:cake_wallet/src/screens/seed/wallet_seed_page.dart';
import 'package:cake_wallet/src/screens/send/send_page.dart'; import 'package:cake_wallet/src/screens/send/send_page.dart';
import 'package:cake_wallet/src/screens/send/send_template_page.dart'; import 'package:cake_wallet/src/screens/send/send_template_page.dart';
import 'package:cake_wallet/src/screens/settings/connection_sync_page.dart'; import 'package:cake_wallet/src/screens/settings/connection_sync_page.dart';
import 'package:cake_wallet/src/screens/settings/desktop_settings/desktop_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/display_settings_page.dart'; import 'package:cake_wallet/src/screens/settings/display_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/domain_lookups_page.dart'; import 'package:cake_wallet/src/screens/settings/domain_lookups_page.dart';
import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart'; import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
import 'package:cake_wallet/src/screens/settings/other_settings_page.dart'; import 'package:cake_wallet/src/screens/settings/other_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/privacy_page.dart'; import 'package:cake_wallet/src/screens/settings/privacy_page.dart';
import 'package:cake_wallet/src/screens/settings/security_backup_page.dart'; import 'package:cake_wallet/src/screens/settings/security_backup_page.dart';
import 'package:cake_wallet/src/screens/settings/silent_payments_settings.dart';
import 'package:cake_wallet/src/screens/settings/tor_page.dart'; import 'package:cake_wallet/src/screens/settings/tor_page.dart';
import 'package:cake_wallet/src/screens/settings/trocador_providers_page.dart'; import 'package:cake_wallet/src/screens/settings/trocador_providers_page.dart';
import 'package:cake_wallet/src/screens/setup_2fa/modify_2fa_page.dart'; import 'package:cake_wallet/src/screens/setup_2fa/modify_2fa_page.dart';
@ -159,10 +163,10 @@ import 'package:cake_wallet/view_model/buy/buy_view_model.dart';
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart'; import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
import 'package:cake_wallet/view_model/contact_list/contact_view_model.dart'; import 'package:cake_wallet/view_model/contact_list/contact_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.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/desktop_sidebar_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/desktop_sidebar_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/home_settings_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/home_settings_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/market_place_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:cake_wallet/view_model/dashboard/receive_option_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
import 'package:cake_wallet/view_model/edit_backup_password_view_model.dart'; import 'package:cake_wallet/view_model/edit_backup_password_view_model.dart';
@ -199,6 +203,7 @@ import 'package:cake_wallet/view_model/settings/display_settings_view_model.dart
import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart'; import 'package:cake_wallet/view_model/settings/other_settings_view_model.dart';
import 'package:cake_wallet/view_model/settings/privacy_settings_view_model.dart'; import 'package:cake_wallet/view_model/settings/privacy_settings_view_model.dart';
import 'package:cake_wallet/view_model/settings/security_settings_view_model.dart'; import 'package:cake_wallet/view_model/settings/security_settings_view_model.dart';
import 'package:cake_wallet/view_model/settings/silent_payments_settings_view_model.dart';
import 'package:cake_wallet/view_model/settings/trocador_providers_view_model.dart'; import 'package:cake_wallet/view_model/settings/trocador_providers_view_model.dart';
import 'package:cake_wallet/view_model/setup_pin_code_view_model.dart'; import 'package:cake_wallet/view_model/setup_pin_code_view_model.dart';
import 'package:cake_wallet/view_model/support_view_model.dart'; import 'package:cake_wallet/view_model/support_view_model.dart';
@ -235,10 +240,6 @@ import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'buy/dfx/dfx_buy_provider.dart';
import 'core/totp_request_details.dart';
import 'src/screens/settings/desktop_settings/desktop_settings_page.dart';
final getIt = GetIt.instance; final getIt = GetIt.instance;
var _isSetupFinished = false; var _isSetupFinished = false;
@ -745,6 +746,9 @@ Future<void> setup({
return DisplaySettingsViewModel(getIt.get<SettingsStore>()); return DisplaySettingsViewModel(getIt.get<SettingsStore>());
}); });
getIt.registerFactory(() =>
SilentPaymentsSettingsViewModel(getIt.get<SettingsStore>(), getIt.get<AppStore>().wallet!));
getIt.registerFactory(() { getIt.registerFactory(() {
return PrivacySettingsViewModel(getIt.get<SettingsStore>(), getIt.get<AppStore>().wallet!); return PrivacySettingsViewModel(getIt.get<SettingsStore>(), getIt.get<AppStore>().wallet!);
}); });
@ -806,6 +810,9 @@ Future<void> setup({
getIt.registerFactory(() => DisplaySettingsPage(getIt.get<DisplaySettingsViewModel>())); getIt.registerFactory(() => DisplaySettingsPage(getIt.get<DisplaySettingsViewModel>()));
getIt.registerFactory(
() => SilentPaymentsSettingsPage(getIt.get<SilentPaymentsSettingsViewModel>()));
getIt.registerFactory(() => OtherSettingsPage(getIt.get<OtherSettingsViewModel>())); getIt.registerFactory(() => OtherSettingsPage(getIt.get<OtherSettingsViewModel>()));
getIt.registerFactory(() => NanoChangeRepPage( getIt.registerFactory(() => NanoChangeRepPage(
@ -893,7 +900,11 @@ Future<void> setup({
case WalletType.monero: case WalletType.monero:
return monero!.createMoneroWalletService(_walletInfoSource, _unspentCoinsInfoSource); return monero!.createMoneroWalletService(_walletInfoSource, _unspentCoinsInfoSource);
case WalletType.bitcoin: case WalletType.bitcoin:
return bitcoin!.createBitcoinWalletService(_walletInfoSource, _unspentCoinsInfoSource); return bitcoin!.createBitcoinWalletService(
_walletInfoSource,
_unspentCoinsInfoSource,
getIt.get<SettingsStore>().silentPaymentsAlwaysScan,
);
case WalletType.litecoin: case WalletType.litecoin:
return bitcoin!.createLitecoinWalletService(_walletInfoSource, _unspentCoinsInfoSource); return bitcoin!.createLitecoinWalletService(_walletInfoSource, _unspentCoinsInfoSource);
case WalletType.ethereum: case WalletType.ethereum:
@ -1089,7 +1100,7 @@ Future<void> setup({
getIt.registerFactory(() => IoniaGiftCardsListViewModel(ioniaService: getIt.get<IoniaService>())); getIt.registerFactory(() => IoniaGiftCardsListViewModel(ioniaService: getIt.get<IoniaService>()));
getIt.registerFactory(() => MarketPlaceViewModel(getIt.get<IoniaService>())); getIt.registerFactory(() => CakeFeaturesViewModel(getIt.get<IoniaService>()));
getIt.registerFactory(() => IoniaAuthViewModel(ioniaService: getIt.get<IoniaService>())); getIt.registerFactory(() => IoniaAuthViewModel(ioniaService: getIt.get<IoniaService>()));

View file

@ -24,8 +24,9 @@ import 'package:collection/collection.dart';
const newCakeWalletMoneroUri = 'xmr-node.cakewallet.com:18081'; const newCakeWalletMoneroUri = 'xmr-node.cakewallet.com:18081';
const cakeWalletBitcoinElectrumUri = 'electrum.cakewallet.com:50002'; const cakeWalletBitcoinElectrumUri = 'electrum.cakewallet.com:50002';
const publicBitcoinTestnetElectrumAddress = 'electrum.blockstream.info'; const cakeWalletSilentPaymentsElectrsUri = 'electrs.cakewallet.com:50001';
const publicBitcoinTestnetElectrumPort = '60002'; const publicBitcoinTestnetElectrumAddress = 'electrs.cakewallet.com';
const publicBitcoinTestnetElectrumPort = '50002';
const publicBitcoinTestnetElectrumUri = const publicBitcoinTestnetElectrumUri =
'$publicBitcoinTestnetElectrumAddress:$publicBitcoinTestnetElectrumPort'; '$publicBitcoinTestnetElectrumAddress:$publicBitcoinTestnetElectrumPort';
const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002'; const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002';
@ -224,6 +225,9 @@ Future<void> defaultSettingsMigration(
await addTronNodeList(nodes: nodes); await addTronNodeList(nodes: nodes);
await changeTronCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes); await changeTronCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
break; break;
case 34:
await _addElectRsNode(nodes, sharedPreferences);
break;
default: default:
break; break;
} }
@ -790,7 +794,8 @@ Future<void> changeDefaultBitcoinNode(
final needToReplaceCurrentBitcoinNode = final needToReplaceCurrentBitcoinNode =
currentBitcoinNode.uri.toString().contains(cakeWalletBitcoinNodeUriPattern); currentBitcoinNode.uri.toString().contains(cakeWalletBitcoinNodeUriPattern);
final newCakeWalletBitcoinNode = Node(uri: newCakeWalletBitcoinUri, type: WalletType.bitcoin); final newCakeWalletBitcoinNode =
Node(uri: newCakeWalletBitcoinUri, type: WalletType.bitcoin, useSSL: false);
await nodeSource.add(newCakeWalletBitcoinNode); await nodeSource.add(newCakeWalletBitcoinNode);
@ -800,6 +805,26 @@ Future<void> changeDefaultBitcoinNode(
} }
} }
Future<void> _addElectRsNode(Box<Node> nodeSource, SharedPreferences sharedPreferences) async {
const cakeWalletBitcoinNodeUriPattern = '.cakewallet.com';
final currentBitcoinNodeId =
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
final currentBitcoinNode =
nodeSource.values.firstWhere((node) => node.key == currentBitcoinNodeId);
final needToReplaceCurrentBitcoinNode =
currentBitcoinNode.uri.toString().contains(cakeWalletBitcoinNodeUriPattern);
final newElectRsBitcoinNode =
Node(uri: cakeWalletSilentPaymentsElectrsUri, type: WalletType.bitcoin, useSSL: false);
await nodeSource.add(newElectRsBitcoinNode);
if (needToReplaceCurrentBitcoinNode) {
await sharedPreferences.setInt(
PreferencesKey.currentBitcoinElectrumSererIdKey, newElectRsBitcoinNode.key as int);
}
}
Future<void> checkCurrentNodes( Future<void> checkCurrentNodes(
Box<Node> nodeSource, Box<Node> powNodeSource, SharedPreferences sharedPreferences) async { Box<Node> nodeSource, Box<Node> powNodeSource, SharedPreferences sharedPreferences) async {
final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey); final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
@ -845,14 +870,19 @@ Future<void> checkCurrentNodes(
} }
if (currentBitcoinElectrumServer == null) { if (currentBitcoinElectrumServer == null) {
final cakeWalletElectrum = Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin); final cakeWalletElectrum =
Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin, useSSL: false);
await nodeSource.add(cakeWalletElectrum); await nodeSource.add(cakeWalletElectrum);
final cakeWalletElectrumTestnet =
Node(uri: publicBitcoinTestnetElectrumUri, type: WalletType.bitcoin, useSSL: false);
await nodeSource.add(cakeWalletElectrumTestnet);
await sharedPreferences.setInt( await sharedPreferences.setInt(
PreferencesKey.currentBitcoinElectrumSererIdKey, cakeWalletElectrum.key as int); PreferencesKey.currentBitcoinElectrumSererIdKey, cakeWalletElectrum.key as int);
} }
if (currentLitecoinElectrumServer == null) { if (currentLitecoinElectrumServer == null) {
final cakeWalletElectrum = Node(uri: cakeWalletLitecoinElectrumUri, type: WalletType.litecoin); final cakeWalletElectrum =
Node(uri: cakeWalletLitecoinElectrumUri, type: WalletType.litecoin, useSSL: false);
await nodeSource.add(cakeWalletElectrum); await nodeSource.add(cakeWalletElectrum);
await sharedPreferences.setInt( await sharedPreferences.setInt(
PreferencesKey.currentLitecoinElectrumSererIdKey, cakeWalletElectrum.key as int); PreferencesKey.currentLitecoinElectrumSererIdKey, cakeWalletElectrum.key as int);
@ -887,7 +917,8 @@ Future<void> checkCurrentNodes(
} }
if (currentBitcoinCashNodeServer == null) { if (currentBitcoinCashNodeServer == null) {
final node = Node(uri: cakeWalletBitcoinCashDefaultNodeUri, type: WalletType.bitcoinCash); final node =
Node(uri: cakeWalletBitcoinCashDefaultNodeUri, type: WalletType.bitcoinCash, useSSL: false);
await nodeSource.add(node); await nodeSource.add(node);
await sharedPreferences.setInt(PreferencesKey.currentBitcoinCashNodeIdKey, node.key as int); await sharedPreferences.setInt(PreferencesKey.currentBitcoinCashNodeIdKey, node.key as int);
} }
@ -921,7 +952,11 @@ Future<void> resetBitcoinElectrumServer(
.firstWhereOrNull((node) => node.uriRaw.toString() == cakeWalletBitcoinElectrumUri); .firstWhereOrNull((node) => node.uriRaw.toString() == cakeWalletBitcoinElectrumUri);
if (cakeWalletNode == null) { if (cakeWalletNode == null) {
cakeWalletNode = Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin); cakeWalletNode =
Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin, useSSL: false);
// final cakeWalletElectrumTestnet =
// Node(uri: publicBitcoinTestnetElectrumUri, type: WalletType.bitcoin, useSSL: false);
// await nodeSource.add(cakeWalletElectrumTestnet);
await nodeSource.add(cakeWalletNode); await nodeSource.add(cakeWalletNode);
} }

View file

@ -44,6 +44,8 @@ class PreferencesKey {
static const polygonTransactionPriority = 'current_fee_priority_polygon'; static const polygonTransactionPriority = 'current_fee_priority_polygon';
static const bitcoinCashTransactionPriority = 'current_fee_priority_bitcoin_cash'; static const bitcoinCashTransactionPriority = 'current_fee_priority_bitcoin_cash';
static const customBitcoinFeeRate = 'custom_electrum_fee_rate'; static const customBitcoinFeeRate = 'custom_electrum_fee_rate';
static const silentPaymentsCardDisplay = 'silentPaymentsCardDisplay';
static const silentPaymentsAlwaysScan = 'silentPaymentsAlwaysScan';
static const shouldShowReceiveWarning = 'should_show_receive_warning'; static const shouldShowReceiveWarning = 'should_show_receive_warning';
static const shouldShowYatPopup = 'should_show_yat_popup'; static const shouldShowYatPopup = 'should_show_yat_popup';
static const shouldShowRepWarning = 'should_show_rep_warning'; static const shouldShowRepWarning = 'should_show_rep_warning';

View file

@ -202,7 +202,7 @@ Future<void> initializeAppConfigs() async {
transactionDescriptions: transactionDescriptions, transactionDescriptions: transactionDescriptions,
secureStorage: secureStorage, secureStorage: secureStorage,
anonpayInvoiceInfo: anonpayInvoiceInfo, anonpayInvoiceInfo: anonpayInvoiceInfo,
initialMigrationVersion: 33, initialMigrationVersion: 34,
); );
} }

View file

@ -3,15 +3,19 @@ import 'dart:async';
import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/sync_status.dart'; import 'package:cw_core/sync_status.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/store/settings_store.dart';
Timer? _checkConnectionTimer; Timer? _checkConnectionTimer;
void startCheckConnectionReaction( void startCheckConnectionReaction(WalletBase wallet, SettingsStore settingsStore,
WalletBase wallet, SettingsStore settingsStore,
{int timeInterval = 5}) { {int timeInterval = 5}) {
_checkConnectionTimer?.cancel(); _checkConnectionTimer?.cancel();
_checkConnectionTimer = _checkConnectionTimer = Timer.periodic(Duration(seconds: timeInterval), (_) async {
Timer.periodic(Duration(seconds: timeInterval), (_) async { if (wallet.type == WalletType.bitcoin && wallet.syncStatus is SyncingSyncStatus) {
return;
}
try { try {
final connectivityResult = await (Connectivity().checkConnectivity()); final connectivityResult = await (Connectivity().checkConnectivity());
@ -20,14 +24,11 @@ void startCheckConnectionReaction(
return; return;
} }
if (wallet.syncStatus is LostConnectionSyncStatus || if (wallet.syncStatus is LostConnectionSyncStatus || wallet.syncStatus is FailedSyncStatus) {
wallet.syncStatus is FailedSyncStatus) { final alive = await settingsStore.getCurrentNode(wallet.type).requestNode();
final alive =
await settingsStore.getCurrentNode(wallet.type).requestNode();
if (alive) { if (alive) {
await wallet.connectToNode( await wallet.connectToNode(node: settingsStore.getCurrentNode(wallet.type));
node: settingsStore.getCurrentNode(wallet.type));
} }
} }
} catch (e) { } catch (e) {

View file

@ -12,12 +12,10 @@ import 'package:wakelock_plus/wakelock_plus.dart';
ReactionDisposer? _onWalletSyncStatusChangeReaction; ReactionDisposer? _onWalletSyncStatusChangeReaction;
void startWalletSyncStatusChangeReaction( void startWalletSyncStatusChangeReaction(
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet,
TransactionInfo> wallet,
FiatConversionStore fiatConversionStore) { FiatConversionStore fiatConversionStore) {
_onWalletSyncStatusChangeReaction?.reaction.dispose(); _onWalletSyncStatusChangeReaction?.reaction.dispose();
_onWalletSyncStatusChangeReaction = _onWalletSyncStatusChangeReaction = reaction((_) => wallet.syncStatus, (SyncStatus status) async {
reaction((_) => wallet.syncStatus, (SyncStatus status) async {
try { try {
if (status is ConnectedSyncStatus) { if (status is ConnectedSyncStatus) {
await wallet.startSync(); await wallet.startSync();
@ -32,7 +30,7 @@ void startWalletSyncStatusChangeReaction(
if (status is SyncedSyncStatus || status is FailedSyncStatus) { if (status is SyncedSyncStatus || status is FailedSyncStatus) {
await WakelockPlus.disable(); await WakelockPlus.disable();
} }
} catch(e) { } catch (e) {
print(e.toString()); print(e.toString());
} }
}); });

View file

@ -76,6 +76,7 @@ import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
import 'package:cake_wallet/src/screens/settings/other_settings_page.dart'; import 'package:cake_wallet/src/screens/settings/other_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/privacy_page.dart'; import 'package:cake_wallet/src/screens/settings/privacy_page.dart';
import 'package:cake_wallet/src/screens/settings/security_backup_page.dart'; import 'package:cake_wallet/src/screens/settings/security_backup_page.dart';
import 'package:cake_wallet/src/screens/settings/silent_payments_settings.dart';
import 'package:cake_wallet/src/screens/settings/tor_page.dart'; import 'package:cake_wallet/src/screens/settings/tor_page.dart';
import 'package:cake_wallet/src/screens/settings/trocador_providers_page.dart'; import 'package:cake_wallet/src/screens/settings/trocador_providers_page.dart';
import 'package:cake_wallet/src/screens/setup_2fa/modify_2fa_page.dart'; import 'package:cake_wallet/src/screens/setup_2fa/modify_2fa_page.dart';
@ -366,6 +367,10 @@ Route<dynamic> createRoute(RouteSettings settings) {
param1: settings.arguments as OnAuthenticationFinished, param2: false), param1: settings.arguments as OnAuthenticationFinished, param2: false),
onWillPop: () async => false)); onWillPop: () async => false));
case Routes.silentPaymentsSettings:
return CupertinoPageRoute<void>(
fullscreenDialog: true, builder: (_) => getIt.get<SilentPaymentsSettingsPage>());
case Routes.connectionSync: case Routes.connectionSync:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
fullscreenDialog: true, builder: (_) => getIt.get<ConnectionSyncPage>()); fullscreenDialog: true, builder: (_) => getIt.get<ConnectionSyncPage>());

View file

@ -81,6 +81,7 @@ class Routes {
static const ioniaMoreOptionsPage = '/ionia_more_options_page'; static const ioniaMoreOptionsPage = '/ionia_more_options_page';
static const ioniaCustomRedeemPage = '/ionia_custom_redeem_page'; static const ioniaCustomRedeemPage = '/ionia_custom_redeem_page';
static const webViewPage = '/web_view_page'; static const webViewPage = '/web_view_page';
static const silentPaymentsSettings = '/silent_payments_settings';
static const connectionSync = '/connection_sync_page'; static const connectionSync = '/connection_sync_page';
static const securityBackupPage = '/security_and_backup_page'; static const securityBackupPage = '/security_and_backup_page';
static const privacyPage = '/privacy_page'; static const privacyPage = '/privacy_page';

View file

@ -4,7 +4,7 @@ import 'package:cake_wallet/entities/preferences_key.dart';
import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/main_actions.dart'; import 'package:cake_wallet/entities/main_actions.dart';
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sidebar_wrapper.dart'; import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_sidebar_wrapper.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/market_place_page.dart'; import 'package:cake_wallet/src/screens/dashboard/pages/cake_features_page.dart';
import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/bottom_sheet_listener.dart'; import 'package:cake_wallet/src/screens/wallet_connect/widgets/modals/bottom_sheet_listener.dart';
import 'package:cake_wallet/src/widgets/gradient_background.dart'; import 'package:cake_wallet/src/widgets/gradient_background.dart';
import 'package:cake_wallet/src/widgets/services_updates_widget.dart'; import 'package:cake_wallet/src/widgets/services_updates_widget.dart';
@ -12,7 +12,7 @@ import 'package:cake_wallet/src/widgets/vulnerable_seeds_popup.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/device_info.dart'; import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/version_comparator.dart'; import 'package:cake_wallet/utils/version_comparator.dart';
import 'package:cake_wallet/view_model/dashboard/market_place_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/yat_emoji_id.dart'; import 'package:cake_wallet/src/screens/yat_emoji_id.dart';
@ -330,10 +330,10 @@ class _DashboardPageView extends BasePage {
if (dashboardViewModel.shouldShowMarketPlaceInDashboard) { if (dashboardViewModel.shouldShowMarketPlaceInDashboard) {
pages.add( pages.add(
Semantics( Semantics(
label: S.of(context).market_place, label: 'Cake ${S.of(context).features}',
child: MarketPlacePage( child: CakeFeaturesPage(
dashboardViewModel: dashboardViewModel, dashboardViewModel: dashboardViewModel,
marketPlaceViewModel: getIt.get<MarketPlaceViewModel>(), cakeFeaturesViewModel: getIt.get<CakeFeaturesViewModel>(),
), ),
), ),
); );

View file

@ -1,9 +1,9 @@
import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/main_actions.dart'; import 'package:cake_wallet/entities/main_actions.dart';
import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_action_button.dart'; import 'package:cake_wallet/src/screens/dashboard/desktop_widgets/desktop_action_button.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/market_place_page.dart'; import 'package:cake_wallet/src/screens/dashboard/pages/cake_features_page.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/market_place_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.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';
@ -74,9 +74,9 @@ class DesktopDashboardActions extends StatelessWidget {
], ],
), ),
Expanded( Expanded(
child: MarketPlacePage( child: CakeFeaturesPage(
dashboardViewModel: dashboardViewModel, dashboardViewModel: dashboardViewModel,
marketPlaceViewModel: getIt.get<MarketPlaceViewModel>(), cakeFeaturesViewModel: getIt.get<CakeFeaturesViewModel>(),
), ),
), ),
], ],

View file

@ -30,6 +30,7 @@ class DesktopWalletSelectionDropDown extends StatefulWidget {
class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionDropDown> { class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionDropDown> {
final moneroIcon = Image.asset('assets/images/monero_logo.png', height: 24, width: 24); final moneroIcon = Image.asset('assets/images/monero_logo.png', height: 24, width: 24);
final bitcoinIcon = Image.asset('assets/images/bitcoin.png', height: 24, width: 24); final bitcoinIcon = Image.asset('assets/images/bitcoin.png', height: 24, width: 24);
final tBitcoinIcon = Image.asset('assets/images/tbtc.png', height: 24, width: 24);
final litecoinIcon = Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24); final litecoinIcon = Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24);
final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24); final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24);
final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24); final ethereumIcon = Image.asset('assets/images/eth_icon.png', height: 24, width: 24);
@ -68,8 +69,11 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
child: ConstrainedBox( child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: 500), constraints: BoxConstraints(maxWidth: 500),
child: DropDownItemWidget( child: DropDownItemWidget(
title: wallet.name, title: wallet.name,
image: wallet.isEnabled ? _imageFor(type: wallet.type) : nonWalletTypeIcon), image: wallet.isEnabled
? _imageFor(type: wallet.type, isTestnet: wallet.isTestnet)
: nonWalletTypeIcon,
),
), ),
onSelected: () => _onSelectedWallet(wallet), onSelected: () => _onSelectedWallet(wallet),
)) ))
@ -120,16 +124,16 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
WidgetsBinding.instance.addPostFrameCallback((_) async { WidgetsBinding.instance.addPostFrameCallback((_) async {
final confirmed = await showPopUp<bool>( final confirmed = await showPopUp<bool>(
context: context, context: context,
builder: (dialogContext) { builder: (dialogContext) {
return AlertWithTwoActions( return AlertWithTwoActions(
alertTitle: S.of(context).change_wallet_alert_title, alertTitle: S.of(context).change_wallet_alert_title,
alertContent: S.of(context).change_wallet_alert_content(selectedWallet.name), alertContent: S.of(context).change_wallet_alert_content(selectedWallet.name),
leftButtonText: S.of(context).cancel, leftButtonText: S.of(context).cancel,
rightButtonText: S.of(context).change, rightButtonText: S.of(context).change,
actionLeftButton: () => Navigator.of(dialogContext).pop(false), actionLeftButton: () => Navigator.of(dialogContext).pop(false),
actionRightButton: () => Navigator.of(dialogContext).pop(true)); actionRightButton: () => Navigator.of(dialogContext).pop(true));
}) ?? }) ??
false; false;
if (confirmed) { if (confirmed) {
@ -138,9 +142,12 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
}); });
} }
Image _imageFor({required WalletType type}) { Image _imageFor({required WalletType type, bool? isTestnet}) {
switch (type) { switch (type) {
case WalletType.bitcoin: case WalletType.bitcoin:
if (isTestnet == true) {
return tBitcoinIcon;
}
return bitcoinIcon; return bitcoinIcon;
case WalletType.monero: case WalletType.monero:
return moneroIcon; return moneroIcon;
@ -160,7 +167,7 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
return polygonIcon; return polygonIcon;
case WalletType.solana: case WalletType.solana:
return solanaIcon; return solanaIcon;
case WalletType.tron: case WalletType.tron:
return tronIcon; return tronIcon;
default: default:
return nonWalletTypeIcon; return nonWalletTypeIcon;
@ -168,24 +175,25 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
} }
Future<void> _loadWallet(WalletListItem wallet) async { Future<void> _loadWallet(WalletListItem wallet) async {
widget._authService.authenticateAction(context, widget._authService.authenticateAction(
onAuthSuccess: (isAuthenticatedSuccessfully) async { context,
if (!isAuthenticatedSuccessfully) { onAuthSuccess: (isAuthenticatedSuccessfully) async {
return; if (!isAuthenticatedSuccessfully) {
} return;
}
try { try {
if (context.mounted) { if (context.mounted) {
changeProcessText(S.of(context).wallet_list_loading_wallet(wallet.name)); changeProcessText(S.of(context).wallet_list_loading_wallet(wallet.name));
}
await widget.walletListViewModel.loadWallet(wallet);
hideProgressText();
setState(() {});
} catch (e) {
if (context.mounted) {
changeProcessText(S.of(context).wallet_list_failed_to_load(wallet.name, e.toString()));
}
} }
await widget.walletListViewModel.loadWallet(wallet);
hideProgressText();
setState(() {});
} catch (e) {
if (context.mounted) {
changeProcessText(S.of(context).wallet_list_failed_to_load(wallet.name, e.toString()));
}
}
}, },
conditionToDetermineIfToUse2FA: conditionToDetermineIfToUse2FA:
widget.walletListViewModel.shouldRequireTOTP2FAForAccessingWallet, widget.walletListViewModel.shouldRequireTOTP2FAForAccessingWallet,
@ -198,17 +206,16 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
context, context,
route: Routes.newWallet, route: Routes.newWallet,
arguments: widget.walletListViewModel.currentWalletType, arguments: widget.walletListViewModel.currentWalletType,
conditionToDetermineIfToUse2FA: widget conditionToDetermineIfToUse2FA:
.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets, widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
); );
} else { } else {
widget._authService.authenticateAction( widget._authService.authenticateAction(
context, context,
route: Routes.newWalletType, route: Routes.newWalletType,
conditionToDetermineIfToUse2FA: widget conditionToDetermineIfToUse2FA:
.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets, widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
); );
} }
} }

View file

@ -187,6 +187,11 @@ class AddressPage extends BasePage {
} }
reaction((_) => receiveOptionViewModel.selectedReceiveOption, (ReceivePageOption option) { reaction((_) => receiveOptionViewModel.selectedReceiveOption, (ReceivePageOption option) {
if (bitcoin!.isBitcoinReceivePageOption(option)) {
addressListViewModel.setAddressType(bitcoin!.getOptionToType(option));
return;
}
switch (option) { switch (option) {
case ReceivePageOption.anonPayInvoice: case ReceivePageOption.anonPayInvoice:
Navigator.pushNamed( Navigator.pushNamed(

View file

@ -1,15 +1,18 @@
import 'dart:math'; import 'dart:math';
import 'package:auto_size_text/auto_size_text.dart'; import 'package:auto_size_text/auto_size_text.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/reactions/wallet_connect.dart'; import 'package:cake_wallet/reactions/wallet_connect.dart';
import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/dashboard/pages/nft_listing_page.dart'; import 'package:cake_wallet/src/screens/dashboard/pages/nft_listing_page.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/home_screen_account_widget.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/home_screen_account_widget.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/cake_image_widget.dart'; import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
import 'package:cake_wallet/src/screens/exchange_trade/information_page.dart'; import 'package:cake_wallet/src/screens/exchange_trade/information_page.dart';
import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart'; import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart';
import 'package:cake_wallet/src/widgets/introducing_card.dart'; import 'package:cake_wallet/src/widgets/introducing_card.dart';
import 'package:cake_wallet/src/widgets/standard_switch.dart';
import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/themes/extensions/balance_page_theme.dart'; 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';
@ -21,6 +24,7 @@ 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/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';
class BalancePage extends StatelessWidget { class BalancePage extends StatelessWidget {
BalancePage({ BalancePage({
@ -221,30 +225,136 @@ class CryptoBalanceWidget extends StatelessWidget {
itemBuilder: (__, index) { itemBuilder: (__, index) {
final balance = final balance =
dashboardViewModel.balanceViewModel.formattedBalances.elementAt(index); dashboardViewModel.balanceViewModel.formattedBalances.elementAt(index);
return BalanceRowWidget( return Observer(builder: (_) {
availableBalanceLabel: return BalanceRowWidget(
'${dashboardViewModel.balanceViewModel.availableBalanceLabel}', availableBalanceLabel:
availableBalance: balance.availableBalance, '${dashboardViewModel.balanceViewModel.availableBalanceLabel}',
availableFiatBalance: balance.fiatAvailableBalance, availableBalance: balance.availableBalance,
additionalBalanceLabel: availableFiatBalance: balance.fiatAvailableBalance,
'${dashboardViewModel.balanceViewModel.additionalBalanceLabel}', additionalBalanceLabel:
additionalBalance: balance.additionalBalance, '${dashboardViewModel.balanceViewModel.additionalBalanceLabel}',
additionalFiatBalance: balance.fiatAdditionalBalance, additionalBalance: balance.additionalBalance,
frozenBalance: balance.frozenBalance, additionalFiatBalance: balance.fiatAdditionalBalance,
frozenFiatBalance: balance.fiatFrozenBalance, frozenBalance: balance.frozenBalance,
currency: balance.asset, frozenFiatBalance: balance.fiatFrozenBalance,
hasAdditionalBalance: currency: balance.asset,
dashboardViewModel.balanceViewModel.hasAdditionalBalance, hasAdditionalBalance:
); dashboardViewModel.balanceViewModel.hasAdditionalBalance,
isTestnet: dashboardViewModel.isTestnet,
);
});
}, },
); );
}, },
) ),
Observer(builder: (context) {
return Column(
children: [
if (dashboardViewModel.showSilentPaymentsCard) ...[
SizedBox(height: 10),
Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
child: DashBoardRoundedCardWidget(
customBorder: 30,
title: S.of(context).silent_payments,
subTitle: S.of(context).enable_silent_payments_scanning,
hint: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => launchUrl(
Uri.parse(
"https://guides.cakewallet.com/docs/cryptos/bitcoin/#silent-payments"),
mode: LaunchMode.externalApplication,
),
child: Row(
children: [
Text(
S.of(context).what_is_silent_payments,
style: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.labelTextColor,
height: 1,
),
softWrap: true,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Icon(Icons.help_outline,
size: 16,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.labelTextColor),
)
],
),
),
Observer(
builder: (_) => StandardSwitch(
value: dashboardViewModel.silentPaymentsScanningActive,
onTaped: () => _toggleSilentPaymentsScanning(context),
),
)
],
),
],
),
onTap: () => _toggleSilentPaymentsScanning(context),
icon: Icon(
Icons.lock,
color:
Theme.of(context).extension<DashboardPageTheme>()!.pageTitleTextColor,
size: 50,
),
),
),
]
],
);
}),
], ],
), ),
), ),
); );
} }
Future<void> _toggleSilentPaymentsScanning(BuildContext context) async {
final isSilentPaymentsScanningActive = dashboardViewModel.silentPaymentsScanningActive;
final newValue = !isSilentPaymentsScanningActive;
dashboardViewModel.silentPaymentsScanningActive = newValue;
final needsToSwitch = !isSilentPaymentsScanningActive &&
await bitcoin!.getNodeIsElectrsSPEnabled(dashboardViewModel.wallet) == false;
if (needsToSwitch) {
return showPopUp<void>(
context: context,
builder: (BuildContext context) => AlertWithTwoActions(
alertTitle: S.of(context).change_current_node_title,
alertContent: S.of(context).confirm_silent_payments_switch_node,
rightButtonText: S.of(context).ok,
leftButtonText: S.of(context).cancel,
actionRightButton: () {
dashboardViewModel.setSilentPaymentsScanning(newValue);
Navigator.of(context).pop();
},
actionLeftButton: () {
dashboardViewModel.silentPaymentsScanningActive = isSilentPaymentsScanningActive;
Navigator.of(context).pop();
},
));
}
return dashboardViewModel.setSilentPaymentsScanning(newValue);
}
} }
class BalanceRowWidget extends StatelessWidget { class BalanceRowWidget extends StatelessWidget {
@ -259,6 +369,7 @@ class BalanceRowWidget extends StatelessWidget {
required this.frozenFiatBalance, required this.frozenFiatBalance,
required this.currency, required this.currency,
required this.hasAdditionalBalance, required this.hasAdditionalBalance,
required this.isTestnet,
super.key, super.key,
}); });
@ -272,6 +383,7 @@ class BalanceRowWidget extends StatelessWidget {
final String frozenFiatBalance; final String frozenFiatBalance;
final CryptoCurrency currency; final CryptoCurrency currency;
final bool hasAdditionalBalance; final bool hasAdditionalBalance;
final bool isTestnet;
// void _showBalanceDescription(BuildContext context) { // void _showBalanceDescription(BuildContext context) {
// showPopUp<void>( // showPopUp<void>(
@ -346,14 +458,24 @@ class BalanceRowWidget extends StatelessWidget {
maxLines: 1, maxLines: 1,
textAlign: TextAlign.start), textAlign: TextAlign.start),
SizedBox(height: 6), SizedBox(height: 6),
Text('${availableFiatBalance}', if (isTestnet)
textAlign: TextAlign.center, Text(S.current.testnet_coins_no_value,
style: TextStyle( textAlign: TextAlign.center,
fontSize: 16, style: TextStyle(
fontFamily: 'Lato', fontSize: 14,
fontWeight: FontWeight.w500, fontFamily: 'Lato',
color: Theme.of(context).extension<BalancePageTheme>()!.textColor, fontWeight: FontWeight.w400,
height: 1)), color: Theme.of(context).extension<BalancePageTheme>()!.textColor,
height: 1)),
if (!isTestnet)
Text('${availableFiatBalance}',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16,
fontFamily: 'Lato',
fontWeight: FontWeight.w500,
color: Theme.of(context).extension<BalancePageTheme>()!.textColor,
height: 1)),
], ],
), ),
), ),
@ -362,27 +484,23 @@ class BalanceRowWidget extends StatelessWidget {
child: Center( child: Center(
child: Column( child: Column(
children: [ children: [
Container( CakeImageWidget(
clipBehavior: Clip.antiAlias, imageUrl: currency.iconPath,
decoration: BoxDecoration(shape: BoxShape.circle), height: 40,
child: CakeImageWidget( width: 40,
imageUrl: currency.iconPath, displayOnError: Container(
height: 40, height: 30.0,
width: 40, width: 30.0,
displayOnError: Container( child: Center(
height: 30.0, child: Text(
width: 30.0, currency.title.substring(0, min(currency.title.length, 2)),
child: Center( style: TextStyle(fontSize: 11),
child: Text(
currency.title.substring(0, min(currency.title.length, 2)),
style: TextStyle(fontSize: 11),
),
),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.grey.shade400,
), ),
), ),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.grey.shade400,
),
), ),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
@ -449,17 +567,18 @@ class BalanceRowWidget extends StatelessWidget {
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
SizedBox(height: 4), SizedBox(height: 4),
Text( if (!isTestnet)
frozenFiatBalance, Text(
textAlign: TextAlign.center, frozenFiatBalance,
style: TextStyle( textAlign: TextAlign.center,
fontSize: 12, style: TextStyle(
fontFamily: 'Lato', fontSize: 12,
fontWeight: FontWeight.w400, fontFamily: 'Lato',
color: Theme.of(context).extension<BalancePageTheme>()!.textColor, fontWeight: FontWeight.w400,
height: 1, color: Theme.of(context).extension<BalancePageTheme>()!.textColor,
height: 1,
),
), ),
),
], ],
), ),
), ),
@ -493,17 +612,18 @@ class BalanceRowWidget extends StatelessWidget {
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
SizedBox(height: 4), SizedBox(height: 4),
Text( if (!isTestnet)
'${additionalFiatBalance}', Text(
textAlign: TextAlign.center, '${additionalFiatBalance}',
style: TextStyle( textAlign: TextAlign.center,
fontSize: 12, style: TextStyle(
fontFamily: 'Lato', fontSize: 12,
fontWeight: FontWeight.w400, fontFamily: 'Lato',
color: Theme.of(context).extension<BalancePageTheme>()!.textColor, fontWeight: FontWeight.w400,
height: 1, color: Theme.of(context).extension<BalancePageTheme>()!.textColor,
height: 1,
),
), ),
),
], ],
), ),
], ],

View file

@ -1,23 +1,20 @@
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart'; import 'package:cake_wallet/src/widgets/dashboard_card_widget.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/market_place_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart'; import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
import 'package:flutter_svg/flutter_svg.dart';
class MarketPlacePage extends StatelessWidget { class CakeFeaturesPage extends StatelessWidget {
MarketPlacePage({ CakeFeaturesPage({
required this.dashboardViewModel, required this.dashboardViewModel,
required this.marketPlaceViewModel, required this.cakeFeaturesViewModel,
}); });
final DashboardViewModel dashboardViewModel; final DashboardViewModel dashboardViewModel;
final MarketPlaceViewModel marketPlaceViewModel; final CakeFeaturesViewModel cakeFeaturesViewModel;
final _scrollController = ScrollController(); final _scrollController = ScrollController();
@override @override
@ -37,7 +34,7 @@ class MarketPlacePage extends StatelessWidget {
children: [ children: [
SizedBox(height: 50), SizedBox(height: 50),
Text( Text(
S.of(context).market_place, 'Cake ${S.of(context).features}',
style: TextStyle( style: TextStyle(
fontSize: 24, fontSize: 24,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
@ -59,15 +56,21 @@ class MarketPlacePage extends StatelessWidget {
// ), // ),
SizedBox(height: 20), SizedBox(height: 20),
DashBoardRoundedCardWidget( DashBoardRoundedCardWidget(
onTap: () => _launchUrl("buy.cakepay.com"),
title: S.of(context).cake_pay_web_cards_title, title: S.of(context).cake_pay_web_cards_title,
subTitle: S.of(context).cake_pay_web_cards_subtitle, subTitle: S.of(context).cake_pay_web_cards_subtitle,
onTap: () => _launchMarketPlaceUrl("buy.cakepay.com"), svgPicture: SvgPicture.asset(
'assets/images/cards.svg',
height: 125,
width: 125,
fit: BoxFit.cover,
),
), ),
const SizedBox(height: 20), SizedBox(height: 10),
DashBoardRoundedCardWidget( DashBoardRoundedCardWidget(
title: "NanoGPT", title: "NanoGPT",
subTitle: S.of(context).nanogpt_subtitle, subTitle: S.of(context).nanogpt_subtitle,
onTap: () => _launchMarketPlaceUrl("cake.nano-gpt.com"), onTap: () => _launchUrl("cake.nano-gpt.com"),
), ),
], ],
), ),
@ -79,41 +82,12 @@ class MarketPlacePage extends StatelessWidget {
); );
} }
void _launchMarketPlaceUrl(String url) async { void _launchUrl(String url) {
try { try {
launchUrl( launchUrl(
Uri.https(url), Uri.https(url),
mode: LaunchMode.externalApplication, mode: LaunchMode.externalApplication,
); );
} catch (e) { } catch (_) {}
print(e);
}
}
// TODO: Remove ionia flow/files if we will discard it
void _navigatorToGiftCardsPage(BuildContext context) {
final walletType = dashboardViewModel.type;
switch (walletType) {
case WalletType.haven:
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).error,
alertContent: S.of(context).gift_cards_unavailable,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
break;
default:
marketPlaceViewModel.isIoniaUserAuthenticated().then((value) {
if (value) {
Navigator.pushNamed(context, Routes.ioniaManageCardsPage);
return;
}
Navigator.of(context).pushNamed(Routes.ioniaWelcomePage);
});
}
} }
} }

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/setting_action_button.dart'; import 'package:cake_wallet/src/widgets/setting_action_button.dart';
import 'package:cake_wallet/src/widgets/setting_actions.dart'; import 'package:cake_wallet/src/widgets/setting_actions.dart';
import 'package:cake_wallet/themes/extensions/menu_theme.dart'; import 'package:cake_wallet/themes/extensions/menu_theme.dart';
@ -180,6 +181,11 @@ class MenuWidgetState extends State<MenuWidget> {
final item = SettingActions.all[index]; final item = SettingActions.all[index];
if (!widget.dashboardViewModel.hasSilentPayments &&
item.name(context) == S.of(context).silent_payments_settings) {
return Container();
}
final isLastTile = index == itemCount - 1; final isLastTile = index == itemCount - 1;
return SettingActionButton( return SettingActionButton(

View file

@ -10,8 +10,7 @@ import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
class PresentReceiveOptionPicker extends StatelessWidget { class PresentReceiveOptionPicker extends StatelessWidget {
PresentReceiveOptionPicker( PresentReceiveOptionPicker({required this.receiveOptionViewModel, required this.color});
{required this.receiveOptionViewModel, required this.color});
final ReceiveOptionViewModel receiveOptionViewModel; final ReceiveOptionViewModel receiveOptionViewModel;
final Color color; final Color color;
@ -43,17 +42,17 @@ class PresentReceiveOptionPicker extends StatelessWidget {
Text( Text(
S.current.receive, S.current.receive,
style: TextStyle( style: TextStyle(
fontSize: 18.0, fontSize: 18.0, fontWeight: FontWeight.bold, fontFamily: 'Lato', color: color),
fontWeight: FontWeight.bold,
fontFamily: 'Lato',
color: color),
), ),
Observer( Observer(
builder: (_) => Text(receiveOptionViewModel.selectedReceiveOption.toString(), builder: (_) => Text(
style: TextStyle( receiveOptionViewModel.selectedReceiveOption
fontSize: 10.0, .toString()
fontWeight: FontWeight.w500, .replaceAll(RegExp(r'silent payments', caseSensitive: false),
color: color))) S.current.silent_payments)
.replaceAll(
RegExp(r'default', caseSensitive: false), S.current.string_default),
style: TextStyle(fontSize: 10.0, fontWeight: FontWeight.w500, color: color)))
], ],
), ),
SizedBox(width: 5), SizedBox(width: 5),
@ -73,65 +72,75 @@ class PresentReceiveOptionPicker extends StatelessWidget {
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
body: Stack( body: Stack(
alignment: AlignmentDirectional.center, alignment: AlignmentDirectional.center,
children:[ AlertBackground( children: [
child: Column( AlertBackground(
mainAxisSize: MainAxisSize.min, child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min,
children: [ mainAxisAlignment: MainAxisAlignment.center,
Spacer(), children: [
Container( Spacer(),
margin: EdgeInsets.symmetric(horizontal: 24), Container(
decoration: BoxDecoration( margin: EdgeInsets.symmetric(horizontal: 24),
borderRadius: BorderRadius.circular(30), decoration: BoxDecoration(
color: Theme.of(context).colorScheme.background, borderRadius: BorderRadius.circular(30),
), color: Theme.of(context).colorScheme.background,
child: Padding( ),
padding: const EdgeInsets.only(top: 24, bottom: 24), child: Padding(
child: (ListView.separated( padding: const EdgeInsets.only(top: 24, bottom: 24),
padding: EdgeInsets.zero, child: (ListView.separated(
shrinkWrap: true, padding: EdgeInsets.zero,
itemCount: receiveOptionViewModel.options.length, shrinkWrap: true,
itemBuilder: (_, index) { itemCount: receiveOptionViewModel.options.length,
final option = receiveOptionViewModel.options[index]; itemBuilder: (_, index) {
return InkWell( final option = receiveOptionViewModel.options[index];
onTap: () { return InkWell(
Navigator.pop(popUpContext); onTap: () {
Navigator.pop(popUpContext);
receiveOptionViewModel.selectReceiveOption(option); receiveOptionViewModel.selectReceiveOption(option);
}, },
child: Padding( child: Padding(
padding: const EdgeInsets.only(left: 24, right: 24), padding: const EdgeInsets.only(left: 24, right: 24),
child: Observer(builder: (_) { child: Observer(builder: (_) {
final value = receiveOptionViewModel.selectedReceiveOption; final value = receiveOptionViewModel.selectedReceiveOption;
return Row( return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text(option.toString(), Text(
textAlign: TextAlign.left, option
style: textSmall( .toString()
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor, .replaceAll(
).copyWith( RegExp(r'silent payments', caseSensitive: false),
fontWeight: S.current.silent_payments)
value == option ? FontWeight.w800 : FontWeight.w500, .replaceAll(RegExp(r'default', caseSensitive: false),
)), S.current.string_default),
RoundedCheckbox( textAlign: TextAlign.left,
value: value == option, style: textSmall(
) color: Theme.of(context)
], .extension<CakeTextTheme>()!
); .titleColor,
}), ).copyWith(
), fontWeight:
); value == option ? FontWeight.w800 : FontWeight.w500,
}, )),
separatorBuilder: (_, index) => SizedBox(height: 30), RoundedCheckbox(
)), value: value == option,
)
],
);
}),
),
);
},
separatorBuilder: (_, index) => SizedBox(height: 30),
)),
),
), ),
), Spacer()
Spacer() ],
], ),
), ),
),
AlertCloseButton(onTap: () => Navigator.of(popUpContext).pop(), bottom: 40) AlertCloseButton(onTap: () => Navigator.of(popUpContext).pop(), bottom: 40)
], ],
), ),

View file

@ -67,8 +67,7 @@ class ReceivePage extends BasePage {
@override @override
Widget Function(BuildContext, Widget) get rootWrapper => Widget Function(BuildContext, Widget) get rootWrapper =>
(BuildContext context, Widget scaffold) => (BuildContext context, Widget scaffold) => GradientBackground(scaffold: scaffold);
GradientBackground(scaffold: scaffold);
@override @override
Widget trailing(BuildContext context) { Widget trailing(BuildContext context) {
@ -99,115 +98,144 @@ class ReceivePage extends BasePage {
@override @override
Widget body(BuildContext context) { Widget body(BuildContext context) {
return KeyboardActions( return KeyboardActions(
config: KeyboardActionsConfig( config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS, keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
keyboardBarColor: Theme.of(context).extension<KeyboardTheme>()!.keyboardBarColor, keyboardBarColor: Theme.of(context).extension<KeyboardTheme>()!.keyboardBarColor,
nextFocus: false, nextFocus: false,
actions: [ actions: [
KeyboardActionsItem( KeyboardActionsItem(
focusNode: _cryptoAmountFocus, focusNode: _cryptoAmountFocus,
toolbarButtons: [(_) => KeyboardDoneButton()], toolbarButtons: [(_) => KeyboardDoneButton()],
) )
]), ]),
child: SingleChildScrollView( child: SingleChildScrollView(
child: Column( child: Column(
children: <Widget>[ children: <Widget>[
Padding( Padding(
padding: EdgeInsets.fromLTRB(24, 50, 24, 24), padding: EdgeInsets.fromLTRB(24, 50, 24, 24),
child: QRWidget( child: QRWidget(
addressListViewModel: addressListViewModel, addressListViewModel: addressListViewModel,
formKey: _formKey, formKey: _formKey,
heroTag: _heroTag, heroTag: _heroTag,
amountTextFieldFocusNode: _cryptoAmountFocus, amountTextFieldFocusNode: _cryptoAmountFocus,
amountController: _amountController, amountController: _amountController,
isLight: currentTheme.type == ThemeType.light), isLight: currentTheme.type == ThemeType.light),
),
Observer(
builder: (_) => ListView.separated(
padding: EdgeInsets.all(0),
separatorBuilder: (context, _) => const HorizontalSectionDivider(),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: addressListViewModel.items.length,
itemBuilder: (context, index) {
final item = addressListViewModel.items[index];
Widget cell = Container();
if (item is WalletAccountListHeader) {
cell = HeaderTile(
showTrailingButton: true,
walletAddressListViewModel: addressListViewModel,
trailingButtonTap: () async {
if (addressListViewModel.type == WalletType.monero ||
addressListViewModel.type == WalletType.haven) {
await showPopUp<void>(
context: context,
builder: (_) => getIt.get<MoneroAccountListPage>());
} else {
await showPopUp<void>(
context: context,
builder: (_) => getIt.get<NanoAccountListPage>());
}
},
title: S.of(context).accounts,
trailingIcon: Icon(
Icons.arrow_forward_ios,
size: 14,
color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
));
}
if (item is WalletAddressListHeader) {
cell = HeaderTile(
title: S.of(context).addresses,
walletAddressListViewModel: addressListViewModel,
showTrailingButton: !addressListViewModel.isAutoGenerateSubaddressEnabled,
showSearchButton: true,
trailingButtonTap: () =>
Navigator.of(context).pushNamed(Routes.newSubaddress),
trailingIcon: Icon(
Icons.add,
size: 20,
color: Theme.of(context)
.extension<ReceivePageTheme>()!
.iconsColor,
));
}
if (item is WalletAddressListItem) {
cell = Observer(builder: (_) {
final isCurrent =
item.address == addressListViewModel.address.address;
final backgroundColor = isCurrent
? Theme.of(context).extension<ReceivePageTheme>()!.currentTileBackgroundColor
: Theme.of(context).extension<ReceivePageTheme>()!.tilesBackgroundColor;
final textColor = isCurrent
? Theme.of(context).extension<ReceivePageTheme>()!.currentTileTextColor
: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor;
return AddressCell.fromItem(item,
isCurrent: isCurrent,
hasBalance: addressListViewModel.isElectrumWallet,
backgroundColor: backgroundColor,
textColor: textColor,
onTap: (_) => addressListViewModel.setAddress(item),
onEdit: () => Navigator.of(context)
.pushNamed(Routes.newSubaddress, arguments: item));
});
}
return index != 0
? cell
: ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30),
topRight: Radius.circular(30)),
child: cell,
);
})),
],
), ),
)); Observer(
builder: (_) => ListView.separated(
padding: EdgeInsets.all(0),
separatorBuilder: (context, _) => const HorizontalSectionDivider(),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: addressListViewModel.items.length,
itemBuilder: (context, index) {
final item = addressListViewModel.items[index];
Widget cell = Container();
if (item is WalletAccountListHeader) {
cell = HeaderTile(
showTrailingButton: true,
walletAddressListViewModel: addressListViewModel,
trailingButtonTap: () async {
if (addressListViewModel.type == WalletType.monero ||
addressListViewModel.type == WalletType.haven) {
await showPopUp<void>(
context: context,
builder: (_) => getIt.get<MoneroAccountListPage>());
} else {
await showPopUp<void>(
context: context,
builder: (_) => getIt.get<NanoAccountListPage>());
}
},
title: S.of(context).accounts,
trailingIcon: Icon(
Icons.arrow_forward_ios,
size: 14,
color: Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
));
}
if (item is WalletAddressListHeader) {
final hasTitle = item.title != null;
cell = HeaderTile(
title: hasTitle ? item.title! : S.of(context).addresses,
walletAddressListViewModel: addressListViewModel,
showTrailingButton:
!addressListViewModel.isAutoGenerateSubaddressEnabled && !hasTitle,
showSearchButton: true,
trailingButtonTap: () =>
Navigator.of(context).pushNamed(Routes.newSubaddress),
trailingIcon: hasTitle
? null
: Icon(
Icons.add,
size: 20,
color:
Theme.of(context).extension<ReceivePageTheme>()!.iconsColor,
),
);
}
if (item is WalletAddressListItem) {
cell = Observer(builder: (_) {
final isCurrent = item.address == addressListViewModel.address.address;
final backgroundColor = isCurrent
? Theme.of(context)
.extension<ReceivePageTheme>()!
.currentTileBackgroundColor
: Theme.of(context)
.extension<ReceivePageTheme>()!
.tilesBackgroundColor;
final textColor = isCurrent
? Theme.of(context)
.extension<ReceivePageTheme>()!
.currentTileTextColor
: Theme.of(context).extension<ReceivePageTheme>()!.tilesTextColor;
return AddressCell.fromItem(
item,
isCurrent: isCurrent,
hasBalance: addressListViewModel.isElectrumWallet,
backgroundColor: backgroundColor,
textColor: textColor,
onTap: item.isOneTimeReceiveAddress == true
? null
: (_) => addressListViewModel.setAddress(item),
onEdit: item.isOneTimeReceiveAddress == true || item.isPrimary
? null
: () => Navigator.of(context)
.pushNamed(Routes.newSubaddress, arguments: item),
onDelete: !addressListViewModel.isSilentPayments || item.isPrimary
? null
: () => addressListViewModel.deleteAddress(item),
);
});
}
return index != 0
? cell
: ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(30), topRight: Radius.circular(30)),
child: cell,
);
})),
Padding(
padding: EdgeInsets.fromLTRB(24, 24, 24, 32),
child: Text(
addressListViewModel.isSilentPayments
? S.of(context).silent_payments_disclaimer
: S.of(context).electrum_address_disclaimer,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 15,
color: Theme.of(context).extension<BalancePageTheme>()!.labelTextColor)),
),
],
),
));
} }
} }

View file

@ -15,18 +15,22 @@ class AddressCell extends StatelessWidget {
required this.textColor, required this.textColor,
this.onTap, this.onTap,
this.onEdit, this.onEdit,
this.onDelete,
this.txCount, this.txCount,
this.balance, this.balance,
this.isChange = false, this.isChange = false,
this.hasBalance = false}); this.hasBalance = false});
factory AddressCell.fromItem(WalletAddressListItem item, factory AddressCell.fromItem(
{required bool isCurrent, WalletAddressListItem item, {
required Color backgroundColor, required bool isCurrent,
required Color textColor, required Color backgroundColor,
Function(String)? onTap, required Color textColor,
bool hasBalance = false, Function(String)? onTap,
Function()? onEdit}) => bool hasBalance = false,
Function()? onEdit,
Function()? onDelete,
}) =>
AddressCell( AddressCell(
address: item.address, address: item.address,
name: item.name ?? '', name: item.name ?? '',
@ -36,6 +40,7 @@ class AddressCell extends StatelessWidget {
textColor: textColor, textColor: textColor,
onTap: onTap, onTap: onTap,
onEdit: onEdit, onEdit: onEdit,
onDelete: onDelete,
txCount: item.txCount, txCount: item.txCount,
balance: item.balance, balance: item.balance,
isChange: item.isChange, isChange: item.isChange,
@ -49,6 +54,7 @@ class AddressCell extends StatelessWidget {
final Color textColor; final Color textColor;
final Function(String)? onTap; final Function(String)? onTap;
final Function()? onEdit; final Function()? onEdit;
final Function()? onDelete;
final int? txCount; final int? txCount;
final String? balance; final String? balance;
final bool isChange; final bool isChange;
@ -64,7 +70,8 @@ class AddressCell extends StatelessWidget {
} else { } else {
return formatIfCashAddr.substring(0, addressPreviewLength) + return formatIfCashAddr.substring(0, addressPreviewLength) +
'...' + '...' +
formatIfCashAddr.substring(formatIfCashAddr.length - addressPreviewLength, formatIfCashAddr.length); formatIfCashAddr.substring(
formatIfCashAddr.length - addressPreviewLength, formatIfCashAddr.length);
} }
} }
@ -139,7 +146,7 @@ class AddressCell extends StatelessWidget {
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
children: [ children: [
Text( Text(
'Balance: $balance', '${S.of(context).balance}: $balance',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -180,7 +187,7 @@ class AddressCell extends StatelessWidget {
ActionPane _actionPane(BuildContext context) => ActionPane( ActionPane _actionPane(BuildContext context) => ActionPane(
motion: const ScrollMotion(), motion: const ScrollMotion(),
extentRatio: 0.3, extentRatio: onDelete != null ? 0.4 : 0.3,
children: [ children: [
SlidableAction( SlidableAction(
onPressed: (_) => onEdit?.call(), onPressed: (_) => onEdit?.call(),
@ -189,6 +196,14 @@ class AddressCell extends StatelessWidget {
icon: Icons.edit, icon: Icons.edit,
label: S.of(context).edit, label: S.of(context).edit,
), ),
if (onDelete != null)
SlidableAction(
onPressed: (_) => onDelete!.call(),
backgroundColor: Colors.red,
foregroundColor: Colors.white,
icon: Icons.delete,
label: S.of(context).delete,
),
], ],
); );
} }

View file

@ -1,3 +1,7 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/main.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/utils/show_pop_up.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:cake_wallet/view_model/rescan_view_model.dart'; import 'package:cake_wallet/view_model/rescan_view_model.dart';
@ -11,7 +15,8 @@ class RescanPage extends BasePage {
: _blockchainHeightWidgetKey = GlobalKey<BlockchainHeightState>(); : _blockchainHeightWidgetKey = GlobalKey<BlockchainHeightState>();
@override @override
String get title => S.current.rescan; String get title =>
_rescanViewModel.isSilentPaymentsScan ? S.current.silent_payments_scanning : S.current.rescan;
final GlobalKey<BlockchainHeightState> _blockchainHeightWidgetKey; final GlobalKey<BlockchainHeightState> _blockchainHeightWidgetKey;
final RescanViewModel _rescanViewModel; final RescanViewModel _rescanViewModel;
@ -19,20 +24,28 @@ class RescanPage extends BasePage {
Widget body(BuildContext context) { Widget body(BuildContext context) {
return Padding( return Padding(
padding: EdgeInsets.only(left: 24, right: 24, bottom: 24), padding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
child: child: Column(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Column(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Observer(
BlockchainHeightWidget(key: _blockchainHeightWidgetKey, builder: (_) => BlockchainHeightWidget(
onHeightOrDateEntered: (value) => key: _blockchainHeightWidgetKey,
_rescanViewModel.isButtonEnabled = value), onHeightOrDateEntered: (value) => _rescanViewModel.isButtonEnabled = value,
isSilentPaymentsScan: _rescanViewModel.isSilentPaymentsScan,
doSingleScan: _rescanViewModel.doSingleScan,
toggleSingleScan: () =>
_rescanViewModel.doSingleScan = !_rescanViewModel.doSingleScan,
)),
Observer( Observer(
builder: (_) => LoadingPrimaryButton( builder: (_) => LoadingPrimaryButton(
isLoading: isLoading: _rescanViewModel.state == RescanWalletState.rescaning,
_rescanViewModel.state == RescanWalletState.rescaning,
text: S.of(context).rescan, text: S.of(context).rescan,
onPressed: () async { onPressed: () async {
await _rescanViewModel.rescanCurrentWallet( if (_rescanViewModel.isSilentPaymentsScan) {
restoreHeight: return _toggleSilentPaymentsScanning(context);
_blockchainHeightWidgetKey.currentState!.height); }
_rescanViewModel.rescanCurrentWallet(
restoreHeight: _blockchainHeightWidgetKey.currentState!.height);
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
color: Theme.of(context).primaryColor, color: Theme.of(context).primaryColor,
@ -42,4 +55,32 @@ class RescanPage extends BasePage {
]), ]),
); );
} }
Future<void> _toggleSilentPaymentsScanning(BuildContext context) async {
final height = _blockchainHeightWidgetKey.currentState!.height;
Navigator.of(context).pop();
final needsToSwitch =
await bitcoin!.getNodeIsElectrsSPEnabled(_rescanViewModel.wallet) == false;
if (needsToSwitch) {
return showPopUp<void>(
context: navigatorKey.currentState!.context,
builder: (BuildContext _dialogContext) => AlertWithTwoActions(
alertTitle: S.of(_dialogContext).change_current_node_title,
alertContent: S.of(_dialogContext).confirm_silent_payments_switch_node,
rightButtonText: S.of(_dialogContext).ok,
leftButtonText: S.of(_dialogContext).cancel,
actionRightButton: () async {
Navigator.of(_dialogContext).pop();
_rescanViewModel.rescanCurrentWallet(restoreHeight: height);
},
actionLeftButton: () => Navigator.of(_dialogContext).pop(),
));
}
_rescanViewModel.rescanCurrentWallet(restoreHeight: height);
}
} }

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/auth_service.dart'; import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/entities/contact_record.dart'; import 'package:cake_wallet/entities/contact_record.dart';
import 'package:cake_wallet/core/execution_state.dart'; import 'package:cake_wallet/core/execution_state.dart';
@ -420,6 +421,10 @@ class SendPage extends BasePage {
return; return;
} }
if (sendViewModel.isElectrumWallet) {
bitcoin!.updateFeeRates(sendViewModel.wallet);
}
reaction((_) => sendViewModel.state, (ExecutionState state) { reaction((_) => sendViewModel.state, (ExecutionState state) {
if (dialogContext != null && dialogContext?.mounted == true) { if (dialogContext != null && dialogContext?.mounted == true) {
Navigator.of(dialogContext!).pop(); Navigator.of(dialogContext!).pop();

View file

@ -39,7 +39,9 @@ class ConnectionSyncPage extends BasePage {
), ),
if (dashboardViewModel.hasRescan) ...[ if (dashboardViewModel.hasRescan) ...[
SettingsCellWithArrow( SettingsCellWithArrow(
title: S.current.rescan, title: dashboardViewModel.hasSilentPayments
? S.current.silent_payments_scanning
: S.current.rescan,
handler: (context) => Navigator.of(context).pushNamed(Routes.rescan), handler: (context) => Navigator.of(context).pushNamed(Routes.rescan),
), ),
if (DeviceInfo.instance.isMobile && FeatureFlag.isBackgroundSyncEnabled) ...[ if (DeviceInfo.instance.isMobile && FeatureFlag.isBackgroundSyncEnabled) ...[

View file

@ -0,0 +1,50 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
import 'package:cake_wallet/view_model/settings/silent_payments_settings_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class SilentPaymentsSettingsPage extends BasePage {
SilentPaymentsSettingsPage(this._silentPaymentsSettingsViewModel);
@override
String get title => S.current.silent_payments_settings;
final SilentPaymentsSettingsViewModel _silentPaymentsSettingsViewModel;
@override
Widget body(BuildContext context) {
return SingleChildScrollView(
child: Observer(builder: (_) {
return Container(
padding: EdgeInsets.only(top: 10),
child: Column(
children: [
SettingsSwitcherCell(
title: S.current.silent_payments_display_card,
value: _silentPaymentsSettingsViewModel.silentPaymentsCardDisplay,
onValueChange: (_, bool value) {
_silentPaymentsSettingsViewModel.setSilentPaymentsCardDisplay(value);
},
),
SettingsSwitcherCell(
title: S.current.silent_payments_always_scan,
value: _silentPaymentsSettingsViewModel.silentPaymentsAlwaysScan,
onValueChange: (_, bool value) {
_silentPaymentsSettingsViewModel.setSilentPaymentsAlwaysScan(value);
},
),
SettingsCellWithArrow(
title: S.current.silent_payments_scanning,
handler: (BuildContext context) => Navigator.of(context).pushNamed(Routes.rescan),
),
],
),
);
}),
);
}
}

View file

@ -57,6 +57,7 @@ class UnspentCoinsListFormState extends State<UnspentCoinsListForm> {
isSending: item.isSending, isSending: item.isSending,
isFrozen: item.isFrozen, isFrozen: item.isFrozen,
isChange: item.isChange, isChange: item.isChange,
isSilentPayment: item.isSilentPayment,
onCheckBoxTap: item.isFrozen onCheckBoxTap: item.isFrozen
? null ? null
: () async { : () async {

View file

@ -12,6 +12,7 @@ class UnspentCoinsListItem extends StatelessWidget {
required this.isSending, required this.isSending,
required this.isFrozen, required this.isFrozen,
required this.isChange, required this.isChange,
required this.isSilentPayment,
this.onCheckBoxTap, this.onCheckBoxTap,
}); });
@ -21,18 +22,16 @@ class UnspentCoinsListItem extends StatelessWidget {
final bool isSending; final bool isSending;
final bool isFrozen; final bool isFrozen;
final bool isChange; final bool isChange;
final bool isSilentPayment;
final Function()? onCheckBoxTap; final Function()? onCheckBoxTap;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final unselectedItemColor = Theme.of(context).cardColor; final unselectedItemColor = Theme.of(context).cardColor;
final selectedItemColor = Theme.of(context).primaryColor; final selectedItemColor = Theme.of(context).primaryColor;
final itemColor = isSending final itemColor = isSending ? selectedItemColor : unselectedItemColor;
? selectedItemColor final amountColor =
: unselectedItemColor; isSending ? Colors.white : Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor;
final amountColor = isSending
? Colors.white
: Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor;
final addressColor = isSending final addressColor = isSending
? Colors.white.withOpacity(0.5) ? Colors.white.withOpacity(0.5)
: Theme.of(context).extension<CakeTextTheme>()!.buttonSecondaryTextColor; : Theme.of(context).extension<CakeTextTheme>()!.buttonSecondaryTextColor;
@ -121,6 +120,23 @@ class UnspentCoinsListItem extends StatelessWidget {
), ),
), ),
), ),
if (isSilentPayment)
Container(
height: 17,
padding: EdgeInsets.only(left: 6, right: 6),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(8.5)),
color: Colors.white),
alignment: Alignment.center,
child: Text(
S.of(context).silent_payments,
style: TextStyle(
color: itemColor,
fontSize: 7,
fontWeight: FontWeight.w600,
),
),
),
], ],
), ),
), ),

View file

@ -96,6 +96,7 @@ class WalletListBody extends StatefulWidget {
class WalletListBodyState extends State<WalletListBody> { class WalletListBodyState extends State<WalletListBody> {
final moneroIcon = Image.asset('assets/images/monero_logo.png', height: 24, width: 24); final moneroIcon = Image.asset('assets/images/monero_logo.png', height: 24, width: 24);
final bitcoinIcon = Image.asset('assets/images/bitcoin.png', height: 24, width: 24); final bitcoinIcon = Image.asset('assets/images/bitcoin.png', height: 24, width: 24);
final tBitcoinIcon = Image.asset('assets/images/tbtc.png', height: 24, width: 24);
final litecoinIcon = Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24); final litecoinIcon = Image.asset('assets/images/litecoin_icon.png', height: 24, width: 24);
final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24); final nonWalletTypeIcon = Image.asset('assets/images/close.png', height: 24, width: 24);
final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24); final havenIcon = Image.asset('assets/images/haven_logo.png', height: 24, width: 24);
@ -162,7 +163,10 @@ class WalletListBodyState extends State<WalletListBody> {
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[ children: <Widget>[
wallet.isEnabled wallet.isEnabled
? _imageFor(type: wallet.type) ? _imageFor(
type: wallet.type,
isTestnet: wallet.isTestnet,
)
: nonWalletTypeIcon, : nonWalletTypeIcon,
SizedBox(width: 10), SizedBox(width: 10),
Flexible( Flexible(
@ -297,9 +301,12 @@ class WalletListBodyState extends State<WalletListBody> {
); );
} }
Image _imageFor({required WalletType type}) { Image _imageFor({required WalletType type, bool? isTestnet}) {
switch (type) { switch (type) {
case WalletType.bitcoin: case WalletType.bitcoin:
if (isTestnet == true) {
return tBitcoinIcon;
}
return bitcoinIcon; return bitcoinIcon;
case WalletType.monero: case WalletType.monero:
return moneroIcon; return moneroIcon;

View file

@ -1,3 +1,5 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/src/widgets/standard_switch.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/date_picker.dart'; import 'package:cake_wallet/utils/date_picker.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -12,13 +14,19 @@ class BlockchainHeightWidget extends StatefulWidget {
this.onHeightChange, this.onHeightChange,
this.focusNode, this.focusNode,
this.onHeightOrDateEntered, this.onHeightOrDateEntered,
this.hasDatePicker = true}) this.hasDatePicker = true,
: super(key: key); this.isSilentPaymentsScan = false,
this.toggleSingleScan,
this.doSingleScan = false,
}) : super(key: key);
final Function(int)? onHeightChange; final Function(int)? onHeightChange;
final Function(bool)? onHeightOrDateEntered; final Function(bool)? onHeightOrDateEntered;
final FocusNode? focusNode; final FocusNode? focusNode;
final bool hasDatePicker; final bool hasDatePicker;
final bool isSilentPaymentsScan;
final bool doSingleScan;
final Function()? toggleSingleScan;
@override @override
State<StatefulWidget> createState() => BlockchainHeightState(); State<StatefulWidget> createState() => BlockchainHeightState();
@ -64,9 +72,10 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
child: BaseTextFormField( child: BaseTextFormField(
focusNode: widget.focusNode, focusNode: widget.focusNode,
controller: restoreHeightController, controller: restoreHeightController,
keyboardType: TextInputType.numberWithOptions( keyboardType: TextInputType.numberWithOptions(signed: false, decimal: false),
signed: false, decimal: false), hintText: widget.isSilentPaymentsScan
hintText: S.of(context).widgets_restore_from_blockheight, ? S.of(context).silent_payments_scan_from_height
: S.of(context).widgets_restore_from_blockheight,
))) )))
], ],
), ),
@ -78,8 +87,7 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
style: TextStyle( style: TextStyle(
fontSize: 16.0, fontSize: 16.0,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
Theme.of(context).extension<CakeTextTheme>()!.titleColor),
), ),
), ),
Row( Row(
@ -91,22 +99,47 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
child: IgnorePointer( child: IgnorePointer(
child: BaseTextFormField( child: BaseTextFormField(
controller: dateController, controller: dateController,
hintText: S.of(context).widgets_restore_from_date, hintText: widget.isSilentPaymentsScan
? S.of(context).silent_payments_scan_from_date
: S.of(context).widgets_restore_from_date,
)), )),
), ),
)) ))
], ],
), ),
if (widget.isSilentPaymentsScan)
Padding(
padding: EdgeInsets.only(top: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
S.of(context).scan_one_block,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.normal,
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
),
),
Padding(
padding: const EdgeInsets.only(right: 8),
child: StandardSwitch(
value: widget.doSingleScan,
onTaped: () => widget.toggleSingleScan?.call(),
),
)
],
),
),
Padding( Padding(
padding: EdgeInsets.only(left: 40, right: 40, top: 24), padding: EdgeInsets.only(left: 40, right: 40, top: 24),
child: Text( child: Text(
S.of(context).restore_from_date_or_blockheight, widget.isSilentPaymentsScan
? S.of(context).silent_payments_scan_from_date_or_blockheight
: S.of(context).restore_from_date_or_blockheight,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12, fontWeight: FontWeight.normal, color: Theme.of(context).hintColor),
fontWeight: FontWeight.normal,
color: Theme.of(context).hintColor
),
), ),
) )
] ]
@ -123,7 +156,12 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
lastDate: now); lastDate: now);
if (date != null) { if (date != null) {
final height = monero!.getHeightByDate(date: date); int height;
if (widget.isSilentPaymentsScan) {
height = bitcoin!.getHeightByDate(date: date);
} else {
height = monero!.getHeightByDate(date: date);
}
setState(() { setState(() {
dateController.text = DateFormat('yyyy-MM-dd').format(date); dateController.text = DateFormat('yyyy-MM-dd').format(date);
restoreHeightController.text = '$height'; restoreHeightController.text = '$height';

View file

@ -2,19 +2,28 @@ import 'package:cake_wallet/themes/extensions/balance_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:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart'; import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
import 'package:flutter_svg/flutter_svg.dart';
class DashBoardRoundedCardWidget extends StatelessWidget { class DashBoardRoundedCardWidget extends StatelessWidget {
DashBoardRoundedCardWidget({ DashBoardRoundedCardWidget({
required this.onTap, required this.onTap,
required this.title, required this.title,
required this.subTitle, required this.subTitle,
this.hint,
this.svgPicture,
this.icon,
this.onClose, this.onClose,
this.customBorder,
}); });
final VoidCallback onTap; final VoidCallback onTap;
final VoidCallback? onClose; final VoidCallback? onClose;
final String title; final String title;
final String subTitle; final String subTitle;
final Widget? hint;
final SvgPicture? svgPicture;
final Icon? icon;
final double? customBorder;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -26,34 +35,56 @@ class DashBoardRoundedCardWidget extends StatelessWidget {
child: Stack( child: Stack(
children: [ children: [
Container( Container(
padding: EdgeInsets.fromLTRB(20, 20, 40, 20), padding: EdgeInsets.all(20),
width: double.infinity, width: double.infinity,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor, color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(customBorder ?? 20),
border: Border.all( border: Border.all(
color: Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor, color: Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor,
), ),
), ),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Row(
title, crossAxisAlignment: CrossAxisAlignment.start,
style: TextStyle( children: [
color: Theme.of(context).extension<DashboardPageTheme>()!.cardTextColor, Expanded(
fontSize: 24, child: Column(
fontWeight: FontWeight.w900, crossAxisAlignment: CrossAxisAlignment.start,
), children: [
Text(
title,
style: TextStyle(
color:
Theme.of(context).extension<DashboardPageTheme>()!.cardTextColor,
fontSize: 24,
fontWeight: FontWeight.w900,
),
softWrap: true,
),
SizedBox(height: 5),
Text(
subTitle,
style: TextStyle(
color: Theme.of(context)
.extension<DashboardPageTheme>()!
.cardTextColor,
fontWeight: FontWeight.w500,
fontFamily: 'Lato'),
softWrap: true,
),
],
),
),
if (svgPicture != null) svgPicture!,
if (icon != null) icon!
],
), ),
SizedBox(height: 5), if (hint != null) ...[
Text( SizedBox(height: 10),
subTitle, hint!,
style: TextStyle( ]
color: Theme.of(context).extension<DashboardPageTheme>()!.cardTextColor,
fontWeight: FontWeight.w500,
fontFamily: 'Lato'),
)
], ],
), ),
), ),

View file

@ -17,6 +17,7 @@ class SettingActions {
connectionSettingAction, connectionSettingAction,
walletSettingAction, walletSettingAction,
addressBookSettingAction, addressBookSettingAction,
silentPaymentsSettingAction,
securityBackupSettingAction, securityBackupSettingAction,
privacySettingAction, privacySettingAction,
displaySettingAction, displaySettingAction,
@ -35,6 +36,15 @@ class SettingActions {
supportSettingAction, supportSettingAction,
]; ];
static SettingActions silentPaymentsSettingAction = SettingActions._(
name: (context) => S.of(context).silent_payments_settings,
image: 'assets/images/bitcoin_menu.png',
onTap: (BuildContext context) {
Navigator.pop(context);
Navigator.of(context).pushNamed(Routes.silentPaymentsSettings);
},
);
static SettingActions connectionSettingAction = SettingActions._( static SettingActions connectionSettingAction = SettingActions._(
name: (context) => S.of(context).connection_sync, name: (context) => S.of(context).connection_sync,
image: 'assets/images/nodes_menu.png', image: 'assets/images/nodes_menu.png',

View file

@ -1,5 +1,4 @@
import 'dart:io'; import 'dart:io';
import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
import 'package:cake_wallet/core/secure_storage.dart'; import 'package:cake_wallet/core/secure_storage.dart';
@ -108,6 +107,8 @@ abstract class SettingsStoreBase with Store {
required this.lookupsOpenAlias, required this.lookupsOpenAlias,
required this.lookupsENS, required this.lookupsENS,
required this.customBitcoinFeeRate, required this.customBitcoinFeeRate,
required this.silentPaymentsCardDisplay,
required this.silentPaymentsAlwaysScan,
TransactionPriority? initialBitcoinTransactionPriority, TransactionPriority? initialBitcoinTransactionPriority,
TransactionPriority? initialMoneroTransactionPriority, TransactionPriority? initialMoneroTransactionPriority,
TransactionPriority? initialHavenTransactionPriority, TransactionPriority? initialHavenTransactionPriority,
@ -518,6 +519,16 @@ abstract class SettingsStoreBase with Store {
(int customBitcoinFeeRate) => (int customBitcoinFeeRate) =>
_sharedPreferences.setInt(PreferencesKey.customBitcoinFeeRate, customBitcoinFeeRate)); _sharedPreferences.setInt(PreferencesKey.customBitcoinFeeRate, customBitcoinFeeRate));
reaction((_) => silentPaymentsCardDisplay, (bool silentPaymentsCardDisplay) {
_sharedPreferences.setBool(
PreferencesKey.silentPaymentsCardDisplay, silentPaymentsCardDisplay);
});
reaction(
(_) => silentPaymentsAlwaysScan,
(bool silentPaymentsAlwaysScan) => _sharedPreferences.setBool(
PreferencesKey.silentPaymentsAlwaysScan, silentPaymentsAlwaysScan));
this.nodes.observe((change) { this.nodes.observe((change) {
if (change.newValue != null && change.key != null) { if (change.newValue != null && change.key != null) {
_saveCurrentNode(change.newValue!, change.key!); _saveCurrentNode(change.newValue!, change.key!);
@ -713,6 +724,12 @@ abstract class SettingsStoreBase with Store {
@observable @observable
int customBitcoinFeeRate; int customBitcoinFeeRate;
@observable
bool silentPaymentsCardDisplay;
@observable
bool silentPaymentsAlwaysScan;
final SecureStorage _secureStorage; final SecureStorage _secureStorage;
final SharedPreferences _sharedPreferences; final SharedPreferences _sharedPreferences;
final BackgroundTasks _backgroundTasks; final BackgroundTasks _backgroundTasks;
@ -859,6 +876,10 @@ abstract class SettingsStoreBase with Store {
final lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true; final lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true;
final lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true; final lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true;
final customBitcoinFeeRate = sharedPreferences.getInt(PreferencesKey.customBitcoinFeeRate) ?? 1; final customBitcoinFeeRate = sharedPreferences.getInt(PreferencesKey.customBitcoinFeeRate) ?? 1;
final silentPaymentsCardDisplay =
sharedPreferences.getBool(PreferencesKey.silentPaymentsCardDisplay) ?? true;
final silentPaymentsAlwaysScan =
sharedPreferences.getBool(PreferencesKey.silentPaymentsAlwaysScan) ?? false;
// If no value // If no value
if (pinLength == null || pinLength == 0) { if (pinLength == null || pinLength == 0) {
@ -1103,6 +1124,8 @@ abstract class SettingsStoreBase with Store {
lookupsOpenAlias: lookupsOpenAlias, lookupsOpenAlias: lookupsOpenAlias,
lookupsENS: lookupsENS, lookupsENS: lookupsENS,
customBitcoinFeeRate: customBitcoinFeeRate, customBitcoinFeeRate: customBitcoinFeeRate,
silentPaymentsCardDisplay: silentPaymentsCardDisplay,
silentPaymentsAlwaysScan: silentPaymentsAlwaysScan,
initialMoneroTransactionPriority: moneroTransactionPriority, initialMoneroTransactionPriority: moneroTransactionPriority,
initialBitcoinTransactionPriority: bitcoinTransactionPriority, initialBitcoinTransactionPriority: bitcoinTransactionPriority,
initialHavenTransactionPriority: havenTransactionPriority, initialHavenTransactionPriority: havenTransactionPriority,
@ -1242,6 +1265,10 @@ abstract class SettingsStoreBase with Store {
lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true; lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true;
lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true; lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true;
customBitcoinFeeRate = sharedPreferences.getInt(PreferencesKey.customBitcoinFeeRate) ?? 1; customBitcoinFeeRate = sharedPreferences.getInt(PreferencesKey.customBitcoinFeeRate) ?? 1;
silentPaymentsCardDisplay =
sharedPreferences.getBool(PreferencesKey.silentPaymentsCardDisplay) ?? true;
silentPaymentsAlwaysScan =
sharedPreferences.getBool(PreferencesKey.silentPaymentsAlwaysScan) ?? false;
final nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey); final nodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
final bitcoinElectrumServerId = final bitcoinElectrumServerId =
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey); sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);

View file

@ -2,6 +2,7 @@ import 'dart:async';
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/contact_base.dart'; import 'package:cake_wallet/entities/contact_base.dart';
import 'package:cake_wallet/entities/wallet_contact.dart'; import 'package:cake_wallet/entities/wallet_contact.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/store/settings_store.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';
@ -40,16 +41,19 @@ abstract class ContactListViewModelBase with Store {
}); });
} else if (info.addresses?.isNotEmpty == true) { } else if (info.addresses?.isNotEmpty == true) {
info.addresses!.forEach((address, label) { info.addresses!.forEach((address, label) {
if (label.isEmpty) {
return;
}
final name = _createName(info.name, label); final name = _createName(info.name, label);
walletContacts.add(WalletContact( walletContacts.add(WalletContact(
address, address,
name, name,
walletTypeToCryptoCurrency(info.type), walletTypeToCryptoCurrency(info.type,
isTestnet:
info.network == null ? false : info.network!.toLowerCase().contains("testnet")),
)); ));
// Only one contact address per wallet
return;
}); });
} else if (info.address != null) { } else {
walletContacts.add(WalletContact( walletContacts.add(WalletContact(
info.address, info.address,
info.name, info.name,
@ -64,7 +68,9 @@ abstract class ContactListViewModelBase with Store {
} }
String _createName(String walletName, String label) { String _createName(String walletName, String label) {
return label.isNotEmpty ? '$walletName ($label)' : walletName; return label.isNotEmpty
? '$walletName (${label.replaceAll(RegExp(r'active', caseSensitive: false), S.current.active).replaceAll(RegExp(r'silent payments', caseSensitive: false), S.current.silent_payments)})'
: walletName;
} }
final bool isAutoGenerateEnabled; final bool isAutoGenerateEnabled;

View file

@ -60,6 +60,9 @@ abstract class BalanceViewModelBase with Store {
@observable @observable
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet; WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet;
@computed
bool get hasSilentPayments => wallet.type == WalletType.bitcoin;
@computed @computed
double get price { double get price {
final price = fiatConvertationStore.prices[appStore.wallet!.currency]; final price = fiatConvertationStore.prices[appStore.wallet!.currency];

View file

@ -0,0 +1,16 @@
import 'package:cake_wallet/ionia/ionia_service.dart';
import 'package:mobx/mobx.dart';
part 'cake_features_view_model.g.dart';
class CakeFeaturesViewModel = CakeFeaturesViewModelBase with _$CakeFeaturesViewModel;
abstract class CakeFeaturesViewModelBase with Store {
final IoniaService _ioniaService;
CakeFeaturesViewModelBase(this._ioniaService);
Future<bool> isIoniaUserAuthenticated() async {
return await _ioniaService.isLogined();
}
}

View file

@ -45,6 +45,7 @@ import 'package:cw_core/wallet_type.dart';
import 'package:eth_sig_util/util/utils.dart'; import 'package:eth_sig_util/util/utils.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
@ -201,6 +202,14 @@ abstract class DashboardViewModelBase with Store {
return true; return true;
}); });
if (hasSilentPayments) {
silentPaymentsScanningActive = bitcoin!.getScanningActive(wallet);
reaction((_) => wallet.syncStatus, (SyncStatus syncStatus) {
silentPaymentsScanningActive = bitcoin!.getScanningActive(wallet);
});
}
} }
@observable @observable
@ -287,11 +296,36 @@ abstract class DashboardViewModelBase with Store {
@observable @observable
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet; WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet;
bool get hasRescan => wallet.type == WalletType.monero || wallet.type == WalletType.haven; @computed
bool get isTestnet => wallet.type == WalletType.bitcoin && bitcoin!.isTestnet(wallet);
@computed
bool get hasRescan =>
wallet.type == WalletType.bitcoin ||
wallet.type == WalletType.monero ||
wallet.type == WalletType.haven;
@computed
bool get hasSilentPayments => wallet.type == WalletType.bitcoin;
@computed
bool get showSilentPaymentsCard => hasSilentPayments && settingsStore.silentPaymentsCardDisplay;
final KeyService keyService; final KeyService keyService;
final SharedPreferences sharedPreferences; final SharedPreferences sharedPreferences;
@observable
bool silentPaymentsScanningActive = false;
@action
void setSilentPaymentsScanning(bool active) {
silentPaymentsScanningActive = active;
if (hasSilentPayments) {
bitcoin!.setScanningActive(wallet, active);
}
}
BalanceViewModel balanceViewModel; BalanceViewModel balanceViewModel;
AppStore appStore; AppStore appStore;

View file

@ -1,17 +0,0 @@
import 'package:cake_wallet/ionia/ionia_service.dart';
import 'package:mobx/mobx.dart';
part 'market_place_view_model.g.dart';
class MarketPlaceViewModel = MarketPlaceViewModelBase with _$MarketPlaceViewModel;
abstract class MarketPlaceViewModelBase with Store {
final IoniaService _ioniaService;
MarketPlaceViewModelBase(this._ioniaService);
Future<bool> isIoniaUserAuthenticated() async {
return await _ioniaService.isLogined();
}
}

View file

@ -1,4 +1,6 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
part 'rescan_view_model.g.dart'; part 'rescan_view_model.g.dart';
@ -8,11 +10,12 @@ class RescanViewModel = RescanViewModelBase with _$RescanViewModel;
enum RescanWalletState { rescaning, none } enum RescanWalletState { rescaning, none }
abstract class RescanViewModelBase with Store { abstract class RescanViewModelBase with Store {
RescanViewModelBase(this._wallet) RescanViewModelBase(this.wallet)
: state = RescanWalletState.none, : state = RescanWalletState.none,
isButtonEnabled = false; isButtonEnabled = false,
doSingleScan = false;
final WalletBase _wallet; final WalletBase wallet;
@observable @observable
RescanWalletState state; RescanWalletState state;
@ -20,11 +23,21 @@ abstract class RescanViewModelBase with Store {
@observable @observable
bool isButtonEnabled; bool isButtonEnabled;
@observable
bool doSingleScan;
@computed
bool get isSilentPaymentsScan => wallet.type == WalletType.bitcoin;
@action @action
Future<void> rescanCurrentWallet({required int restoreHeight}) async { Future<void> rescanCurrentWallet({required int restoreHeight}) async {
state = RescanWalletState.rescaning; state = RescanWalletState.rescaning;
await _wallet.rescan(height: restoreHeight); if (wallet.type != WalletType.bitcoin) {
_wallet.transactionHistory.clear(); wallet.rescan(height: restoreHeight);
wallet.transactionHistory.clear();
} else {
bitcoin!.rescan(wallet, height: restoreHeight, doSingleScan: doSingleScan);
}
state = RescanWalletState.none; state = RescanWalletState.none;
} }
} }

View file

@ -229,7 +229,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
isFiatDisabled ? '' : pendingTransactionFeeFiatAmount + ' ' + fiat.title; isFiatDisabled ? '' : pendingTransactionFeeFiatAmount + ' ' + fiat.title;
@computed @computed
bool get isReadyForSend => wallet.syncStatus is SyncedSyncStatus; bool get isReadyForSend =>
wallet.syncStatus is SyncedSyncStatus ||
// If silent payments scanning, can still send payments
(wallet.type == WalletType.bitcoin && wallet.syncStatus is SyncingSyncStatus);
@computed @computed
List<Template> get templates => sendTemplateViewModel.templates List<Template> get templates => sendTemplateViewModel.templates
@ -599,6 +602,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
walletType == WalletType.litecoin || walletType == WalletType.litecoin ||
walletType == WalletType.bitcoinCash) { walletType == WalletType.bitcoinCash) {
if (error is TransactionWrongBalanceException) { if (error is TransactionWrongBalanceException) {
if (error.amount != null)
return S.current
.tx_wrong_balance_with_amount_exception(currency.toString(), error.amount.toString());
return S.current.tx_wrong_balance_exception(currency.toString()); return S.current.tx_wrong_balance_exception(currency.toString());
} }
if (error is TransactionNoInputsException) { if (error is TransactionNoInputsException) {
@ -625,9 +632,15 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
if (error is TransactionCommitFailedVoutNegative) { if (error is TransactionCommitFailedVoutNegative) {
return S.current.tx_rejected_vout_negative; return S.current.tx_rejected_vout_negative;
} }
if (error is TransactionCommitFailedBIP68Final) {
return S.current.tx_rejected_bip68_final;
}
if (error is TransactionNoDustOnChangeException) { if (error is TransactionNoDustOnChangeException) {
return S.current.tx_commit_exception_no_dust_on_change(error.min, error.max); return S.current.tx_commit_exception_no_dust_on_change(error.min, error.max);
} }
if (error is TransactionInputNotSupported) {
return S.current.tx_invalid_input;
}
} }
return errorMessage; return errorMessage;

View file

@ -0,0 +1,33 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:mobx/mobx.dart';
part 'silent_payments_settings_view_model.g.dart';
class SilentPaymentsSettingsViewModel = SilentPaymentsSettingsViewModelBase
with _$SilentPaymentsSettingsViewModel;
abstract class SilentPaymentsSettingsViewModelBase with Store {
SilentPaymentsSettingsViewModelBase(this._settingsStore, this._wallet);
final SettingsStore _settingsStore;
final WalletBase _wallet;
@computed
bool get silentPaymentsCardDisplay => _settingsStore.silentPaymentsCardDisplay;
@computed
bool get silentPaymentsAlwaysScan => _settingsStore.silentPaymentsAlwaysScan;
@action
void setSilentPaymentsCardDisplay(bool value) {
_settingsStore.silentPaymentsCardDisplay = value;
}
@action
void setSilentPaymentsAlwaysScan(bool value) {
_settingsStore.silentPaymentsAlwaysScan = value;
if (value) bitcoin!.setScanningActive(_wallet, true);
}
}

View file

@ -15,7 +15,8 @@ abstract class UnspentCoinsItemBase with Store {
required this.isChange, required this.isChange,
required this.amountRaw, required this.amountRaw,
required this.vout, required this.vout,
required this.keyImage required this.keyImage,
required this.isSilentPayment,
}); });
@observable @observable
@ -47,4 +48,7 @@ abstract class UnspentCoinsItemBase with Store {
@observable @observable
String? keyImage; String? keyImage;
@observable
bool isSilentPayment;
} }

View file

@ -1,10 +1,12 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/monero/monero.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: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';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
import 'package:flutter/cupertino.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
@ -86,22 +88,33 @@ abstract class UnspentCoinsListViewModelBase with Store {
@action @action
void _updateUnspentCoinsInfo() { void _updateUnspentCoinsInfo() {
_items.clear(); _items.clear();
_items.addAll(_getUnspents().map((elem) {
final info =
getUnspentCoinInfo(elem.hash, elem.address, elem.value, elem.vout, elem.keyImage);
return UnspentCoinsItem( List<UnspentCoinsItem> unspents = [];
address: elem.address, _getUnspents().forEach((elem) {
amount: '${formatAmountToString(elem.value)} ${wallet.currency.title}', try {
hash: elem.hash, final info =
isFrozen: info.isFrozen, getUnspentCoinInfo(elem.hash, elem.address, elem.value, elem.vout, elem.keyImage);
note: info.note,
isSending: info.isSending, unspents.add(UnspentCoinsItem(
amountRaw: elem.value, address: elem.address,
vout: elem.vout, amount: '${formatAmountToString(elem.value)} ${wallet.currency.title}',
keyImage: elem.keyImage, hash: elem.hash,
isChange: elem.isChange, isFrozen: info.isFrozen,
); note: info.note,
})); isSending: info.isSending,
amountRaw: elem.value,
vout: elem.vout,
keyImage: elem.keyImage,
isChange: elem.isChange,
isSilentPayment: info.isSilentPayment ?? false,
));
} catch (e, s) {
print(s);
print(e.toString());
ExceptionHandler.onError(FlutterErrorDetails(exception: e, stack: s));
}
});
_items.addAll(unspents);
} }
} }

View file

@ -1,3 +1,6 @@
import 'package:cake_wallet/utils/list_item.dart'; import 'package:cake_wallet/utils/list_item.dart';
class WalletAddressListHeader extends ListItem {} class WalletAddressListHeader extends ListItem {
final String? title;
WalletAddressListHeader({this.title});
}

View file

@ -8,8 +8,10 @@ class WalletAddressListItem extends ListItem {
this.name, this.name,
this.txCount, this.txCount,
this.balance, this.balance,
this.isChange = false}) this.isChange = false,
: super(); // Address that is only ever used once, shouldn't be used to receive funds, copy and paste, share etc
this.isOneTimeReceiveAddress = false,
}) : super();
final int? id; final int? id;
final bool isPrimary; final bool isPrimary;
@ -18,7 +20,8 @@ class WalletAddressListItem extends ListItem {
final int? txCount; final int? txCount;
final String? balance; final String? balance;
final bool isChange; final bool isChange;
final bool? isOneTimeReceiveAddress;
@override @override
String toString() => name ?? address; String toString() => name ?? address;
} }

View file

@ -334,20 +334,55 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
} }
if (isElectrumWallet) { if (isElectrumWallet) {
final addressItems = bitcoin!.getSubAddresses(wallet).map((subaddress) { if (bitcoin!.hasSelectedSilentPayments(wallet)) {
final isPrimary = subaddress.id == 0; final addressItems = bitcoin!.getSilentPaymentAddresses(wallet).map((address) {
final isPrimary = address.id == 0;
return WalletAddressListItem( return WalletAddressListItem(
id: subaddress.id, id: address.id,
isPrimary: isPrimary, isPrimary: isPrimary,
name: subaddress.name, name: address.name,
address: subaddress.address, address: address.address,
txCount: subaddress.txCount, txCount: address.txCount,
balance: AmountConverter.amountIntToString( balance: AmountConverter.amountIntToString(
walletTypeToCryptoCurrency(type), subaddress.balance), walletTypeToCryptoCurrency(type), address.balance),
isChange: subaddress.isChange); isChange: address.isChange,
}); );
addressList.addAll(addressItems); });
addressList.addAll(addressItems);
addressList.add(WalletAddressListHeader(title: S.current.received));
final receivedAddressItems =
bitcoin!.getSilentPaymentReceivedAddresses(wallet).map((address) {
return WalletAddressListItem(
id: address.id,
isPrimary: false,
name: address.name,
address: address.address,
txCount: address.txCount,
balance: AmountConverter.amountIntToString(
walletTypeToCryptoCurrency(type), address.balance),
isChange: address.isChange,
isOneTimeReceiveAddress: true,
);
});
addressList.addAll(receivedAddressItems);
} else {
final addressItems = bitcoin!.getSubAddresses(wallet).map((subaddress) {
final isPrimary = subaddress.id == 0;
return WalletAddressListItem(
id: subaddress.id,
isPrimary: isPrimary,
name: subaddress.name,
address: subaddress.address,
txCount: subaddress.txCount,
balance: AmountConverter.amountIntToString(
walletTypeToCryptoCurrency(type), subaddress.balance),
isChange: subaddress.isChange);
});
addressList.addAll(addressItems);
}
} }
if (wallet.type == WalletType.ethereum) { if (wallet.type == WalletType.ethereum) {
@ -416,9 +451,14 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
wallet.type == WalletType.litecoin || wallet.type == WalletType.litecoin ||
wallet.type == WalletType.bitcoinCash; wallet.type == WalletType.bitcoinCash;
@computed
bool get isSilentPayments =>
wallet.type == WalletType.bitcoin && bitcoin!.hasSelectedSilentPayments(wallet);
@computed @computed
bool get isAutoGenerateSubaddressEnabled => bool get isAutoGenerateSubaddressEnabled =>
_settingsStore.autoGenerateSubaddressStatus != AutoGenerateSubaddressStatus.disabled; _settingsStore.autoGenerateSubaddressStatus != AutoGenerateSubaddressStatus.disabled &&
!isSilentPayments;
List<ListItem> _baseItems; List<ListItem> _baseItems;
@ -479,4 +519,11 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
amount = ''; amount = '';
} }
} }
@action
void deleteAddress(ListItem item) {
if (wallet.type == WalletType.bitcoin && item is WalletAddressListItem) {
bitcoin!.deleteSilentPaymentAddress(wallet, item.address);
}
}
} }

View file

@ -7,6 +7,7 @@ class WalletListItem {
required this.key, required this.key,
this.isCurrent = false, this.isCurrent = false,
this.isEnabled = true, this.isEnabled = true,
this.isTestnet = false,
}); });
final String name; final String name;
@ -14,4 +15,5 @@ class WalletListItem {
final bool isCurrent; final bool isCurrent;
final dynamic key; final dynamic key;
final bool isEnabled; final bool isEnabled;
final bool isTestnet;
} }

View file

@ -61,6 +61,7 @@ abstract class WalletListViewModelBase with Store {
key: info.key, key: info.key,
isCurrent: info.name == _appStore.wallet?.name && info.type == _appStore.wallet?.type, isCurrent: info.name == _appStore.wallet?.name && info.type == _appStore.wallet?.type,
isEnabled: availableWalletTypes.contains(info.type), isEnabled: availableWalletTypes.contains(info.type),
isTestnet: info.network?.toLowerCase().contains('testnet') ?? false,
), ),
), ),
); );

View file

@ -47,6 +47,8 @@ PODS:
- shared_preferences_foundation (0.0.1): - shared_preferences_foundation (0.0.1):
- Flutter - Flutter
- FlutterMacOS - FlutterMacOS
- sp_scanner (0.0.1):
- FlutterMacOS
- url_launcher_macos (0.0.1): - url_launcher_macos (0.0.1):
- FlutterMacOS - FlutterMacOS
- wakelock_plus (0.0.1): - wakelock_plus (0.0.1):
@ -67,6 +69,7 @@ DEPENDENCIES:
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
- share_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/share_plus_macos/macos`) - share_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/share_plus_macos/macos`)
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`) - shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
- sp_scanner (from `Flutter/ephemeral/.symlinks/plugins/sp_scanner/macos`)
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
- wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`) - wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`)
@ -104,6 +107,8 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral/.symlinks/plugins/share_plus_macos/macos :path: Flutter/ephemeral/.symlinks/plugins/share_plus_macos/macos
shared_preferences_foundation: shared_preferences_foundation:
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin :path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
sp_scanner:
:path: Flutter/ephemeral/.symlinks/plugins/sp_scanner/macos
url_launcher_macos: url_launcher_macos:
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
wakelock_plus: wakelock_plus:
@ -126,7 +131,8 @@ SPEC CHECKSUMS:
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
share_plus_macos: 853ee48e7dce06b633998ca0735d482dd671ade4 share_plus_macos: 853ee48e7dce06b633998ca0735d482dd671ade4
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95 sp_scanner: 269d96e0ec3173e69156be7239b95182be3b8303
url_launcher_macos: 5f437abeda8c85500ceb03f5c1938a8c5a705399
wakelock_plus: 4783562c9a43d209c458cb9b30692134af456269 wakelock_plus: 4783562c9a43d209c458cb9b30692134af456269
PODFILE CHECKSUM: 65ec1541137fb5b35d00490dec1bb48d4d9586bb PODFILE CHECKSUM: 65ec1541137fb5b35d00490dec1bb48d4d9586bb

View file

@ -1,13 +1,13 @@
cd cw_core && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_core; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd ..
cd cw_evm && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_evm; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd ..
cd cw_monero && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_monero; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd ..
cd cw_bitcoin && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_bitcoin; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd ..
cd cw_haven && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_haven; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd ..
cd cw_nano && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_nano; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd ..
cd cw_bitcoin_cash && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_bitcoin_cash; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd ..
cd cw_solana && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_solana; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd ..
cd cw_tron && flutter pub get && flutter packages pub run build_runner build --delete-conflicting-outputs && cd .. cd cw_tron; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; cd ..
cd cw_mweb && flutter pub get && cd .. cd cw_mweb && flutter pub get && cd ..
cd cw_ethereum && flutter pub get && cd .. cd cw_polygon; flutter pub get; cd ..
cd cw_polygon && flutter pub get && cd .. cd cw_ethereum; flutter pub get; cd ..
flutter packages pub run build_runner build --delete-conflicting-outputs flutter packages pub run build_runner build --delete-conflicting-outputs

View file

@ -105,7 +105,7 @@ dependencies:
solana: ^0.30.1 solana: ^0.30.1
bitcoin_base: bitcoin_base:
git: git:
url: https://github.com/cake-tech/bitcoin_base.git url: https://github.com/cake-tech/bitcoin_base
ref: cake-mweb ref: cake-mweb
ledger_flutter: ^1.0.1 ledger_flutter: ^1.0.1
@ -117,7 +117,7 @@ dev_dependencies:
mobx_codegen: ^2.1.1 mobx_codegen: ^2.1.1
build_resolvers: ^2.0.9 build_resolvers: ^2.0.9
hive_generator: ^1.1.3 hive_generator: ^1.1.3
flutter_launcher_icons: ^0.11.0 # flutter_launcher_icons: ^0.11.0
# check flutter_launcher_icons for usage # check flutter_launcher_icons for usage
pedantic: ^1.8.0 pedantic: ^1.8.0
# replace https://github.com/dart-lang/lints#migrating-from-packagepedantic # replace https://github.com/dart-lang/lints#migrating-from-packagepedantic

View file

@ -71,6 +71,7 @@
"backup": "نسخ الاحتياطي", "backup": "نسخ الاحتياطي",
"backup_file": "ملف النسخ الاحتياطي", "backup_file": "ملف النسخ الاحتياطي",
"backup_password": "كلمة مرور النسخ الاحتياطي", "backup_password": "كلمة مرور النسخ الاحتياطي",
"balance": "توازن",
"balance_page": "صفحة التوازن", "balance_page": "صفحة التوازن",
"bill_amount": "مبلغ الفاتورة", "bill_amount": "مبلغ الفاتورة",
"billing_address_info": "إذا طُلب منك عنوان إرسال فواتير ، فأدخل عنوان الشحن الخاص بك", "billing_address_info": "إذا طُلب منك عنوان إرسال فواتير ، فأدخل عنوان الشحن الخاص بك",
@ -78,6 +79,7 @@
"bitcoin_dark_theme": "موضوع البيتكوين الظلام", "bitcoin_dark_theme": "موضوع البيتكوين الظلام",
"bitcoin_light_theme": "موضوع البيتكوين الخفيفة", "bitcoin_light_theme": "موضوع البيتكوين الخفيفة",
"bitcoin_payments_require_1_confirmation": "تتطلب مدفوعات Bitcoin تأكيدًا واحدًا ، والذي قد يستغرق 20 دقيقة أو أكثر. شكرا لصبرك! سيتم إرسال بريد إلكتروني إليك عند تأكيد الدفع.", "bitcoin_payments_require_1_confirmation": "تتطلب مدفوعات Bitcoin تأكيدًا واحدًا ، والذي قد يستغرق 20 دقيقة أو أكثر. شكرا لصبرك! سيتم إرسال بريد إلكتروني إليك عند تأكيد الدفع.",
"block_remaining": "1 كتلة متبقية",
"Blocks_remaining": "بلوك متبقي ${status}", "Blocks_remaining": "بلوك متبقي ${status}",
"bluetooth": "بلوتوث", "bluetooth": "بلوتوث",
"bright_theme": "مشرق", "bright_theme": "مشرق",
@ -139,6 +141,7 @@
"confirm_fee_deduction": "تأكيد خصم الرسوم", "confirm_fee_deduction": "تأكيد خصم الرسوم",
"confirm_fee_deduction_content": "هل توافق على خصم الرسوم من الإخراج؟", "confirm_fee_deduction_content": "هل توافق على خصم الرسوم من الإخراج؟",
"confirm_sending": "تأكيد الإرسال", "confirm_sending": "تأكيد الإرسال",
"confirm_silent_payments_switch_node": "حاليا مطلوب لتبديل العقد لمسح المدفوعات الصامتة",
"confirmations": "التأكيدات", "confirmations": "التأكيدات",
"confirmed": "رصيد مؤكد", "confirmed": "رصيد مؤكد",
"confirmed_tx": "مؤكد", "confirmed_tx": "مؤكد",
@ -221,6 +224,7 @@
"electrum_address_disclaimer": "نقوم بإنشاء عناوين جديدة في كل مرة تستخدم فيها عنوانًا ، لكن العناوين السابقة تستمر في العمل", "electrum_address_disclaimer": "نقوم بإنشاء عناوين جديدة في كل مرة تستخدم فيها عنوانًا ، لكن العناوين السابقة تستمر في العمل",
"email_address": "عنوان البريد الالكترونى", "email_address": "عنوان البريد الالكترونى",
"enable_replace_by_fee": "تمكين الاستبدال", "enable_replace_by_fee": "تمكين الاستبدال",
"enable_silent_payments_scanning": "تمكين المسح الضوئي للمدفوعات الصامتة",
"enabled": "ممكنة", "enabled": "ممكنة",
"enter_amount": "أدخل المبلغ", "enter_amount": "أدخل المبلغ",
"enter_backup_password": "أدخل كلمة المرور الاحتياطية هنا", "enter_backup_password": "أدخل كلمة المرور الاحتياطية هنا",
@ -278,6 +282,7 @@
"extracted_address_content": "سوف ترسل الأموال إلى\n${recipient_name}", "extracted_address_content": "سوف ترسل الأموال إلى\n${recipient_name}",
"failed_authentication": "${state_error} فشل المصادقة.", "failed_authentication": "${state_error} فشل المصادقة.",
"faq": "الأسئلة الشائعة", "faq": "الأسئلة الشائعة",
"features": "سمات",
"fetching": "جار الجلب", "fetching": "جار الجلب",
"fiat_api": "Fiat API", "fiat_api": "Fiat API",
"fiat_balance": "الرصيد فيات", "fiat_balance": "الرصيد فيات",
@ -531,6 +536,7 @@
"save_backup_password_alert": "حفظ كلمة المرور الاحتياطية", "save_backup_password_alert": "حفظ كلمة المرور الاحتياطية",
"save_to_downloads": "ﺕﻼﻳﺰﻨﺘﻟﺍ ﻲﻓ ﻆﻔﺣ", "save_to_downloads": "ﺕﻼﻳﺰﻨﺘﻟﺍ ﻲﻓ ﻆﻔﺣ",
"saved_the_trade_id": "لقد تم حفظ معرف العملية", "saved_the_trade_id": "لقد تم حفظ معرف العملية",
"scan_one_block": "مسح كتلة واحدة",
"scan_qr_code": "امسح رمز QR ضوئيًا", "scan_qr_code": "امسح رمز QR ضوئيًا",
"scan_qr_code_to_get_address": "امسح ال QR للحصول على العنوان", "scan_qr_code_to_get_address": "امسح ال QR للحصول على العنوان",
"scan_qr_on_device": " ﺮﺧﺁ ﺯﺎﻬﺟ ﻰﻠﻋ ﺎﻴًﺋﻮﺿ ﺍﺬﻫ ﺔﻌﻳﺮﺴﻟﺍ ﺔﺑﺎﺠﺘﺳﻻﺍ ﺰﻣﺭ ﺢﺴﻤﺑ ﻢﻗ", "scan_qr_on_device": " ﺮﺧﺁ ﺯﺎﻬﺟ ﻰﻠﻋ ﺎﻴًﺋﻮﺿ ﺍﺬﻫ ﺔﻌﻳﺮﺴﻟﺍ ﺔﺑﺎﺠﺘﺳﻻﺍ ﺰﻣﺭ ﺢﺴﻤﺑ ﻢﻗ",
@ -641,11 +647,22 @@
"sign_up": "اشتراك", "sign_up": "اشتراك",
"signTransaction": " ﺔﻠﻣﺎﻌﻤﻟﺍ ﻊﻴﻗﻮﺗ", "signTransaction": " ﺔﻠﻣﺎﻌﻤﻟﺍ ﻊﻴﻗﻮﺗ",
"signup_for_card_accept_terms": "قم بالتسجيل للحصول على البطاقة وقبول الشروط.", "signup_for_card_accept_terms": "قم بالتسجيل للحصول على البطاقة وقبول الشروط.",
"silent_payments": "مدفوعات صامتة",
"silent_payments_always_scan": "حدد المدفوعات الصامتة دائمًا المسح الضوئي",
"silent_payments_disclaimer": "العناوين الجديدة ليست هويات جديدة. إنها إعادة استخدام هوية موجودة مع ملصق مختلف.",
"silent_payments_display_card": "عرض بطاقة المدفوعات الصامتة",
"silent_payments_scan_from_date": "فحص من التاريخ",
"silent_payments_scan_from_date_or_blockheight": "يرجى إدخال ارتفاع الكتلة الذي تريد بدء المسح الضوئي للمدفوعات الصامتة الواردة ، أو استخدام التاريخ بدلاً من ذلك. يمكنك اختيار ما إذا كانت المحفظة تواصل مسح كل كتلة ، أو تتحقق فقط من الارتفاع المحدد.",
"silent_payments_scan_from_height": "فحص من ارتفاع الكتلة",
"silent_payments_scanned_tip": "ممسوح ليفحص! (${tip})",
"silent_payments_scanning": "المدفوعات الصامتة المسح الضوئي",
"silent_payments_settings": "إعدادات المدفوعات الصامتة",
"slidable": "قابل للانزلاق", "slidable": "قابل للانزلاق",
"sort_by": "ترتيب حسب", "sort_by": "ترتيب حسب",
"spend_key_private": "مفتاح الإنفاق (خاص)", "spend_key_private": "مفتاح الإنفاق (خاص)",
"spend_key_public": "مفتاح الإنفاق (عام)", "spend_key_public": "مفتاح الإنفاق (عام)",
"status": "الحالة:", "status": "الحالة:",
"string_default": "تقصير",
"subaddress_title": "قائمة العناوين الفرعية", "subaddress_title": "قائمة العناوين الفرعية",
"subaddresses": "العناوين الفرعية", "subaddresses": "العناوين الفرعية",
"submit_request": "تقديم طلب", "submit_request": "تقديم طلب",
@ -670,10 +687,13 @@
"sync_status_starting_sync": "بدء المزامنة", "sync_status_starting_sync": "بدء المزامنة",
"sync_status_syncronized": "متزامن", "sync_status_syncronized": "متزامن",
"sync_status_syncronizing": "يتم المزامنة", "sync_status_syncronizing": "يتم المزامنة",
"sync_status_timed_out": "نفد وقته",
"sync_status_unsupported": "عقدة غير مدعومة",
"syncing_wallet_alert_content": "قد لا يكتمل رصيدك وقائمة المعاملات الخاصة بك حتى تظهر عبارة “SYNCHRONIZED“ في الأعلى. انقر / اضغط لمعرفة المزيد.", "syncing_wallet_alert_content": "قد لا يكتمل رصيدك وقائمة المعاملات الخاصة بك حتى تظهر عبارة “SYNCHRONIZED“ في الأعلى. انقر / اضغط لمعرفة المزيد.",
"syncing_wallet_alert_title": "محفظتك تتم مزامنتها", "syncing_wallet_alert_title": "محفظتك تتم مزامنتها",
"template": "قالب", "template": "قالب",
"template_name": "اسم القالب", "template_name": "اسم القالب",
"testnet_coins_no_value": "عملات TestNet ليس لها قيمة",
"third_intro_content": "يعيش Yats خارج Cake Wallet أيضًا. يمكن استبدال أي عنوان محفظة على وجه الأرض بـ Yat!", "third_intro_content": "يعيش Yats خارج Cake Wallet أيضًا. يمكن استبدال أي عنوان محفظة على وجه الأرض بـ Yat!",
"third_intro_title": "يتماشي Yat بلطف مع الآخرين", "third_intro_title": "يتماشي Yat بلطف مع الآخرين",
"thorchain_contract_address_not_supported": "لا يدعم Thorchain الإرسال إلى عنوان العقد", "thorchain_contract_address_not_supported": "لا يدعم Thorchain الإرسال إلى عنوان العقد",
@ -749,13 +769,16 @@
"trusted": "موثوق به", "trusted": "موثوق به",
"tx_commit_exception_no_dust_on_change": "يتم رفض المعاملة مع هذا المبلغ. باستخدام هذه العملات المعدنية ، يمكنك إرسال ${min} دون تغيير أو ${max} الذي يعيد التغيير.", "tx_commit_exception_no_dust_on_change": "يتم رفض المعاملة مع هذا المبلغ. باستخدام هذه العملات المعدنية ، يمكنك إرسال ${min} دون تغيير أو ${max} الذي يعيد التغيير.",
"tx_commit_failed": "فشل ارتكاب المعاملة. يرجى الاتصال بالدعم.", "tx_commit_failed": "فشل ارتكاب المعاملة. يرجى الاتصال بالدعم.",
"tx_invalid_input": "أنت تستخدم نوع الإدخال الخاطئ لهذا النوع من الدفع",
"tx_no_dust_exception": "يتم رفض المعاملة عن طريق إرسال مبلغ صغير جدًا. يرجى محاولة زيادة المبلغ.", "tx_no_dust_exception": "يتم رفض المعاملة عن طريق إرسال مبلغ صغير جدًا. يرجى محاولة زيادة المبلغ.",
"tx_not_enough_inputs_exception": "لا يكفي المدخلات المتاحة. الرجاء تحديد المزيد تحت التحكم في العملة", "tx_not_enough_inputs_exception": "لا يكفي المدخلات المتاحة. الرجاء تحديد المزيد تحت التحكم في العملة",
"tx_rejected_bip68_final": "تحتوي المعاملة على مدخلات غير مؤكدة وفشلت في استبدال الرسوم.",
"tx_rejected_dust_change": "المعاملة التي يتم رفضها بموجب قواعد الشبكة ، ومبلغ التغيير المنخفض (الغبار). حاول إرسال كل أو تقليل المبلغ.", "tx_rejected_dust_change": "المعاملة التي يتم رفضها بموجب قواعد الشبكة ، ومبلغ التغيير المنخفض (الغبار). حاول إرسال كل أو تقليل المبلغ.",
"tx_rejected_dust_output": "المعاملة التي يتم رفضها بموجب قواعد الشبكة ، وكمية الإخراج المنخفض (الغبار). يرجى زيادة المبلغ.", "tx_rejected_dust_output": "المعاملة التي يتم رفضها بموجب قواعد الشبكة ، وكمية الإخراج المنخفض (الغبار). يرجى زيادة المبلغ.",
"tx_rejected_dust_output_send_all": "المعاملة التي يتم رفضها بموجب قواعد الشبكة ، وكمية الإخراج المنخفض (الغبار). يرجى التحقق من رصيد العملات المعدنية المحددة تحت التحكم في العملة.", "tx_rejected_dust_output_send_all": "المعاملة التي يتم رفضها بموجب قواعد الشبكة ، وكمية الإخراج المنخفض (الغبار). يرجى التحقق من رصيد العملات المعدنية المحددة تحت التحكم في العملة.",
"tx_rejected_vout_negative": "لا يوجد ما يكفي من الرصيد لدفع رسوم هذه الصفقة. يرجى التحقق من رصيد العملات المعدنية تحت السيطرة على العملة.", "tx_rejected_vout_negative": "لا يوجد ما يكفي من الرصيد لدفع رسوم هذه الصفقة. يرجى التحقق من رصيد العملات المعدنية تحت السيطرة على العملة.",
"tx_wrong_balance_exception": "ليس لديك ما يكفي من ${currency} لإرسال هذا المبلغ.", "tx_wrong_balance_exception": "ليس لديك ما يكفي من ${currency} لإرسال هذا المبلغ.",
"tx_wrong_balance_with_amount_exception": "ليس لديك ما يكفي ${currency} لإرسال المبلغ الإجمالي ${amount}",
"tx_zero_fee_exception": "لا يمكن إرسال معاملة مع 0 رسوم. حاول زيادة المعدل أو التحقق من اتصالك للحصول على أحدث التقديرات.", "tx_zero_fee_exception": "لا يمكن إرسال معاملة مع 0 رسوم. حاول زيادة المعدل أو التحقق من اتصالك للحصول على أحدث التقديرات.",
"unavailable_balance": "ﺮﻓﻮﺘﻣ ﺮﻴﻏ ﺪﻴﺻﺭ", "unavailable_balance": "ﺮﻓﻮﺘﻣ ﺮﻴﻏ ﺪﻴﺻﺭ",
"unavailable_balance_description": ".ﺎﻫﺪﻴﻤﺠﺗ ءﺎﻐﻟﺇ ﺭﺮﻘﺗ ﻰﺘﺣ ﺕﻼﻣﺎﻌﻤﻠﻟ ﻝﻮﺻﻮﻠﻟ ﺔﻠﺑﺎﻗ ﺮﻴﻏ ﺓﺪﻤﺠﻤﻟﺍ ﺓﺪﺻﺭﻷﺍ ﻞﻈﺗ ﺎﻤﻨﻴﺑ ،ﺎﻬﺑ ﺔﺻﺎﺨﻟﺍ ﺕﻼﻣﺎﻌﻤﻟﺍ ﻝﺎﻤﺘﻛﺍ ﺩﺮﺠﻤﺑ ﺔﺣﺎﺘﻣ ﺔﻠﻔﻘﻤﻟﺍ ﺓﺪﺻﺭﻷﺍ ﺢﺒﺼﺘﺳ .ﻚﺑ ﺔﺻﺎﺨﻟﺍ ﺕﻼﻤﻌﻟﺍ ﻲﻓ ﻢﻜﺤﺘﻟﺍ ﺕﺍﺩﺍﺪﻋﺇ ﻲﻓ ﻂﺸﻧ ﻞﻜﺸﺑ ﺎﻫﺪﻴﻤﺠﺘﺑ ﺖﻤﻗ", "unavailable_balance_description": ".ﺎﻫﺪﻴﻤﺠﺗ ءﺎﻐﻟﺇ ﺭﺮﻘﺗ ﻰﺘﺣ ﺕﻼﻣﺎﻌﻤﻠﻟ ﻝﻮﺻﻮﻠﻟ ﺔﻠﺑﺎﻗ ﺮﻴﻏ ﺓﺪﻤﺠﻤﻟﺍ ﺓﺪﺻﺭﻷﺍ ﻞﻈﺗ ﺎﻤﻨﻴﺑ ،ﺎﻬﺑ ﺔﺻﺎﺨﻟﺍ ﺕﻼﻣﺎﻌﻤﻟﺍ ﻝﺎﻤﺘﻛﺍ ﺩﺮﺠﻤﺑ ﺔﺣﺎﺘﻣ ﺔﻠﻔﻘﻤﻟﺍ ﺓﺪﺻﺭﻷﺍ ﺢﺒﺼﺘﺳ .ﻚﺑ ﺔﺻﺎﺨﻟﺍ ﺕﻼﻤﻌﻟﺍ ﻲﻓ ﻢﻜﺤﺘﻟﺍ ﺕﺍﺩﺍﺪﻋﺇ ﻲﻓ ﻂﺸﻧ ﻞﻜﺸﺑ ﺎﻫﺪﻴﻤﺠﺘﺑ ﺖﻤﻗ",
@ -809,6 +832,7 @@
"warning": "تحذير", "warning": "تحذير",
"welcome": "مرحبا بك في", "welcome": "مرحبا بك في",
"welcome_to_cakepay": "مرحبا بكم في Cake Pay!", "welcome_to_cakepay": "مرحبا بكم في Cake Pay!",
"what_is_silent_payments": "ما هي المدفوعات الصامتة؟",
"widgets_address": "عنوان", "widgets_address": "عنوان",
"widgets_or": "أو", "widgets_or": "أو",
"widgets_restore_from_blockheight": "استعادة من ارتفاع البلوك", "widgets_restore_from_blockheight": "استعادة من ارتفاع البلوك",

View file

@ -71,6 +71,7 @@
"backup": "Резервно копие", "backup": "Резервно копие",
"backup_file": "Резервно копие", "backup_file": "Резервно копие",
"backup_password": "Парола за възстановяване", "backup_password": "Парола за възстановяване",
"balance": "Баланс",
"balance_page": "Страница за баланс", "balance_page": "Страница за баланс",
"bill_amount": "Искана сума", "bill_amount": "Искана сума",
"billing_address_info": "Ако Ви попитат за билинг адрес, въведето своя адрес за доставка", "billing_address_info": "Ако Ви попитат за билинг адрес, въведето своя адрес за доставка",
@ -78,6 +79,7 @@
"bitcoin_dark_theme": "Тъмна тема за биткойн", "bitcoin_dark_theme": "Тъмна тема за биткойн",
"bitcoin_light_theme": "Лека биткойн тема", "bitcoin_light_theme": "Лека биткойн тема",
"bitcoin_payments_require_1_confirmation": "Плащанията с Bitcoin изискват потвърждение, което може да отнеме 20 минути или повече. Благодарим за търпението! Ще получите имейл, когато плащането е потвърдено.", "bitcoin_payments_require_1_confirmation": "Плащанията с Bitcoin изискват потвърждение, което може да отнеме 20 минути или повече. Благодарим за търпението! Ще получите имейл, когато плащането е потвърдено.",
"block_remaining": "1 блок останал",
"Blocks_remaining": "${status} оставащи блока", "Blocks_remaining": "${status} оставащи блока",
"bluetooth": "Bluetooth", "bluetooth": "Bluetooth",
"bright_theme": "Ярко", "bright_theme": "Ярко",
@ -139,6 +141,7 @@
"confirm_fee_deduction": "Потвърдете приспадането на таксите", "confirm_fee_deduction": "Потвърдете приспадането на таксите",
"confirm_fee_deduction_content": "Съгласни ли сте да приспадате таксата от продукцията?", "confirm_fee_deduction_content": "Съгласни ли сте да приспадате таксата от продукцията?",
"confirm_sending": "Потвърждаване на изпращането", "confirm_sending": "Потвърждаване на изпращането",
"confirm_silent_payments_switch_node": "Понастоящем се изисква да превключвате възлите за сканиране на мълчаливи плащания",
"confirmations": "потвърждения", "confirmations": "потвърждения",
"confirmed": "Потвърден баланс", "confirmed": "Потвърден баланс",
"confirmed_tx": "Потвърдено", "confirmed_tx": "Потвърдено",
@ -221,6 +224,7 @@
"electrum_address_disclaimer": "Нови адреси се генерират всеки път, когато използвате този, но и предишните продължават да работят", "electrum_address_disclaimer": "Нови адреси се генерират всеки път, когато използвате този, но и предишните продължават да работят",
"email_address": "Имейл адрес", "email_address": "Имейл адрес",
"enable_replace_by_fee": "Активиране на замяна по забрана", "enable_replace_by_fee": "Активиране на замяна по забрана",
"enable_silent_payments_scanning": "Активирайте безшумните плащания за сканиране",
"enabled": "Активирано", "enabled": "Активирано",
"enter_amount": "Въведете сума", "enter_amount": "Въведете сума",
"enter_backup_password": "Въведете парола за възстановяване", "enter_backup_password": "Въведете парола за възстановяване",
@ -278,6 +282,7 @@
"extracted_address_content": "Ще изпратите средства на \n${recipient_name}", "extracted_address_content": "Ще изпратите средства на \n${recipient_name}",
"failed_authentication": "Неуспешно удостоверяване. ${state_error}", "failed_authentication": "Неуспешно удостоверяване. ${state_error}",
"faq": "FAQ", "faq": "FAQ",
"features": "Характеристика",
"fetching": "Обработване", "fetching": "Обработване",
"fiat_api": "Fiat API", "fiat_api": "Fiat API",
"fiat_balance": "Фиат Баланс", "fiat_balance": "Фиат Баланс",
@ -531,6 +536,7 @@
"save_backup_password_alert": "Запазване на паролата за възстановяване", "save_backup_password_alert": "Запазване на паролата за възстановяване",
"save_to_downloads": "Запазване в Изтегляния", "save_to_downloads": "Запазване в Изтегляния",
"saved_the_trade_id": "Запазих trade ID-то", "saved_the_trade_id": "Запазих trade ID-то",
"scan_one_block": "Сканирайте един блок",
"scan_qr_code": "Сканирайте QR кода, за да получите адреса", "scan_qr_code": "Сканирайте QR кода, за да получите адреса",
"scan_qr_code_to_get_address": "Сканирайте QR кода, за да получите адреса", "scan_qr_code_to_get_address": "Сканирайте QR кода, за да получите адреса",
"scan_qr_on_device": "Сканирайте този QR код на друго устройство", "scan_qr_on_device": "Сканирайте този QR код на друго устройство",
@ -641,11 +647,22 @@
"sign_up": "Регистрация", "sign_up": "Регистрация",
"signTransaction": "Подпишете транзакция", "signTransaction": "Подпишете транзакция",
"signup_for_card_accept_terms": "Регистрайте се за картата и приемете условията.", "signup_for_card_accept_terms": "Регистрайте се за картата и приемете условията.",
"silent_payments": "Мълчаливи плащания",
"silent_payments_always_scan": "Задайте мълчаливи плащания винаги сканиране",
"silent_payments_disclaimer": "Новите адреси не са нови идентичности. Това е повторна употреба на съществуваща идентичност с различен етикет.",
"silent_payments_display_card": "Показване на безшумни плащания карта",
"silent_payments_scan_from_date": "Сканиране от дата",
"silent_payments_scan_from_date_or_blockheight": "Моля, въведете височината на блока, която искате да започнете да сканирате за входящи безшумни плащания, или вместо това използвайте датата. Можете да изберете дали портфейлът продължава да сканира всеки блок или проверява само определената височина.",
"silent_payments_scan_from_height": "Сканиране от височината на блока",
"silent_payments_scanned_tip": "Сканиран за съвет! (${tip})",
"silent_payments_scanning": "Безшумни плащания за сканиране",
"silent_payments_settings": "Настройки за безшумни плащания",
"slidable": "Плъзгащ се", "slidable": "Плъзгащ се",
"sort_by": "Сортирай по", "sort_by": "Сортирай по",
"spend_key_private": "Spend key (таен)", "spend_key_private": "Spend key (таен)",
"spend_key_public": "Spend key (публичен)", "spend_key_public": "Spend key (публичен)",
"status": "Статус: ", "status": "Статус: ",
"string_default": "По подразбиране",
"subaddress_title": "Лист от подадреси", "subaddress_title": "Лист от подадреси",
"subaddresses": "Подадреси", "subaddresses": "Подадреси",
"submit_request": "изпращане на заявка", "submit_request": "изпращане на заявка",
@ -670,10 +687,13 @@
"sync_status_starting_sync": "ЗАПОЧВАНЕ НА СИНХРОНИЗАЦИЯ", "sync_status_starting_sync": "ЗАПОЧВАНЕ НА СИНХРОНИЗАЦИЯ",
"sync_status_syncronized": "СИНХРОНИЗИРАНО", "sync_status_syncronized": "СИНХРОНИЗИРАНО",
"sync_status_syncronizing": "СИНХРОНИЗИРАНЕ", "sync_status_syncronizing": "СИНХРОНИЗИРАНЕ",
"sync_status_timed_out": "ВРЕМЕТО ИЗТЕЧЕ",
"sync_status_unsupported": "Неподдържан възел",
"syncing_wallet_alert_content": "Списъкът ви с баланс и транзакции може да не е пълен, докато в горната част не пише „СИНХРОНИЗИРАН“. Кликнете/докоснете, за да научите повече.", "syncing_wallet_alert_content": "Списъкът ви с баланс и транзакции може да не е пълен, докато в горната част не пише „СИНХРОНИЗИРАН“. Кликнете/докоснете, за да научите повече.",
"syncing_wallet_alert_title": "Вашият портфейл се синхронизира", "syncing_wallet_alert_title": "Вашият портфейл се синхронизира",
"template": "Шаблон", "template": "Шаблон",
"template_name": "Име на шаблон", "template_name": "Име на шаблон",
"testnet_coins_no_value": "Тестовите монети нямат стойност",
"third_intro_content": "Yats също живее извън Cake Wallet. Всеки адрес на портфейл може да бъде заменен с Yat!", "third_intro_content": "Yats също живее извън Cake Wallet. Всеки адрес на портфейл може да бъде заменен с Yat!",
"third_intro_title": "Yat добре се сработва с други", "third_intro_title": "Yat добре се сработва с други",
"thorchain_contract_address_not_supported": "Thorchain не подкрепя изпращането до адрес на договор", "thorchain_contract_address_not_supported": "Thorchain не подкрепя изпращането до адрес на договор",
@ -749,13 +769,16 @@
"trusted": "Надежден", "trusted": "Надежден",
"tx_commit_exception_no_dust_on_change": "Сделката се отхвърля с тази сума. С тези монети можете да изпратите ${min} без промяна или ${max}, която връща промяна.", "tx_commit_exception_no_dust_on_change": "Сделката се отхвърля с тази сума. С тези монети можете да изпратите ${min} без промяна или ${max}, която връща промяна.",
"tx_commit_failed": "Компетацията на транзакцията не успя. Моля, свържете се с поддръжката.", "tx_commit_failed": "Компетацията на транзакцията не успя. Моля, свържете се с поддръжката.",
"tx_invalid_input": "Използвате грешен тип вход за този тип плащане",
"tx_no_dust_exception": "Сделката се отхвърля чрез изпращане на сума твърде малка. Моля, опитайте да увеличите сумата.", "tx_no_dust_exception": "Сделката се отхвърля чрез изпращане на сума твърде малка. Моля, опитайте да увеличите сумата.",
"tx_not_enough_inputs_exception": "Няма достатъчно налични входове. Моля, изберете повече под контрол на монети", "tx_not_enough_inputs_exception": "Няма достатъчно налични входове. Моля, изберете повече под контрол на монети",
"tx_rejected_bip68_final": "Сделката има непотвърдени входове и не успя да се замени с такса.",
"tx_rejected_dust_change": "Транзакция, отхвърлена от мрежови правила, ниска сума на промяна (прах). Опитайте да изпратите всички или да намалите сумата.", "tx_rejected_dust_change": "Транзакция, отхвърлена от мрежови правила, ниска сума на промяна (прах). Опитайте да изпратите всички или да намалите сумата.",
"tx_rejected_dust_output": "Транзакция, отхвърлена от мрежови правила, ниска стойност на изхода (прах). Моля, увеличете сумата.", "tx_rejected_dust_output": "Транзакция, отхвърлена от мрежови правила, ниска стойност на изхода (прах). Моля, увеличете сумата.",
"tx_rejected_dust_output_send_all": "Транзакция, отхвърлена от мрежови правила, ниска стойност на изхода (прах). Моля, проверете баланса на монетите, избрани под контрол на монети.", "tx_rejected_dust_output_send_all": "Транзакция, отхвърлена от мрежови правила, ниска стойност на изхода (прах). Моля, проверете баланса на монетите, избрани под контрол на монети.",
"tx_rejected_vout_negative": "Няма достатъчно баланс, за да платите за таксите на тази транзакция. Моля, проверете баланса на монетите под контрол на монетите.", "tx_rejected_vout_negative": "Няма достатъчно баланс, за да платите за таксите на тази транзакция. Моля, проверете баланса на монетите под контрол на монетите.",
"tx_wrong_balance_exception": "Нямате достатъчно ${currency}, за да изпратите тази сума.", "tx_wrong_balance_exception": "Нямате достатъчно ${currency}, за да изпратите тази сума.",
"tx_wrong_balance_with_amount_exception": "Нямате достатъчно ${currency} За да изпратите общата сума на ${amount}",
"tx_zero_fee_exception": "Не може да изпраща транзакция с 0 такса. Опитайте да увеличите скоростта или да проверите връзката си за най -новите оценки.", "tx_zero_fee_exception": "Не може да изпраща транзакция с 0 такса. Опитайте да увеличите скоростта или да проверите връзката си за най -новите оценки.",
"unavailable_balance": "Неналично салдо", "unavailable_balance": "Неналично салдо",
"unavailable_balance_description": "Неналично салдо: Тази обща сума включва средства, които са заключени в чакащи транзакции и тези, които сте замразили активно в настройките за контрол на монетите. Заключените баланси ще станат достъпни, след като съответните им транзакции бъдат завършени, докато замразените баланси остават недостъпни за транзакции, докато не решите да ги размразите.", "unavailable_balance_description": "Неналично салдо: Тази обща сума включва средства, които са заключени в чакащи транзакции и тези, които сте замразили активно в настройките за контрол на монетите. Заключените баланси ще станат достъпни, след като съответните им транзакции бъдат завършени, докато замразените баланси остават недостъпни за транзакции, докато не решите да ги размразите.",
@ -809,6 +832,7 @@
"warning": "Внимание", "warning": "Внимание",
"welcome": "Добре дошли в", "welcome": "Добре дошли в",
"welcome_to_cakepay": "Добре дошли в Cake Pay!", "welcome_to_cakepay": "Добре дошли в Cake Pay!",
"what_is_silent_payments": "Какво са мълчаливи плащания?",
"widgets_address": "Адрес", "widgets_address": "Адрес",
"widgets_or": "или", "widgets_or": "или",
"widgets_restore_from_blockheight": "Възстановяване от blockheight", "widgets_restore_from_blockheight": "Възстановяване от blockheight",

View file

@ -71,6 +71,7 @@
"backup": "Záloha", "backup": "Záloha",
"backup_file": "Soubor se zálohou", "backup_file": "Soubor se zálohou",
"backup_password": "Heslo pro zálohy", "backup_password": "Heslo pro zálohy",
"balance": "Zůstatek",
"balance_page": "Stránka zůstatku", "balance_page": "Stránka zůstatku",
"bill_amount": "Účtovaná částka", "bill_amount": "Účtovaná částka",
"billing_address_info": "Při dotazu na fakturační adresu, zadejte svou doručovací adresu", "billing_address_info": "Při dotazu na fakturační adresu, zadejte svou doručovací adresu",
@ -78,6 +79,7 @@
"bitcoin_dark_theme": "Tmavé téma bitcoinů", "bitcoin_dark_theme": "Tmavé téma bitcoinů",
"bitcoin_light_theme": "Světlé téma bitcoinů", "bitcoin_light_theme": "Světlé téma bitcoinů",
"bitcoin_payments_require_1_confirmation": "U plateb Bitcoinem je vyžadováno alespoň 1 potvrzení, což může trvat 20 minut i déle. Děkujeme za vaši trpělivost! Až bude platba potvrzena, budete informováni e-mailem.", "bitcoin_payments_require_1_confirmation": "U plateb Bitcoinem je vyžadováno alespoň 1 potvrzení, což může trvat 20 minut i déle. Děkujeme za vaši trpělivost! Až bude platba potvrzena, budete informováni e-mailem.",
"block_remaining": "1 blok zbývající",
"Blocks_remaining": "Zbývá ${status} bloků", "Blocks_remaining": "Zbývá ${status} bloků",
"bluetooth": "Bluetooth", "bluetooth": "Bluetooth",
"bright_theme": "Jasný", "bright_theme": "Jasný",
@ -139,6 +141,7 @@
"confirm_fee_deduction": "Potvrďte odpočet poplatků", "confirm_fee_deduction": "Potvrďte odpočet poplatků",
"confirm_fee_deduction_content": "Souhlasíte s odečtením poplatku z výstupu?", "confirm_fee_deduction_content": "Souhlasíte s odečtením poplatku z výstupu?",
"confirm_sending": "Potvrdit odeslání", "confirm_sending": "Potvrdit odeslání",
"confirm_silent_payments_switch_node": "V současné době je nutné přepínat uzly pro skenování tichých plateb",
"confirmations": "Potvrzení", "confirmations": "Potvrzení",
"confirmed": "Potvrzený zůstatek", "confirmed": "Potvrzený zůstatek",
"confirmed_tx": "Potvrzeno", "confirmed_tx": "Potvrzeno",
@ -221,6 +224,7 @@
"electrum_address_disclaimer": "Po každém použití je generována nová adresa, ale předchozí adresy také stále fungují", "electrum_address_disclaimer": "Po každém použití je generována nová adresa, ale předchozí adresy také stále fungují",
"email_address": "E-mailová adresa", "email_address": "E-mailová adresa",
"enable_replace_by_fee": "Povolit výměnu podle poplatku", "enable_replace_by_fee": "Povolit výměnu podle poplatku",
"enable_silent_payments_scanning": "Povolte skenování tichých plateb",
"enabled": "Povoleno", "enabled": "Povoleno",
"enter_amount": "Zadejte částku", "enter_amount": "Zadejte částku",
"enter_backup_password": "Zde zadejte své heslo pro zálohy", "enter_backup_password": "Zde zadejte své heslo pro zálohy",
@ -278,6 +282,7 @@
"extracted_address_content": "Prostředky budete posílat na\n${recipient_name}", "extracted_address_content": "Prostředky budete posílat na\n${recipient_name}",
"failed_authentication": "Ověřování selhalo. ${state_error}", "failed_authentication": "Ověřování selhalo. ${state_error}",
"faq": "FAQ", "faq": "FAQ",
"features": "Funkce",
"fetching": "Načítá se", "fetching": "Načítá se",
"fiat_api": "Fiat API", "fiat_api": "Fiat API",
"fiat_balance": "Fiat Balance", "fiat_balance": "Fiat Balance",
@ -531,6 +536,7 @@
"save_backup_password_alert": "Uložit heslo pro zálohy", "save_backup_password_alert": "Uložit heslo pro zálohy",
"save_to_downloads": "Uložit do Stažených souborů", "save_to_downloads": "Uložit do Stažených souborů",
"saved_the_trade_id": "Uložil jsem si ID transakce (trade ID)", "saved_the_trade_id": "Uložil jsem si ID transakce (trade ID)",
"scan_one_block": "Prohledejte jeden blok",
"scan_qr_code": "Naskenujte QR kód pro získání adresy", "scan_qr_code": "Naskenujte QR kód pro získání adresy",
"scan_qr_code_to_get_address": "Prohledejte QR kód a získejte adresu", "scan_qr_code_to_get_address": "Prohledejte QR kód a získejte adresu",
"scan_qr_on_device": "Naskenujte tento QR kód na jiném zařízení", "scan_qr_on_device": "Naskenujte tento QR kód na jiném zařízení",
@ -641,11 +647,22 @@
"sign_up": "Registrovat se", "sign_up": "Registrovat se",
"signTransaction": "Podepsat transakci", "signTransaction": "Podepsat transakci",
"signup_for_card_accept_terms": "Zaregistrujte se pro kartu a souhlaste s podmínkami.", "signup_for_card_accept_terms": "Zaregistrujte se pro kartu a souhlaste s podmínkami.",
"silent_payments": "Tiché platby",
"silent_payments_always_scan": "Nastavit tiché platby vždy skenování",
"silent_payments_disclaimer": "Nové adresy nejsou nové identity. Je to opětovné použití existující identity s jiným štítkem.",
"silent_payments_display_card": "Zobrazit kartu Silent Payments",
"silent_payments_scan_from_date": "Skenovat od data",
"silent_payments_scan_from_date_or_blockheight": "Zadejte výšku bloku, kterou chcete začít skenovat, zda jsou přicházející tiché platby, nebo místo toho použijte datum. Můžete si vybrat, zda peněženka pokračuje v skenování každého bloku nebo zkontroluje pouze zadanou výšku.",
"silent_payments_scan_from_height": "Skenování z výšky bloku",
"silent_payments_scanned_tip": "Naskenované na tip! (${tip})",
"silent_payments_scanning": "Skenování tichých plateb",
"silent_payments_settings": "Nastavení tichých plateb",
"slidable": "Posuvné", "slidable": "Posuvné",
"sort_by": "Seřazeno podle", "sort_by": "Seřazeno podle",
"spend_key_private": "Klíč pro platby (soukromý)", "spend_key_private": "Klíč pro platby (soukromý)",
"spend_key_public": "Klíč pro platby (veřejný)", "spend_key_public": "Klíč pro platby (veřejný)",
"status": "Status: ", "status": "Status: ",
"string_default": "Výchozí",
"subaddress_title": "Seznam subadres", "subaddress_title": "Seznam subadres",
"subaddresses": "Subadresy", "subaddresses": "Subadresy",
"submit_request": "odeslat požadavek", "submit_request": "odeslat požadavek",
@ -670,10 +687,13 @@
"sync_status_starting_sync": "SPOUŠTĚNÍ SYNCHRONIZACE", "sync_status_starting_sync": "SPOUŠTĚNÍ SYNCHRONIZACE",
"sync_status_syncronized": "SYNCHRONIZOVÁNO", "sync_status_syncronized": "SYNCHRONIZOVÁNO",
"sync_status_syncronizing": "SYNCHRONIZUJI", "sync_status_syncronizing": "SYNCHRONIZUJI",
"sync_status_timed_out": "ČAS VYPRŠEL",
"sync_status_unsupported": "Nepodporovaný uzel",
"syncing_wallet_alert_content": "Váš seznam zůstatků a transakcí nemusí být úplný, dokud nebude nahoře uvedeno „SYNCHRONIZOVANÉ“. Kliknutím/klepnutím se dozvíte více.", "syncing_wallet_alert_content": "Váš seznam zůstatků a transakcí nemusí být úplný, dokud nebude nahoře uvedeno „SYNCHRONIZOVANÉ“. Kliknutím/klepnutím se dozvíte více.",
"syncing_wallet_alert_title": "Vaše peněženka se synchronizuje", "syncing_wallet_alert_title": "Vaše peněženka se synchronizuje",
"template": "Šablona", "template": "Šablona",
"template_name": "Název šablony", "template_name": "Název šablony",
"testnet_coins_no_value": "Mince TestNet nemají žádnou hodnotu",
"third_intro_content": "Yat existuje i mimo Cake Wallet. Jakákoliv adresa peněženky na světě může být nahrazena Yatem!", "third_intro_content": "Yat existuje i mimo Cake Wallet. Jakákoliv adresa peněženky na světě může být nahrazena Yatem!",
"third_intro_title": "Yat dobře spolupracuje s ostatními", "third_intro_title": "Yat dobře spolupracuje s ostatními",
"thorchain_contract_address_not_supported": "Thorchain nepodporuje odeslání na adresu smlouvy", "thorchain_contract_address_not_supported": "Thorchain nepodporuje odeslání na adresu smlouvy",
@ -749,13 +769,16 @@
"trusted": "Důvěřovat", "trusted": "Důvěřovat",
"tx_commit_exception_no_dust_on_change": "Transakce je zamítnuta s touto částkou. S těmito mincemi můžete odeslat ${min} bez změny nebo ${max}, které se vrátí změna.", "tx_commit_exception_no_dust_on_change": "Transakce je zamítnuta s touto částkou. S těmito mincemi můžete odeslat ${min} bez změny nebo ${max}, které se vrátí změna.",
"tx_commit_failed": "Transakce COMPORT selhala. Kontaktujte prosím podporu.", "tx_commit_failed": "Transakce COMPORT selhala. Kontaktujte prosím podporu.",
"tx_invalid_input": "Pro tento typ platby používáte nesprávný typ vstupu",
"tx_no_dust_exception": "Transakce je zamítnuta odesláním příliš malé. Zkuste prosím zvýšit částku.", "tx_no_dust_exception": "Transakce je zamítnuta odesláním příliš malé. Zkuste prosím zvýšit částku.",
"tx_not_enough_inputs_exception": "Není k dispozici dostatek vstupů. Vyberte prosím více pod kontrolou mincí", "tx_not_enough_inputs_exception": "Není k dispozici dostatek vstupů. Vyberte prosím více pod kontrolou mincí",
"tx_rejected_bip68_final": "Transakce má nepotvrzené vstupy a nepodařilo se nahradit poplatkem.",
"tx_rejected_dust_change": "Transakce zamítnuta podle síťových pravidel, množství nízké změny (prach). Zkuste odeslat vše nebo snížit částku.", "tx_rejected_dust_change": "Transakce zamítnuta podle síťových pravidel, množství nízké změny (prach). Zkuste odeslat vše nebo snížit částku.",
"tx_rejected_dust_output": "Transakce zamítnuta síťovými pravidly, nízkým množstvím výstupu (prach). Zvyšte prosím částku.", "tx_rejected_dust_output": "Transakce zamítnuta síťovými pravidly, nízkým množstvím výstupu (prach). Zvyšte prosím částku.",
"tx_rejected_dust_output_send_all": "Transakce zamítnuta síťovými pravidly, nízkým množstvím výstupu (prach). Zkontrolujte prosím zůstatek mincí vybraných pod kontrolou mincí.", "tx_rejected_dust_output_send_all": "Transakce zamítnuta síťovými pravidly, nízkým množstvím výstupu (prach). Zkontrolujte prosím zůstatek mincí vybraných pod kontrolou mincí.",
"tx_rejected_vout_negative": "Nedostatek zůstatek na zaplacení poplatků za tuto transakci. Zkontrolujte prosím zůstatek mincí pod kontrolou mincí.", "tx_rejected_vout_negative": "Nedostatek zůstatek na zaplacení poplatků za tuto transakci. Zkontrolujte prosím zůstatek mincí pod kontrolou mincí.",
"tx_wrong_balance_exception": "Nemáte dost ${currency} pro odeslání této částky.", "tx_wrong_balance_exception": "Nemáte dost ${currency} pro odeslání této částky.",
"tx_wrong_balance_with_amount_exception": "Nemáte dost ${currency} na odeslání celkové částky ${amount}",
"tx_zero_fee_exception": "Nelze odeslat transakci s 0 poplatkem. Zkuste zvýšit sazbu nebo zkontrolovat připojení pro nejnovější odhady.", "tx_zero_fee_exception": "Nelze odeslat transakci s 0 poplatkem. Zkuste zvýšit sazbu nebo zkontrolovat připojení pro nejnovější odhady.",
"unavailable_balance": "Nedostupný zůstatek", "unavailable_balance": "Nedostupný zůstatek",
"unavailable_balance_description": "Nedostupný zůstatek: Tento součet zahrnuje prostředky, které jsou uzamčeny v nevyřízených transakcích a ty, které jste aktivně zmrazili v nastavení kontroly mincí. Uzamčené zůstatky budou k dispozici po dokončení příslušných transakcí, zatímco zmrazené zůstatky zůstanou pro transakce nepřístupné, dokud se nerozhodnete je uvolnit.", "unavailable_balance_description": "Nedostupný zůstatek: Tento součet zahrnuje prostředky, které jsou uzamčeny v nevyřízených transakcích a ty, které jste aktivně zmrazili v nastavení kontroly mincí. Uzamčené zůstatky budou k dispozici po dokončení příslušných transakcí, zatímco zmrazené zůstatky zůstanou pro transakce nepřístupné, dokud se nerozhodnete je uvolnit.",
@ -809,6 +832,7 @@
"warning": "Varování", "warning": "Varování",
"welcome": "Vítejte v", "welcome": "Vítejte v",
"welcome_to_cakepay": "Vítejte v Cake Pay!", "welcome_to_cakepay": "Vítejte v Cake Pay!",
"what_is_silent_payments": "Co jsou tiché platby?",
"widgets_address": "Adresa", "widgets_address": "Adresa",
"widgets_or": "nebo", "widgets_or": "nebo",
"widgets_restore_from_blockheight": "Obnovit z výšky bloku", "widgets_restore_from_blockheight": "Obnovit z výšky bloku",

View file

@ -71,6 +71,7 @@
"backup": "Sicherung", "backup": "Sicherung",
"backup_file": "Sicherungsdatei", "backup_file": "Sicherungsdatei",
"backup_password": "Passwort sichern", "backup_password": "Passwort sichern",
"balance": "Gleichgewicht",
"balance_page": "Balance-Seite", "balance_page": "Balance-Seite",
"bill_amount": "Rechnungsbetrag", "bill_amount": "Rechnungsbetrag",
"billing_address_info": "Wenn Sie nach einer Rechnungsadresse gefragt werden, geben Sie bitte Ihre Lieferadresse an", "billing_address_info": "Wenn Sie nach einer Rechnungsadresse gefragt werden, geben Sie bitte Ihre Lieferadresse an",
@ -78,6 +79,7 @@
"bitcoin_dark_theme": "Dunkles Bitcoin-Thema", "bitcoin_dark_theme": "Dunkles Bitcoin-Thema",
"bitcoin_light_theme": "Bitcoin Light-Thema", "bitcoin_light_theme": "Bitcoin Light-Thema",
"bitcoin_payments_require_1_confirmation": "Bitcoin-Zahlungen erfordern 1 Bestätigung, was 20 Minuten oder länger dauern kann. Danke für Ihre Geduld! Sie erhalten eine E-Mail, wenn die Zahlung bestätigt ist.", "bitcoin_payments_require_1_confirmation": "Bitcoin-Zahlungen erfordern 1 Bestätigung, was 20 Minuten oder länger dauern kann. Danke für Ihre Geduld! Sie erhalten eine E-Mail, wenn die Zahlung bestätigt ist.",
"block_remaining": "1 Block verbleibend",
"Blocks_remaining": "${status} verbleibende Blöcke", "Blocks_remaining": "${status} verbleibende Blöcke",
"bluetooth": "Bluetooth", "bluetooth": "Bluetooth",
"bright_theme": "Strahlend hell", "bright_theme": "Strahlend hell",
@ -139,6 +141,7 @@
"confirm_fee_deduction": "Gebührenabzug bestätigen", "confirm_fee_deduction": "Gebührenabzug bestätigen",
"confirm_fee_deduction_content": "Stimmen Sie zu, die Gebühr von der Ausgabe abzuziehen?", "confirm_fee_deduction_content": "Stimmen Sie zu, die Gebühr von der Ausgabe abzuziehen?",
"confirm_sending": "Senden bestätigen", "confirm_sending": "Senden bestätigen",
"confirm_silent_payments_switch_node": "Derzeit ist es erforderlich, Knoten zu wechseln, um stille Zahlungen zu scannen",
"confirmations": "Bestätigungen", "confirmations": "Bestätigungen",
"confirmed": "Bestätigter Saldo", "confirmed": "Bestätigter Saldo",
"confirmed_tx": "Bestätigt", "confirmed_tx": "Bestätigt",
@ -221,6 +224,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_replace_by_fee": "Aktivieren Sie Ersatz für Fee", "enable_replace_by_fee": "Aktivieren Sie Ersatz für Fee",
"enable_silent_payments_scanning": "Aktivieren Sie stille Zahlungen Scannen",
"enabled": "Ermöglicht", "enabled": "Ermöglicht",
"enter_amount": "Betrag eingeben", "enter_amount": "Betrag eingeben",
"enter_backup_password": "Sicherungskennwort hier eingeben", "enter_backup_password": "Sicherungskennwort hier eingeben",
@ -278,6 +282,7 @@
"extracted_address_content": "Sie senden Geld an\n${recipient_name}", "extracted_address_content": "Sie senden Geld an\n${recipient_name}",
"failed_authentication": "Authentifizierung fehlgeschlagen. ${state_error}", "failed_authentication": "Authentifizierung fehlgeschlagen. ${state_error}",
"faq": "Häufig gestellte Fragen", "faq": "Häufig gestellte Fragen",
"features": "Merkmale",
"fetching": "Frage ab", "fetching": "Frage ab",
"fiat_api": "Fiat API", "fiat_api": "Fiat API",
"fiat_balance": "Fiat Balance", "fiat_balance": "Fiat Balance",
@ -532,6 +537,7 @@
"save_backup_password_alert": "Sicherungskennwort speichern", "save_backup_password_alert": "Sicherungskennwort speichern",
"save_to_downloads": "Unter „Downloads“ speichern", "save_to_downloads": "Unter „Downloads“ speichern",
"saved_the_trade_id": "Ich habe die Handels-ID gespeichert", "saved_the_trade_id": "Ich habe die Handels-ID gespeichert",
"scan_one_block": "Einen Block scannen",
"scan_qr_code": "QR-Code scannen", "scan_qr_code": "QR-Code scannen",
"scan_qr_code_to_get_address": "Scannen Sie den QR-Code, um die Adresse zu erhalten", "scan_qr_code_to_get_address": "Scannen Sie den QR-Code, um die Adresse zu erhalten",
"scan_qr_on_device": "Scannen Sie diesen QR-Code auf einem anderen Gerät", "scan_qr_on_device": "Scannen Sie diesen QR-Code auf einem anderen Gerät",
@ -642,11 +648,22 @@
"sign_up": "Anmelden", "sign_up": "Anmelden",
"signTransaction": "Transaktion unterzeichnen", "signTransaction": "Transaktion unterzeichnen",
"signup_for_card_accept_terms": "Melden Sie sich für die Karte an und akzeptieren Sie die Bedingungen.", "signup_for_card_accept_terms": "Melden Sie sich für die Karte an und akzeptieren Sie die Bedingungen.",
"silent_payments": "Stille Zahlungen",
"silent_payments_always_scan": "Setzen Sie stille Zahlungen immer scannen",
"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_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_height": "Scan aus der Blockhöhe scannen",
"silent_payments_scanned_tip": "Gescannt zum Trinkgeld! (${tip})",
"silent_payments_scanning": "Stille Zahlungen scannen",
"silent_payments_settings": "Einstellungen für stille Zahlungen",
"slidable": "Verschiebbar", "slidable": "Verschiebbar",
"sort_by": "Sortiere nach", "sort_by": "Sortiere nach",
"spend_key_private": "Spend Key (geheim)", "spend_key_private": "Spend Key (geheim)",
"spend_key_public": "Spend Key (öffentlich)", "spend_key_public": "Spend Key (öffentlich)",
"status": "Status: ", "status": "Status: ",
"string_default": "Standard",
"subaddress_title": "Unteradressenliste", "subaddress_title": "Unteradressenliste",
"subaddresses": "Unteradressen", "subaddresses": "Unteradressen",
"submit_request": "Eine Anfrage stellen", "submit_request": "Eine Anfrage stellen",
@ -671,10 +688,13 @@
"sync_status_starting_sync": "STARTE SYNCHRONISIERUNG", "sync_status_starting_sync": "STARTE SYNCHRONISIERUNG",
"sync_status_syncronized": "SYNCHRONISIERT", "sync_status_syncronized": "SYNCHRONISIERT",
"sync_status_syncronizing": "SYNCHRONISIERE", "sync_status_syncronizing": "SYNCHRONISIERE",
"sync_status_timed_out": "Zeitlich abgestimmt",
"sync_status_unsupported": "Nicht unterstützter Knoten",
"syncing_wallet_alert_content": "Ihr Kontostand und Ihre Transaktionsliste sind möglicherweise erst vollständig, wenn oben „SYNCHRONISIERT“ steht. Klicken/tippen Sie, um mehr zu erfahren.", "syncing_wallet_alert_content": "Ihr Kontostand und Ihre Transaktionsliste sind möglicherweise erst vollständig, wenn oben „SYNCHRONISIERT“ steht. Klicken/tippen Sie, um mehr zu erfahren.",
"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",
"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",
@ -750,13 +770,16 @@
"trusted": "Vertrauenswürdige", "trusted": "Vertrauenswürdige",
"tx_commit_exception_no_dust_on_change": "Die Transaktion wird diesen Betrag abgelehnt. Mit diesen Münzen können Sie ${min} ohne Veränderung oder ${max} senden, die Änderungen zurückgeben.", "tx_commit_exception_no_dust_on_change": "Die Transaktion wird diesen Betrag abgelehnt. Mit diesen Münzen können Sie ${min} ohne Veränderung oder ${max} senden, die Änderungen zurückgeben.",
"tx_commit_failed": "Transaktionsausschüsse ist fehlgeschlagen. Bitte wenden Sie sich an Support.", "tx_commit_failed": "Transaktionsausschüsse ist fehlgeschlagen. Bitte wenden Sie sich an Support.",
"tx_invalid_input": "Sie verwenden den falschen Eingangstyp für diese Art von Zahlung",
"tx_no_dust_exception": "Die Transaktion wird abgelehnt, indem eine Menge zu klein gesendet wird. Bitte versuchen Sie, die Menge zu erhöhen.", "tx_no_dust_exception": "Die Transaktion wird abgelehnt, indem eine Menge zu klein gesendet wird. Bitte versuchen Sie, die Menge zu erhöhen.",
"tx_not_enough_inputs_exception": "Nicht genügend Eingänge verfügbar. Bitte wählen Sie mehr unter Münzkontrolle aus", "tx_not_enough_inputs_exception": "Nicht genügend Eingänge verfügbar. Bitte wählen Sie mehr unter Münzkontrolle aus",
"tx_rejected_bip68_final": "Die Transaktion hat unbestätigte Inputs und konnte nicht durch Gebühr ersetzt werden.",
"tx_rejected_dust_change": "Transaktion abgelehnt durch Netzwerkregeln, niedriger Änderungsbetrag (Staub). Versuchen Sie, alle zu senden oder die Menge zu reduzieren.", "tx_rejected_dust_change": "Transaktion abgelehnt durch Netzwerkregeln, niedriger Änderungsbetrag (Staub). Versuchen Sie, alle zu senden oder die Menge zu reduzieren.",
"tx_rejected_dust_output": "Transaktion durch Netzwerkregeln, niedriger Ausgangsmenge (Staub) abgelehnt. Bitte erhöhen Sie den Betrag.", "tx_rejected_dust_output": "Transaktion durch Netzwerkregeln, niedriger Ausgangsmenge (Staub) abgelehnt. Bitte erhöhen Sie den Betrag.",
"tx_rejected_dust_output_send_all": "Transaktion durch Netzwerkregeln, niedriger Ausgangsmenge (Staub) abgelehnt. Bitte überprüfen Sie den Gleichgewicht der unter Münzkontrolle ausgewählten Münzen.", "tx_rejected_dust_output_send_all": "Transaktion durch Netzwerkregeln, niedriger Ausgangsmenge (Staub) abgelehnt. Bitte überprüfen Sie den Gleichgewicht der unter Münzkontrolle ausgewählten Münzen.",
"tx_rejected_vout_negative": "Nicht genug Guthaben, um die Gebühren dieser Transaktion zu bezahlen. Bitte überprüfen Sie den Restbetrag der Münzen unter Münzkontrolle.", "tx_rejected_vout_negative": "Nicht genug Guthaben, um die Gebühren dieser Transaktion zu bezahlen. Bitte überprüfen Sie den Restbetrag der Münzen unter Münzkontrolle.",
"tx_wrong_balance_exception": "Sie haben nicht genug ${currency}, um diesen Betrag zu senden.", "tx_wrong_balance_exception": "Sie haben nicht genug ${currency}, um diesen Betrag zu senden.",
"tx_wrong_balance_with_amount_exception": "Sie haben nicht genug ${currency}, um die Gesamtmenge von ${amount} zu senden",
"tx_zero_fee_exception": "Transaktion kann nicht mit 0 Gebühren gesendet werden. Versuchen Sie, die Rate zu erhöhen oder Ihre Verbindung auf die neuesten Schätzungen zu überprüfen.", "tx_zero_fee_exception": "Transaktion kann nicht mit 0 Gebühren gesendet werden. Versuchen Sie, die Rate zu erhöhen oder Ihre Verbindung auf die neuesten Schätzungen zu überprüfen.",
"unavailable_balance": "Nicht verfügbares Guthaben", "unavailable_balance": "Nicht verfügbares Guthaben",
"unavailable_balance_description": "Nicht verfügbares Guthaben: Diese Summe umfasst Gelder, die in ausstehenden Transaktionen gesperrt sind, und solche, die Sie in Ihren Münzkontrolleinstellungen aktiv eingefroren haben. Gesperrte Guthaben werden verfügbar, sobald die entsprechenden Transaktionen abgeschlossen sind, während eingefrorene Guthaben für Transaktionen nicht zugänglich bleiben, bis Sie sich dazu entschließen, sie wieder freizugeben.", "unavailable_balance_description": "Nicht verfügbares Guthaben: Diese Summe umfasst Gelder, die in ausstehenden Transaktionen gesperrt sind, und solche, die Sie in Ihren Münzkontrolleinstellungen aktiv eingefroren haben. Gesperrte Guthaben werden verfügbar, sobald die entsprechenden Transaktionen abgeschlossen sind, während eingefrorene Guthaben für Transaktionen nicht zugänglich bleiben, bis Sie sich dazu entschließen, sie wieder freizugeben.",
@ -812,6 +835,7 @@
"warning": "Warnung", "warning": "Warnung",
"welcome": "Willkommen bei", "welcome": "Willkommen bei",
"welcome_to_cakepay": "Willkommen bei Cake Pay!", "welcome_to_cakepay": "Willkommen bei Cake Pay!",
"what_is_silent_payments": "Was sind stille Zahlungen?",
"widgets_address": "Adresse", "widgets_address": "Adresse",
"widgets_or": "oder", "widgets_or": "oder",
"widgets_restore_from_blockheight": "Ab Blockhöhe wiederherstellen", "widgets_restore_from_blockheight": "Ab Blockhöhe wiederherstellen",

View file

@ -71,6 +71,7 @@
"backup": "Backup", "backup": "Backup",
"backup_file": "Backup file", "backup_file": "Backup file",
"backup_password": "Backup password", "backup_password": "Backup password",
"balance": "Balance",
"balance_page": "Balance Page", "balance_page": "Balance Page",
"bill_amount": "Bill Amount", "bill_amount": "Bill Amount",
"billing_address_info": "If asked for a billing address, provide your shipping address", "billing_address_info": "If asked for a billing address, provide your shipping address",
@ -78,6 +79,7 @@
"bitcoin_dark_theme": "Bitcoin Dark Theme", "bitcoin_dark_theme": "Bitcoin Dark Theme",
"bitcoin_light_theme": "Bitcoin Light Theme", "bitcoin_light_theme": "Bitcoin Light Theme",
"bitcoin_payments_require_1_confirmation": "Bitcoin payments require 1 confirmation, which can take 20 minutes or longer. Thanks for your patience! You will be emailed when the payment is confirmed.", "bitcoin_payments_require_1_confirmation": "Bitcoin payments require 1 confirmation, which can take 20 minutes or longer. Thanks for your patience! You will be emailed when the payment is confirmed.",
"block_remaining": "1 Block Remaining",
"Blocks_remaining": "${status} Blocks Remaining", "Blocks_remaining": "${status} Blocks Remaining",
"bluetooth": "Bluetooth", "bluetooth": "Bluetooth",
"bright_theme": "Bright", "bright_theme": "Bright",
@ -139,6 +141,7 @@
"confirm_fee_deduction": "Confirm Fee Deduction", "confirm_fee_deduction": "Confirm Fee Deduction",
"confirm_fee_deduction_content": "Do you agree to deduct the fee from the output?", "confirm_fee_deduction_content": "Do you agree to deduct the fee from the output?",
"confirm_sending": "Confirm sending", "confirm_sending": "Confirm sending",
"confirm_silent_payments_switch_node": "Currently it is required to switch nodes to scan silent payments",
"confirmations": "Confirmations", "confirmations": "Confirmations",
"confirmed": "Confirmed Balance", "confirmed": "Confirmed Balance",
"confirmed_tx": "Confirmed", "confirmed_tx": "Confirmed",
@ -221,6 +224,7 @@
"electrum_address_disclaimer": "We generate new addresses each time you use one, but previous addresses continue to work", "electrum_address_disclaimer": "We generate new addresses each time you use one, but previous addresses continue to work",
"email_address": "Email Address", "email_address": "Email Address",
"enable_replace_by_fee": "Enable Replace-By-Fee", "enable_replace_by_fee": "Enable Replace-By-Fee",
"enable_silent_payments_scanning": "Enable silent payments scanning",
"enabled": "Enabled", "enabled": "Enabled",
"enter_amount": "Enter Amount", "enter_amount": "Enter Amount",
"enter_backup_password": "Enter backup password here", "enter_backup_password": "Enter backup password here",
@ -278,6 +282,7 @@
"extracted_address_content": "You will be sending funds to\n${recipient_name}", "extracted_address_content": "You will be sending funds to\n${recipient_name}",
"failed_authentication": "Failed authentication. ${state_error}", "failed_authentication": "Failed authentication. ${state_error}",
"faq": "FAQ", "faq": "FAQ",
"features": "Features",
"fetching": "Fetching", "fetching": "Fetching",
"fiat_api": "Fiat API", "fiat_api": "Fiat API",
"fiat_balance": "Fiat Balance", "fiat_balance": "Fiat Balance",
@ -531,6 +536,7 @@
"save_backup_password_alert": "Save backup password", "save_backup_password_alert": "Save backup password",
"save_to_downloads": "Save to Downloads", "save_to_downloads": "Save to Downloads",
"saved_the_trade_id": "I've saved the trade ID", "saved_the_trade_id": "I've saved the trade ID",
"scan_one_block": "Scan one block",
"scan_qr_code": "Scan QR code", "scan_qr_code": "Scan QR code",
"scan_qr_code_to_get_address": "Scan the QR code to get the address", "scan_qr_code_to_get_address": "Scan the QR code to get the address",
"scan_qr_on_device": "Scan this QR code on another device", "scan_qr_on_device": "Scan this QR code on another device",
@ -641,11 +647,22 @@
"sign_up": "Sign Up", "sign_up": "Sign Up",
"signTransaction": "Sign Transaction", "signTransaction": "Sign Transaction",
"signup_for_card_accept_terms": "Sign up for the card and accept the terms.", "signup_for_card_accept_terms": "Sign up for the card and accept the terms.",
"silent_payments": "Silent Payments",
"silent_payments_always_scan": "Set Silent Payments always scanning",
"silent_payments_disclaimer": "New addresses are not new identities. It is a re-use of an existing identity with a different label.",
"silent_payments_display_card": "Show Silent Payments card",
"silent_payments_scan_from_date": "Scan from date",
"silent_payments_scan_from_date_or_blockheight": "Please enter the block height you want to start scanning for incoming silent payments, or, use the date instead. You can choose if the wallet continues scanning every block, or checks only the specified height.",
"silent_payments_scan_from_height": "Scan from block height",
"silent_payments_scanned_tip": "SCANNED TO TIP! (${tip})",
"silent_payments_scanning": "Silent Payments Scanning",
"silent_payments_settings": "Silent Payments settings",
"slidable": "Slidable", "slidable": "Slidable",
"sort_by": "Sort by", "sort_by": "Sort by",
"spend_key_private": "Spend key (private)", "spend_key_private": "Spend key (private)",
"spend_key_public": "Spend key (public)", "spend_key_public": "Spend key (public)",
"status": "Status: ", "status": "Status: ",
"string_default": "Default",
"subaddress_title": "Subaddress list", "subaddress_title": "Subaddress list",
"subaddresses": "Subaddresses", "subaddresses": "Subaddresses",
"submit_request": "submit a request", "submit_request": "submit a request",
@ -670,10 +687,13 @@
"sync_status_starting_sync": "STARTING SYNC", "sync_status_starting_sync": "STARTING SYNC",
"sync_status_syncronized": "SYNCHRONIZED", "sync_status_syncronized": "SYNCHRONIZED",
"sync_status_syncronizing": "SYNCHRONIZING", "sync_status_syncronizing": "SYNCHRONIZING",
"sync_status_timed_out": "TIMED OUT",
"sync_status_unsupported": "UNSUPPORTED NODE",
"syncing_wallet_alert_content": "Your balance and transaction list may not be complete until it says “SYNCHRONIZED” at the top. Click/tap to learn more.", "syncing_wallet_alert_content": "Your balance and transaction list may not be complete until it says “SYNCHRONIZED” at the top. Click/tap to learn more.",
"syncing_wallet_alert_title": "Your wallet is syncing", "syncing_wallet_alert_title": "Your wallet is syncing",
"template": "Template", "template": "Template",
"template_name": "Template Name", "template_name": "Template Name",
"testnet_coins_no_value": "Testnet coins have no value",
"third_intro_content": "Yats live outside of Cake Wallet, too. Any wallet address on earth can be replaced with a Yat!", "third_intro_content": "Yats live outside of Cake Wallet, too. Any wallet address on earth can be replaced with a Yat!",
"third_intro_title": "Yat plays nicely with others", "third_intro_title": "Yat plays nicely with others",
"thorchain_contract_address_not_supported": "THORChain does not support sending to a contract address", "thorchain_contract_address_not_supported": "THORChain does not support sending to a contract address",
@ -749,13 +769,16 @@
"trusted": "Trusted", "trusted": "Trusted",
"tx_commit_exception_no_dust_on_change": "The transaction is rejected with this amount. With these coins you can send ${min} without change or ${max} that returns change.", "tx_commit_exception_no_dust_on_change": "The transaction is rejected with this amount. With these coins you can send ${min} without change or ${max} that returns change.",
"tx_commit_failed": "Transaction commit failed. Please contact support.", "tx_commit_failed": "Transaction commit failed. Please contact support.",
"tx_invalid_input": "You are using the wrong input type for this type of payment",
"tx_no_dust_exception": "The transaction is rejected by sending an amount too small. Please try increasing the amount.", "tx_no_dust_exception": "The transaction is rejected by sending an amount too small. Please try increasing the amount.",
"tx_not_enough_inputs_exception": "Not enough inputs available. Please select more under Coin Control", "tx_not_enough_inputs_exception": "Not enough inputs available. Please select more under Coin Control",
"tx_rejected_bip68_final": "Transaction has unconfirmed inputs and failed to replace by fee.",
"tx_rejected_dust_change": "Transaction rejected by network rules, low change amount (dust). Try sending ALL or reducing the amount.", "tx_rejected_dust_change": "Transaction rejected by network rules, low change amount (dust). Try sending ALL or reducing the amount.",
"tx_rejected_dust_output": "Transaction rejected by network rules, low output amount (dust). Please increase the amount.", "tx_rejected_dust_output": "Transaction rejected by network rules, low output amount (dust). Please increase the amount.",
"tx_rejected_dust_output_send_all": "Transaction rejected by network rules, low output amount (dust). Please check the balance of coins selected under Coin Control.", "tx_rejected_dust_output_send_all": "Transaction rejected by network rules, low output amount (dust). Please check the balance of coins selected under Coin Control.",
"tx_rejected_vout_negative": "Not enough balance to pay for this transaction's fees. Please check the balance of coins under Coin Control.", "tx_rejected_vout_negative": "Not enough balance to pay for this transaction's fees. Please check the balance of coins under Coin Control.",
"tx_wrong_balance_exception": "You do not have enough ${currency} to send this amount.", "tx_wrong_balance_exception": "You do not have enough ${currency} to send this amount.",
"tx_wrong_balance_with_amount_exception": "You do not have enough ${currency} to send the total amount of ${amount}",
"tx_zero_fee_exception": "Cannot send transaction with 0 fee. Try increasing the rate or checking your connection for latest estimates.", "tx_zero_fee_exception": "Cannot send transaction with 0 fee. Try increasing the rate or checking your connection for latest estimates.",
"unavailable_balance": "Unavailable balance", "unavailable_balance": "Unavailable balance",
"unavailable_balance_description": "Unavailable Balance: This total includes funds that are locked in pending transactions and those you have actively frozen in your coin control settings. Locked balances will become available once their respective transactions are completed, while frozen balances remain inaccessible for transactions until you decide to unfreeze them.", "unavailable_balance_description": "Unavailable Balance: This total includes funds that are locked in pending transactions and those you have actively frozen in your coin control settings. Locked balances will become available once their respective transactions are completed, while frozen balances remain inaccessible for transactions until you decide to unfreeze them.",
@ -809,6 +832,7 @@
"warning": "Warning", "warning": "Warning",
"welcome": "Welcome to", "welcome": "Welcome to",
"welcome_to_cakepay": "Welcome to Cake Pay!", "welcome_to_cakepay": "Welcome to Cake Pay!",
"what_is_silent_payments": "What is silent payments?",
"widgets_address": "Address", "widgets_address": "Address",
"widgets_or": "or", "widgets_or": "or",
"widgets_restore_from_blockheight": "Restore from blockheight", "widgets_restore_from_blockheight": "Restore from blockheight",

View file

@ -71,6 +71,7 @@
"backup": "Apoyo", "backup": "Apoyo",
"backup_file": "Archivo de respaldo", "backup_file": "Archivo de respaldo",
"backup_password": "Contraseña de respaldo", "backup_password": "Contraseña de respaldo",
"balance": "Balance",
"balance_page": "Página de saldo", "balance_page": "Página de saldo",
"bill_amount": "Importe de la factura", "bill_amount": "Importe de la factura",
"billing_address_info": "Si se le solicita una dirección de facturación, proporcione su dirección de envío", "billing_address_info": "Si se le solicita una dirección de facturación, proporcione su dirección de envío",
@ -78,6 +79,7 @@
"bitcoin_dark_theme": "Tema oscuro de Bitcoin", "bitcoin_dark_theme": "Tema oscuro de Bitcoin",
"bitcoin_light_theme": "Tema de la luz de Bitcoin", "bitcoin_light_theme": "Tema de la luz de Bitcoin",
"bitcoin_payments_require_1_confirmation": "Los pagos de Bitcoin requieren 1 confirmación, que puede demorar 20 minutos o más. ¡Gracias por su paciencia! Se le enviará un correo electrónico cuando se confirme el pago.", "bitcoin_payments_require_1_confirmation": "Los pagos de Bitcoin requieren 1 confirmación, que puede demorar 20 minutos o más. ¡Gracias por su paciencia! Se le enviará un correo electrónico cuando se confirme el pago.",
"block_remaining": "1 bloqueo restante",
"Blocks_remaining": "${status} Bloques restantes", "Blocks_remaining": "${status} Bloques restantes",
"bluetooth": "Bluetooth", "bluetooth": "Bluetooth",
"bright_theme": "Brillante", "bright_theme": "Brillante",
@ -139,6 +141,7 @@
"confirm_fee_deduction": "Confirmar la deducción de la tarifa", "confirm_fee_deduction": "Confirmar la deducción de la tarifa",
"confirm_fee_deduction_content": "¿Acepta deducir la tarifa de la producción?", "confirm_fee_deduction_content": "¿Acepta deducir la tarifa de la producción?",
"confirm_sending": "Confirmar envío", "confirm_sending": "Confirmar envío",
"confirm_silent_payments_switch_node": "Actualmente se requiere cambiar los nodos para escanear pagos silenciosos",
"confirmations": "Confirmaciones", "confirmations": "Confirmaciones",
"confirmed": "Saldo confirmado", "confirmed": "Saldo confirmado",
"confirmed_tx": "Confirmado", "confirmed_tx": "Confirmado",
@ -221,6 +224,7 @@
"electrum_address_disclaimer": "Generamos nuevas direcciones cada vez que usa una, pero las direcciones anteriores siguen funcionando", "electrum_address_disclaimer": "Generamos nuevas direcciones cada vez que usa una, pero las direcciones anteriores siguen funcionando",
"email_address": "Dirección de correo electrónico", "email_address": "Dirección de correo electrónico",
"enable_replace_by_fee": "Habilitar reemplazar por tarea", "enable_replace_by_fee": "Habilitar reemplazar por tarea",
"enable_silent_payments_scanning": "Habilitar escaneo de pagos silenciosos",
"enabled": "Activado", "enabled": "Activado",
"enter_amount": "Ingrese la cantidad", "enter_amount": "Ingrese la cantidad",
"enter_backup_password": "Ingrese la contraseña de respaldo aquí", "enter_backup_password": "Ingrese la contraseña de respaldo aquí",
@ -278,6 +282,7 @@
"extracted_address_content": "Enviará fondos a\n${recipient_name}", "extracted_address_content": "Enviará fondos a\n${recipient_name}",
"failed_authentication": "Autenticación fallida. ${state_error}", "failed_authentication": "Autenticación fallida. ${state_error}",
"faq": "FAQ", "faq": "FAQ",
"features": "Características",
"fetching": "Cargando", "fetching": "Cargando",
"fiat_api": "Fiat API", "fiat_api": "Fiat API",
"fiat_balance": "Equilibrio Fiat", "fiat_balance": "Equilibrio Fiat",
@ -532,6 +537,7 @@
"save_backup_password_alert": "Guardar contraseña de respaldo", "save_backup_password_alert": "Guardar contraseña de respaldo",
"save_to_downloads": "Guardar en Descargas", "save_to_downloads": "Guardar en Descargas",
"saved_the_trade_id": "He salvado comercial ID", "saved_the_trade_id": "He salvado comercial ID",
"scan_one_block": "Escanear un bloque",
"scan_qr_code": "Escanear código QR", "scan_qr_code": "Escanear código QR",
"scan_qr_code_to_get_address": "Escanee el código QR para obtener la dirección", "scan_qr_code_to_get_address": "Escanee el código QR para obtener la dirección",
"scan_qr_on_device": "Escanea este código QR en otro dispositivo", "scan_qr_on_device": "Escanea este código QR en otro dispositivo",
@ -642,11 +648,22 @@
"sign_up": "Registrarse", "sign_up": "Registrarse",
"signTransaction": "Firmar transacción", "signTransaction": "Firmar transacción",
"signup_for_card_accept_terms": "Regístrese para obtener la tarjeta y acepte los términos.", "signup_for_card_accept_terms": "Regístrese para obtener la tarjeta y acepte los términos.",
"silent_payments": "Pagos silenciosos",
"silent_payments_always_scan": "Establecer pagos silenciosos siempre escaneando",
"silent_payments_disclaimer": "Las nuevas direcciones no son nuevas identidades. Es una reutilización de una identidad existente con una etiqueta diferente.",
"silent_payments_display_card": "Mostrar tarjeta de pagos silenciosos",
"silent_payments_scan_from_date": "Escanear desde la fecha",
"silent_payments_scan_from_date_or_blockheight": "Ingrese la altura del bloque que desea comenzar a escanear para pagos silenciosos entrantes, o use la fecha en su lugar. Puede elegir si la billetera continúa escaneando cada bloque, o verifica solo la altura especificada.",
"silent_payments_scan_from_height": "Escanear desde la altura del bloque",
"silent_payments_scanned_tip": "Escaneado hasta la punta! (${tip})",
"silent_payments_scanning": "Escaneo de pagos silenciosos",
"silent_payments_settings": "Configuración de pagos silenciosos",
"slidable": "deslizable", "slidable": "deslizable",
"sort_by": "Ordenar por", "sort_by": "Ordenar por",
"spend_key_private": "Spend clave (privado)", "spend_key_private": "Spend clave (privado)",
"spend_key_public": "Spend clave (público)", "spend_key_public": "Spend clave (público)",
"status": "Estado: ", "status": "Estado: ",
"string_default": "Por defecto",
"subaddress_title": "Lista de subdirecciones", "subaddress_title": "Lista de subdirecciones",
"subaddresses": "Subdirecciones", "subaddresses": "Subdirecciones",
"submit_request": "presentar una solicitud", "submit_request": "presentar una solicitud",
@ -671,10 +688,13 @@
"sync_status_starting_sync": "EMPEZANDO A SINCRONIZAR", "sync_status_starting_sync": "EMPEZANDO A SINCRONIZAR",
"sync_status_syncronized": "SINCRONIZADO", "sync_status_syncronized": "SINCRONIZADO",
"sync_status_syncronizing": "SINCRONIZANDO", "sync_status_syncronizing": "SINCRONIZANDO",
"sync_status_timed_out": "CADUCADO",
"sync_status_unsupported": "Nodo no compatible",
"syncing_wallet_alert_content": "Es posible que su lista de saldo y transacciones no esté completa hasta que diga \"SINCRONIZADO\" en la parte superior. Haga clic/toque para obtener más información.", "syncing_wallet_alert_content": "Es posible que su lista de saldo y transacciones no esté completa hasta que diga \"SINCRONIZADO\" en la parte superior. Haga clic/toque para obtener más información.",
"syncing_wallet_alert_title": "Tu billetera se está sincronizando", "syncing_wallet_alert_title": "Tu billetera se está sincronizando",
"template": "Plantilla", "template": "Plantilla",
"template_name": "Nombre de la plantilla", "template_name": "Nombre de la plantilla",
"testnet_coins_no_value": "Las monedas de prueba no tienen valor",
"third_intro_content": "Los Yats también viven fuera de Cake Wallet. Cualquier dirección de billetera en la tierra se puede reemplazar con un Yat!", "third_intro_content": "Los Yats también viven fuera de Cake Wallet. Cualquier dirección de billetera en la tierra se puede reemplazar con un Yat!",
"third_intro_title": "Yat juega muy bien con otras", "third_intro_title": "Yat juega muy bien con otras",
"thorchain_contract_address_not_supported": "Thorchain no admite enviar a una dirección de contrato", "thorchain_contract_address_not_supported": "Thorchain no admite enviar a una dirección de contrato",
@ -750,13 +770,16 @@
"trusted": "de confianza", "trusted": "de confianza",
"tx_commit_exception_no_dust_on_change": "La transacción se rechaza con esta cantidad. Con estas monedas puede enviar ${min} sin cambios o ${max} que devuelve el cambio.", "tx_commit_exception_no_dust_on_change": "La transacción se rechaza con esta cantidad. Con estas monedas puede enviar ${min} sin cambios o ${max} que devuelve el cambio.",
"tx_commit_failed": "La confirmación de transacción falló. Póngase en contacto con el soporte.", "tx_commit_failed": "La confirmación de transacción falló. Póngase en contacto con el soporte.",
"tx_invalid_input": "Está utilizando el tipo de entrada incorrecto para este tipo de pago",
"tx_no_dust_exception": "La transacción se rechaza enviando una cantidad demasiado pequeña. Intente aumentar la cantidad.", "tx_no_dust_exception": "La transacción se rechaza enviando una cantidad demasiado pequeña. Intente aumentar la cantidad.",
"tx_not_enough_inputs_exception": "No hay suficientes entradas disponibles. Seleccione más bajo control de monedas", "tx_not_enough_inputs_exception": "No hay suficientes entradas disponibles. Seleccione más bajo control de monedas",
"tx_rejected_bip68_final": "La transacción tiene entradas no confirmadas y no ha podido reemplazar por tarifa.",
"tx_rejected_dust_change": "Transacción rechazada por reglas de red, bajo cambio de cambio (polvo). Intente enviar todo o reducir la cantidad.", "tx_rejected_dust_change": "Transacción rechazada por reglas de red, bajo cambio de cambio (polvo). Intente enviar todo o reducir la cantidad.",
"tx_rejected_dust_output": "Transacción rechazada por reglas de red, baja cantidad de salida (polvo). Aumente la cantidad.", "tx_rejected_dust_output": "Transacción rechazada por reglas de red, baja cantidad de salida (polvo). Aumente la cantidad.",
"tx_rejected_dust_output_send_all": "Transacción rechazada por reglas de red, baja cantidad de salida (polvo). Verifique el saldo de monedas seleccionadas bajo control de monedas.", "tx_rejected_dust_output_send_all": "Transacción rechazada por reglas de red, baja cantidad de salida (polvo). Verifique el saldo de monedas seleccionadas bajo control de monedas.",
"tx_rejected_vout_negative": "No es suficiente saldo para pagar las tarifas de esta transacción. Verifique el saldo de monedas bajo control de monedas.", "tx_rejected_vout_negative": "No es suficiente saldo para pagar las tarifas de esta transacción. Verifique el saldo de monedas bajo control de monedas.",
"tx_wrong_balance_exception": "No tiene suficiente ${currency} para enviar esta cantidad.", "tx_wrong_balance_exception": "No tiene suficiente ${currency} para enviar esta cantidad.",
"tx_wrong_balance_with_amount_exception": "No tiene suficiente ${currency} para enviar la cantidad total de ${amount}",
"tx_zero_fee_exception": "No se puede enviar transacciones con 0 tarifa. Intente aumentar la tasa o verificar su conexión para las últimas estimaciones.", "tx_zero_fee_exception": "No se puede enviar transacciones con 0 tarifa. Intente aumentar la tasa o verificar su conexión para las últimas estimaciones.",
"unavailable_balance": "Saldo no disponible", "unavailable_balance": "Saldo no disponible",
"unavailable_balance_description": "Saldo no disponible: este total incluye fondos que están bloqueados en transacciones pendientes y aquellos que usted ha congelado activamente en su configuración de control de monedas. Los saldos bloqueados estarán disponibles una vez que se completen sus respectivas transacciones, mientras que los saldos congelados permanecerán inaccesibles para las transacciones hasta que usted decida descongelarlos.", "unavailable_balance_description": "Saldo no disponible: este total incluye fondos que están bloqueados en transacciones pendientes y aquellos que usted ha congelado activamente en su configuración de control de monedas. Los saldos bloqueados estarán disponibles una vez que se completen sus respectivas transacciones, mientras que los saldos congelados permanecerán inaccesibles para las transacciones hasta que usted decida descongelarlos.",
@ -810,6 +833,7 @@
"warning": "Advertencia", "warning": "Advertencia",
"welcome": "Bienvenido", "welcome": "Bienvenido",
"welcome_to_cakepay": "¡Bienvenido a Cake Pay!", "welcome_to_cakepay": "¡Bienvenido a Cake Pay!",
"what_is_silent_payments": "¿Qué son los pagos silenciosos?",
"widgets_address": "Dirección", "widgets_address": "Dirección",
"widgets_or": "o", "widgets_or": "o",
"widgets_restore_from_blockheight": "Restaurar desde blockheight", "widgets_restore_from_blockheight": "Restaurar desde blockheight",

View file

@ -71,6 +71,7 @@
"backup": "Sauvegarde", "backup": "Sauvegarde",
"backup_file": "Fichier de sauvegarde", "backup_file": "Fichier de sauvegarde",
"backup_password": "Mot de passe de sauvegarde", "backup_password": "Mot de passe de sauvegarde",
"balance": "Équilibre",
"balance_page": "Page Solde", "balance_page": "Page Solde",
"bill_amount": "Montant de la facture", "bill_amount": "Montant de la facture",
"billing_address_info": "Si une adresse de facturation vous est demandée, indiquez votre adresse de livraison", "billing_address_info": "Si une adresse de facturation vous est demandée, indiquez votre adresse de livraison",
@ -78,6 +79,7 @@
"bitcoin_dark_theme": "Thème sombre Bitcoin", "bitcoin_dark_theme": "Thème sombre Bitcoin",
"bitcoin_light_theme": "Thème léger Bitcoin", "bitcoin_light_theme": "Thème léger Bitcoin",
"bitcoin_payments_require_1_confirmation": "Les paiements Bitcoin nécessitent 1 confirmation, ce qui peut prendre 20 minutes ou plus. Merci pour votre patience ! Vous serez averti par e-mail lorsque le paiement sera confirmé.", "bitcoin_payments_require_1_confirmation": "Les paiements Bitcoin nécessitent 1 confirmation, ce qui peut prendre 20 minutes ou plus. Merci pour votre patience ! Vous serez averti par e-mail lorsque le paiement sera confirmé.",
"block_remaining": "1 bloc restant",
"Blocks_remaining": "Blocs Restants : ${status}", "Blocks_remaining": "Blocs Restants : ${status}",
"bluetooth": "Bluetooth", "bluetooth": "Bluetooth",
"bright_theme": "Vif", "bright_theme": "Vif",
@ -139,6 +141,7 @@
"confirm_fee_deduction": "Confirmer la déduction des frais", "confirm_fee_deduction": "Confirmer la déduction des frais",
"confirm_fee_deduction_content": "Acceptez-vous de déduire les frais de la production?", "confirm_fee_deduction_content": "Acceptez-vous de déduire les frais de la production?",
"confirm_sending": "Confirmer l'envoi", "confirm_sending": "Confirmer l'envoi",
"confirm_silent_payments_switch_node": "Actuellement, il est nécessaire de changer de nœuds pour scanner les paiements silencieux",
"confirmations": "Confirmations", "confirmations": "Confirmations",
"confirmed": "Solde confirmé", "confirmed": "Solde confirmé",
"confirmed_tx": "Confirmé", "confirmed_tx": "Confirmé",
@ -221,6 +224,7 @@
"electrum_address_disclaimer": "Nous générons de nouvelles adresses à chaque fois que vous en utilisez une, mais les adresses précédentes continuent à fonctionner", "electrum_address_disclaimer": "Nous générons de nouvelles adresses à chaque fois que vous en utilisez une, mais les adresses précédentes continuent à fonctionner",
"email_address": "Adresse e-mail", "email_address": "Adresse e-mail",
"enable_replace_by_fee": "Activer Remplace-by-Fee", "enable_replace_by_fee": "Activer Remplace-by-Fee",
"enable_silent_payments_scanning": "Activer la numérisation des paiements silencieux",
"enabled": "Activé", "enabled": "Activé",
"enter_amount": "Entrez le montant", "enter_amount": "Entrez le montant",
"enter_backup_password": "Entrez le mot de passe de sauvegarde ici", "enter_backup_password": "Entrez le mot de passe de sauvegarde ici",
@ -278,6 +282,7 @@
"extracted_address_content": "Vous allez envoyer des fonds à\n${recipient_name}", "extracted_address_content": "Vous allez envoyer des fonds à\n${recipient_name}",
"failed_authentication": "Échec d'authentification. ${state_error}", "failed_authentication": "Échec d'authentification. ${state_error}",
"faq": "FAQ", "faq": "FAQ",
"features": "Caractéristiques",
"fetching": "Récupération", "fetching": "Récupération",
"fiat_api": "Fiat API", "fiat_api": "Fiat API",
"fiat_balance": "Solde fiat", "fiat_balance": "Solde fiat",
@ -531,6 +536,7 @@
"save_backup_password_alert": "Enregistrer le mot de passe de sauvegarde", "save_backup_password_alert": "Enregistrer le mot de passe de sauvegarde",
"save_to_downloads": "Enregistrer dans les téléchargements", "save_to_downloads": "Enregistrer dans les téléchargements",
"saved_the_trade_id": "J'ai sauvegardé l'ID d'échange", "saved_the_trade_id": "J'ai sauvegardé l'ID d'échange",
"scan_one_block": "Scanner un bloc",
"scan_qr_code": "Scannez le QR code", "scan_qr_code": "Scannez le QR code",
"scan_qr_code_to_get_address": "Scannez le QR code pour obtenir l'adresse", "scan_qr_code_to_get_address": "Scannez le QR code pour obtenir l'adresse",
"scan_qr_on_device": "Scannez ce code QR sur un autre appareil", "scan_qr_on_device": "Scannez ce code QR sur un autre appareil",
@ -641,11 +647,22 @@
"sign_up": "S'inscrire", "sign_up": "S'inscrire",
"signTransaction": "Signer une transaction", "signTransaction": "Signer une transaction",
"signup_for_card_accept_terms": "Inscrivez-vous pour la carte et acceptez les conditions.", "signup_for_card_accept_terms": "Inscrivez-vous pour la carte et acceptez les conditions.",
"silent_payments": "Paiements silencieux",
"silent_payments_always_scan": "Définir les paiements silencieux toujours à la scanne",
"silent_payments_disclaimer": "Les nouvelles adresses ne sont pas de nouvelles identités. Il s'agit d'une réutilisation d'une identité existante avec une étiquette différente.",
"silent_payments_display_card": "Afficher la carte de paiement silencieuse",
"silent_payments_scan_from_date": "Analyser à partir de la date",
"silent_payments_scan_from_date_or_blockheight": "Veuillez saisir la hauteur du bloc que vous souhaitez commencer à scanner pour les paiements silencieux entrants, ou utilisez la date à la place. Vous pouvez choisir si le portefeuille continue de numériser chaque bloc ou ne vérifie que la hauteur spécifiée.",
"silent_payments_scan_from_height": "Scan à partir de la hauteur du bloc",
"silent_payments_scanned_tip": "Scanné à la pointe! (${tip})",
"silent_payments_scanning": "Payments silencieux SCANNING",
"silent_payments_settings": "Paramètres de paiement silencieux",
"slidable": "Glissable", "slidable": "Glissable",
"sort_by": "Trier par", "sort_by": "Trier par",
"spend_key_private": "Clef de dépense (spend key) (privée)", "spend_key_private": "Clef de dépense (spend key) (privée)",
"spend_key_public": "Clef de dépense (spend key) (publique)", "spend_key_public": "Clef de dépense (spend key) (publique)",
"status": "Statut : ", "status": "Statut : ",
"string_default": "Défaut",
"subaddress_title": "Liste des sous-adresses", "subaddress_title": "Liste des sous-adresses",
"subaddresses": "Sous-adresses", "subaddresses": "Sous-adresses",
"submit_request": "soumettre une requête", "submit_request": "soumettre une requête",
@ -670,10 +687,13 @@
"sync_status_starting_sync": "DÉBUT DE SYNCHRO", "sync_status_starting_sync": "DÉBUT DE SYNCHRO",
"sync_status_syncronized": "SYNCHRONISÉ", "sync_status_syncronized": "SYNCHRONISÉ",
"sync_status_syncronizing": "SYNCHRONISATION EN COURS", "sync_status_syncronizing": "SYNCHRONISATION EN COURS",
"sync_status_timed_out": "FIN DU TEMPS",
"sync_status_unsupported": "Nœud non pris en charge",
"syncing_wallet_alert_content": "Votre solde et votre liste de transactions peuvent ne pas être à jour tant que la mention « SYNCHRONISÉ » n'apparaît en haut de l'écran. Cliquez/appuyez pour en savoir plus.", "syncing_wallet_alert_content": "Votre solde et votre liste de transactions peuvent ne pas être à jour tant que la mention « SYNCHRONISÉ » n'apparaît en haut de l'écran. Cliquez/appuyez pour en savoir plus.",
"syncing_wallet_alert_title": "Votre portefeuille (wallet) est en cours de synchronisation", "syncing_wallet_alert_title": "Votre portefeuille (wallet) est en cours de synchronisation",
"template": "Modèle", "template": "Modèle",
"template_name": "Nom du modèle", "template_name": "Nom du modèle",
"testnet_coins_no_value": "Les pièces TestNet n'ont aucune valeur",
"third_intro_content": "Les Yats existent aussi en dehors de Cake Wallet. Toute adresse sur terre peut être remplacée par un Yat !", "third_intro_content": "Les Yats existent aussi en dehors de Cake Wallet. Toute adresse sur terre peut être remplacée par un Yat !",
"third_intro_title": "Yat est universel", "third_intro_title": "Yat est universel",
"thorchain_contract_address_not_supported": "Thorchain ne prend pas en charge l'envoi à une adresse de contrat", "thorchain_contract_address_not_supported": "Thorchain ne prend pas en charge l'envoi à une adresse de contrat",
@ -749,13 +769,16 @@
"trusted": "de confiance", "trusted": "de confiance",
"tx_commit_exception_no_dust_on_change": "La transaction est rejetée avec ce montant. Avec ces pièces, vous pouvez envoyer ${min} sans changement ou ${max} qui renvoie le changement.", "tx_commit_exception_no_dust_on_change": "La transaction est rejetée avec ce montant. Avec ces pièces, vous pouvez envoyer ${min} sans changement ou ${max} qui renvoie le changement.",
"tx_commit_failed": "La validation de la transaction a échoué. Veuillez contacter l'assistance.", "tx_commit_failed": "La validation de la transaction a échoué. Veuillez contacter l'assistance.",
"tx_invalid_input": "Vous utilisez le mauvais type d'entrée pour ce type de paiement",
"tx_no_dust_exception": "La transaction est rejetée en envoyant un montant trop faible. Veuillez essayer d'augmenter le montant.", "tx_no_dust_exception": "La transaction est rejetée en envoyant un montant trop faible. Veuillez essayer d'augmenter le montant.",
"tx_not_enough_inputs_exception": "Pas assez d'entrées disponibles. Veuillez sélectionner plus sous Control Control", "tx_not_enough_inputs_exception": "Pas assez d'entrées disponibles. Veuillez sélectionner plus sous Control Control",
"tx_rejected_bip68_final": "La transaction a des entrées non confirmées et n'a pas réussi à remplacer par les frais.",
"tx_rejected_dust_change": "Transaction rejetée par les règles du réseau, montant de faible variation (poussière). Essayez d'envoyer tout ou de réduire le montant.", "tx_rejected_dust_change": "Transaction rejetée par les règles du réseau, montant de faible variation (poussière). Essayez d'envoyer tout ou de réduire le montant.",
"tx_rejected_dust_output": "Transaction rejetée par les règles du réseau, faible quantité de sortie (poussière). Veuillez augmenter le montant.", "tx_rejected_dust_output": "Transaction rejetée par les règles du réseau, faible quantité de sortie (poussière). Veuillez augmenter le montant.",
"tx_rejected_dust_output_send_all": "Transaction rejetée par les règles du réseau, faible quantité de sortie (poussière). Veuillez vérifier le solde des pièces sélectionnées sous le contrôle des pièces de monnaie.", "tx_rejected_dust_output_send_all": "Transaction rejetée par les règles du réseau, faible quantité de sortie (poussière). Veuillez vérifier le solde des pièces sélectionnées sous le contrôle des pièces de monnaie.",
"tx_rejected_vout_negative": "Pas assez de solde pour payer les frais de cette transaction. Veuillez vérifier le solde des pièces sous le contrôle des pièces.", "tx_rejected_vout_negative": "Pas assez de solde pour payer les frais de cette transaction. Veuillez vérifier le solde des pièces sous le contrôle des pièces.",
"tx_wrong_balance_exception": "Vous n'avez pas assez ${currency} pour envoyer ce montant.", "tx_wrong_balance_exception": "Vous n'avez pas assez ${currency} pour envoyer ce montant.",
"tx_wrong_balance_with_amount_exception": "Vous n'avez pas assez ${currency} pour envoyer le montant total de ${amount}",
"tx_zero_fee_exception": "Impossible d'envoyer une transaction avec 0 frais. Essayez d'augmenter le taux ou de vérifier votre connexion pour les dernières estimations.", "tx_zero_fee_exception": "Impossible d'envoyer une transaction avec 0 frais. Essayez d'augmenter le taux ou de vérifier votre connexion pour les dernières estimations.",
"unavailable_balance": "Solde indisponible", "unavailable_balance": "Solde indisponible",
"unavailable_balance_description": "Solde indisponible : ce total comprend les fonds bloqués dans les transactions en attente et ceux que vous avez activement gelés dans vos paramètres de contrôle des pièces. Les soldes bloqués deviendront disponibles une fois leurs transactions respectives terminées, tandis que les soldes gelés resteront inaccessibles aux transactions jusqu'à ce que vous décidiez de les débloquer.", "unavailable_balance_description": "Solde indisponible : ce total comprend les fonds bloqués dans les transactions en attente et ceux que vous avez activement gelés dans vos paramètres de contrôle des pièces. Les soldes bloqués deviendront disponibles une fois leurs transactions respectives terminées, tandis que les soldes gelés resteront inaccessibles aux transactions jusqu'à ce que vous décidiez de les débloquer.",
@ -809,6 +832,7 @@
"warning": "Avertissement", "warning": "Avertissement",
"welcome": "Bienvenue sur", "welcome": "Bienvenue sur",
"welcome_to_cakepay": "Bienvenue sur Cake Pay !", "welcome_to_cakepay": "Bienvenue sur Cake Pay !",
"what_is_silent_payments": "Qu'est-ce que les paiements silencieux?",
"widgets_address": "Adresse", "widgets_address": "Adresse",
"widgets_or": "ou", "widgets_or": "ou",
"widgets_restore_from_blockheight": "Restaurer depuis une hauteur de bloc", "widgets_restore_from_blockheight": "Restaurer depuis une hauteur de bloc",

View file

@ -71,6 +71,7 @@
"backup": "Ajiyayyen", "backup": "Ajiyayyen",
"backup_file": "Ajiyayyen fayil", "backup_file": "Ajiyayyen fayil",
"backup_password": "Ajiyayyen kalmar sirri", "backup_password": "Ajiyayyen kalmar sirri",
"balance": "Ma'auni",
"balance_page": "Ma'auni Page", "balance_page": "Ma'auni Page",
"bill_amount": "Adadin Bill", "bill_amount": "Adadin Bill",
"billing_address_info": "Idan an nemi adireshin biyan kuɗi, samar da adireshin jigilar kaya", "billing_address_info": "Idan an nemi adireshin biyan kuɗi, samar da adireshin jigilar kaya",
@ -78,6 +79,7 @@
"bitcoin_dark_theme": "Bitcoin Dark Jigo", "bitcoin_dark_theme": "Bitcoin Dark Jigo",
"bitcoin_light_theme": "Jigon Hasken Bitcoin", "bitcoin_light_theme": "Jigon Hasken Bitcoin",
"bitcoin_payments_require_1_confirmation": "Akwatin Bitcoin na buɗe 1 sambumbu, da yake za ta samu mintuna 20 ko yawa. Ina kira ga sabuwar lafiya! Zaka sanarwa ta email lokacin da aka samu akwatin samun lambar waya.", "bitcoin_payments_require_1_confirmation": "Akwatin Bitcoin na buɗe 1 sambumbu, da yake za ta samu mintuna 20 ko yawa. Ina kira ga sabuwar lafiya! Zaka sanarwa ta email lokacin da aka samu akwatin samun lambar waya.",
"block_remaining": "1 toshe ragowar",
"Blocks_remaining": "${status} Katanga ya rage", "Blocks_remaining": "${status} Katanga ya rage",
"bluetooth": "Bluetooth", "bluetooth": "Bluetooth",
"bright_theme": "Mai haske", "bright_theme": "Mai haske",
@ -139,6 +141,7 @@
"confirm_fee_deduction": "Tabbatar da cire kudade", "confirm_fee_deduction": "Tabbatar da cire kudade",
"confirm_fee_deduction_content": "Shin kun yarda ku cire kuɗin daga fitarwa?", "confirm_fee_deduction_content": "Shin kun yarda ku cire kuɗin daga fitarwa?",
"confirm_sending": "Tabbatar da aikawa", "confirm_sending": "Tabbatar da aikawa",
"confirm_silent_payments_switch_node": "A halin yanzu ana buƙatar sauya nodes don bincika biyan siliki",
"confirmations": "Tabbatar", "confirmations": "Tabbatar",
"confirmed": "An tabbatar", "confirmed": "An tabbatar",
"confirmed_tx": "Tabbatar", "confirmed_tx": "Tabbatar",
@ -221,6 +224,7 @@
"electrum_address_disclaimer": "Muna samar da sababbin adireshi duk lokacin da kuka yi amfani da ɗaya, amma adiresoshin da suka gabata suna ci gaba da aiki", "electrum_address_disclaimer": "Muna samar da sababbin adireshi duk lokacin da kuka yi amfani da ɗaya, amma adiresoshin da suka gabata suna ci gaba da aiki",
"email_address": "Adireshin i-mel", "email_address": "Adireshin i-mel",
"enable_replace_by_fee": "Ba da damar maye gurbin-by-kudin", "enable_replace_by_fee": "Ba da damar maye gurbin-by-kudin",
"enable_silent_payments_scanning": "Kunna biya biya",
"enabled": "An kunna", "enabled": "An kunna",
"enter_amount": "Shigar da Adadi", "enter_amount": "Shigar da Adadi",
"enter_backup_password": "Shigar da kalmar wucewa ta madadin nan", "enter_backup_password": "Shigar da kalmar wucewa ta madadin nan",
@ -278,6 +282,7 @@
"extracted_address_content": "Za ku aika da kudade zuwa\n${recipient_name}", "extracted_address_content": "Za ku aika da kudade zuwa\n${recipient_name}",
"failed_authentication": "Binne wajen shiga. ${state_error}", "failed_authentication": "Binne wajen shiga. ${state_error}",
"faq": "FAQ", "faq": "FAQ",
"features": "Fasas",
"fetching": "Daukewa", "fetching": "Daukewa",
"fiat_api": "API ɗin Fiat", "fiat_api": "API ɗin Fiat",
"fiat_balance": "Fiat Balance", "fiat_balance": "Fiat Balance",
@ -533,6 +538,7 @@
"save_backup_password_alert": "Ajiye kalmar sirri ta ajiya", "save_backup_password_alert": "Ajiye kalmar sirri ta ajiya",
"save_to_downloads": "Ajiye zuwa Zazzagewa", "save_to_downloads": "Ajiye zuwa Zazzagewa",
"saved_the_trade_id": "Na ajiye ID na ciniki", "saved_the_trade_id": "Na ajiye ID na ciniki",
"scan_one_block": "Duba toshe daya",
"scan_qr_code": "Gani QR kodin", "scan_qr_code": "Gani QR kodin",
"scan_qr_code_to_get_address": "Duba lambar QR don samun adireshin", "scan_qr_code_to_get_address": "Duba lambar QR don samun adireshin",
"scan_qr_on_device": "Duba wannan lambar QR akan wata na'ura", "scan_qr_on_device": "Duba wannan lambar QR akan wata na'ura",
@ -643,11 +649,22 @@
"sign_up": "Shiga", "sign_up": "Shiga",
"signTransaction": "Sa hannu Ma'amala", "signTransaction": "Sa hannu Ma'amala",
"signup_for_card_accept_terms": "Yi rajista don katin kuma karɓi sharuɗɗan.", "signup_for_card_accept_terms": "Yi rajista don katin kuma karɓi sharuɗɗan.",
"silent_payments": "Biya silent",
"silent_payments_always_scan": "Saita biya na shiru koyaushe",
"silent_payments_disclaimer": "Sabbin adiresoshin ba sabon tsari bane. Wannan shine sake amfani da asalin asalin tare da wata alama daban.",
"silent_payments_display_card": "Nuna katin silent",
"silent_payments_scan_from_date": "Scan daga kwanan wata",
"silent_payments_scan_from_date_or_blockheight": "Da fatan za a shigar da toshe wurin da kake son fara bincika don biyan silins mai shigowa, ko, yi amfani da kwanan wata. Zaka iya zabar idan walat ɗin ya ci gaba da bincika kowane toshe, ko duba tsinkaye da aka ƙayyade.",
"silent_payments_scan_from_height": "Scan daga tsayin daka",
"silent_payments_scanned_tip": "Bincika don tip! (${tip})",
"silent_payments_scanning": "Silent biya scanning",
"silent_payments_settings": "Saitunan Silent",
"slidable": "Mai iya zamewa", "slidable": "Mai iya zamewa",
"sort_by": "Kasa", "sort_by": "Kasa",
"spend_key_private": "makullin biya (maɓallin kalmar sirri)", "spend_key_private": "makullin biya (maɓallin kalmar sirri)",
"spend_key_public": "makullin biya (maɓallin jama'a)", "spend_key_public": "makullin biya (maɓallin jama'a)",
"status": "Matsayi:", "status": "Matsayi:",
"string_default": "Ƙin cika alƙawari",
"subaddress_title": "Jagorar subaddress", "subaddress_title": "Jagorar subaddress",
"subaddresses": "Subaddresses", "subaddresses": "Subaddresses",
"submit_request": "gabatar da bukata", "submit_request": "gabatar da bukata",
@ -672,10 +689,13 @@
"sync_status_starting_sync": "KWAFI", "sync_status_starting_sync": "KWAFI",
"sync_status_syncronized": "KYAU", "sync_status_syncronized": "KYAU",
"sync_status_syncronizing": "KWAFI", "sync_status_syncronizing": "KWAFI",
"sync_status_timed_out": "ATED Out",
"sync_status_unsupported": "Ba a Taimako ba",
"syncing_wallet_alert_content": "Ma'aunin ku da lissafin ma'amala bazai cika ba har sai an ce \"SYNCHRONIZED\" a saman. Danna/matsa don ƙarin koyo.", "syncing_wallet_alert_content": "Ma'aunin ku da lissafin ma'amala bazai cika ba har sai an ce \"SYNCHRONIZED\" a saman. Danna/matsa don ƙarin koyo.",
"syncing_wallet_alert_title": "Walat ɗin ku yana aiki tare", "syncing_wallet_alert_title": "Walat ɗin ku yana aiki tare",
"template": "Samfura", "template": "Samfura",
"template_name": "Sunan Samfura", "template_name": "Sunan Samfura",
"testnet_coins_no_value": "TalkNet tsabar kudi ba su da darajar",
"third_intro_content": "Yats suna zaune a wajen Kek Wallet, kuma. Ana iya maye gurbin kowane adireshin walat a duniya da Yat!", "third_intro_content": "Yats suna zaune a wajen Kek Wallet, kuma. Ana iya maye gurbin kowane adireshin walat a duniya da Yat!",
"third_intro_title": "Yat yana wasa da kyau tare da wasu", "third_intro_title": "Yat yana wasa da kyau tare da wasu",
"thorchain_contract_address_not_supported": "Thorchain baya goyon bayan aika zuwa adireshin kwangila", "thorchain_contract_address_not_supported": "Thorchain baya goyon bayan aika zuwa adireshin kwangila",
@ -751,13 +771,16 @@
"trusted": "Amintacce", "trusted": "Amintacce",
"tx_commit_exception_no_dust_on_change": "An ƙi ma'amala da wannan adadin. Tare da waɗannan tsabar kudi Zaka iya aika ${min}, ba tare da canji ba ko ${max} wanda ya dawo canzawa.", "tx_commit_exception_no_dust_on_change": "An ƙi ma'amala da wannan adadin. Tare da waɗannan tsabar kudi Zaka iya aika ${min}, ba tare da canji ba ko ${max} wanda ya dawo canzawa.",
"tx_commit_failed": "Ma'amala ya kasa. Da fatan za a tuntuɓi goyan baya.", "tx_commit_failed": "Ma'amala ya kasa. Da fatan za a tuntuɓi goyan baya.",
"tx_invalid_input": "Kuna amfani da nau'in shigar da ba daidai ba don wannan nau'in biyan kuɗi",
"tx_no_dust_exception": "An ƙi ma'amala ta hanyar aika adadin ƙarami. Da fatan za a gwada ƙara adadin.", "tx_no_dust_exception": "An ƙi ma'amala ta hanyar aika adadin ƙarami. Da fatan za a gwada ƙara adadin.",
"tx_not_enough_inputs_exception": "Bai isa ba hanyoyin da ake samu. Da fatan za selectiari a karkashin Kwarewar Coin", "tx_not_enough_inputs_exception": "Bai isa ba hanyoyin da ake samu. Da fatan za selectiari a karkashin Kwarewar Coin",
"tx_rejected_bip68_final": "Ma'amala tana da abubuwan da basu dace ba kuma sun kasa maye gurbin ta.",
"tx_rejected_dust_change": "Ma'amala ta ƙi ta dokokin cibiyar sadarwa, ƙarancin canji (ƙura). Gwada aikawa da duka ko rage adadin.", "tx_rejected_dust_change": "Ma'amala ta ƙi ta dokokin cibiyar sadarwa, ƙarancin canji (ƙura). Gwada aikawa da duka ko rage adadin.",
"tx_rejected_dust_output": "Ma'adar da aka ƙi ta dokokin cibiyar sadarwa, ƙananan fitarwa (ƙura). Da fatan za a ƙara adadin.", "tx_rejected_dust_output": "Ma'adar da aka ƙi ta dokokin cibiyar sadarwa, ƙananan fitarwa (ƙura). Da fatan za a ƙara adadin.",
"tx_rejected_dust_output_send_all": "Ma'adar da aka ƙi ta dokokin cibiyar sadarwa, ƙananan fitarwa (ƙura). Da fatan za a duba daidaiton tsabar kudi a ƙarƙashin ikon tsabar kudin.", "tx_rejected_dust_output_send_all": "Ma'adar da aka ƙi ta dokokin cibiyar sadarwa, ƙananan fitarwa (ƙura). Da fatan za a duba daidaiton tsabar kudi a ƙarƙashin ikon tsabar kudin.",
"tx_rejected_vout_negative": "Bai isa daidai ba don biyan wannan kudin ma'amala. Da fatan za a duba daidaiton tsabar kudi a ƙarƙashin ikon tsabar kudin.", "tx_rejected_vout_negative": "Bai isa daidai ba don biyan wannan kudin ma'amala. Da fatan za a duba daidaiton tsabar kudi a ƙarƙashin ikon tsabar kudin.",
"tx_wrong_balance_exception": "Ba ku da isasshen ${currency} don aika wannan adadin.", "tx_wrong_balance_exception": "Ba ku da isasshen ${currency} don aika wannan adadin.",
"tx_wrong_balance_with_amount_exception": "Ba ku da isasshen ${currency} don aika jimlar adadin ${amount}",
"tx_zero_fee_exception": "Ba zai iya aika ma'amala da kuɗi 0 ba. Gwada ƙara ƙimar ko bincika haɗin ku don mahimmin ƙididdiga.", "tx_zero_fee_exception": "Ba zai iya aika ma'amala da kuɗi 0 ba. Gwada ƙara ƙimar ko bincika haɗin ku don mahimmin ƙididdiga.",
"unavailable_balance": "Ma'aunin da ba ya samuwa", "unavailable_balance": "Ma'aunin da ba ya samuwa",
"unavailable_balance_description": "Ma'auni Babu: Wannan jimlar ya haɗa da kuɗi waɗanda ke kulle a cikin ma'amaloli da ke jiran aiki da waɗanda kuka daskare sosai a cikin saitunan sarrafa kuɗin ku. Ma'auni da aka kulle za su kasance da zarar an kammala ma'amalolinsu, yayin da daskararrun ma'auni ba za su iya samun damar yin ciniki ba har sai kun yanke shawarar cire su.", "unavailable_balance_description": "Ma'auni Babu: Wannan jimlar ya haɗa da kuɗi waɗanda ke kulle a cikin ma'amaloli da ke jiran aiki da waɗanda kuka daskare sosai a cikin saitunan sarrafa kuɗin ku. Ma'auni da aka kulle za su kasance da zarar an kammala ma'amalolinsu, yayin da daskararrun ma'auni ba za su iya samun damar yin ciniki ba har sai kun yanke shawarar cire su.",
@ -811,6 +834,7 @@
"warning": "Gargadi", "warning": "Gargadi",
"welcome": "Barka da zuwa", "welcome": "Barka da zuwa",
"welcome_to_cakepay": "Barka da zuwa Cake Pay!", "welcome_to_cakepay": "Barka da zuwa Cake Pay!",
"what_is_silent_payments": "Menene biyan shiru?",
"widgets_address": "Adireshin", "widgets_address": "Adireshin",
"widgets_or": "ko", "widgets_or": "ko",
"widgets_restore_from_blockheight": "Sake dawo da daga blockheight", "widgets_restore_from_blockheight": "Sake dawo da daga blockheight",

View file

@ -71,6 +71,7 @@
"backup": "बैकअप", "backup": "बैकअप",
"backup_file": "बैकअपफ़ाइल", "backup_file": "बैकअपफ़ाइल",
"backup_password": "बैकअप पासवर्ड", "backup_password": "बैकअप पासवर्ड",
"balance": "संतुलन",
"balance_page": "बैलेंस पेज", "balance_page": "बैलेंस पेज",
"bill_amount": "बिल राशि", "bill_amount": "बिल राशि",
"billing_address_info": "यदि बिलिंग पता मांगा जाए, तो अपना शिपिंग पता प्रदान करें", "billing_address_info": "यदि बिलिंग पता मांगा जाए, तो अपना शिपिंग पता प्रदान करें",
@ -78,6 +79,7 @@
"bitcoin_dark_theme": "बिटकॉइन डार्क थीम", "bitcoin_dark_theme": "बिटकॉइन डार्क थीम",
"bitcoin_light_theme": "बिटकॉइन लाइट थीम", "bitcoin_light_theme": "बिटकॉइन लाइट थीम",
"bitcoin_payments_require_1_confirmation": "बिटकॉइन भुगतान के लिए 1 पुष्टिकरण की आवश्यकता होती है, जिसमें 20 मिनट या अधिक समय लग सकता है। आपके धैर्य के लिए धन्यवाद! भुगतान की पुष्टि होने पर आपको ईमेल किया जाएगा।", "bitcoin_payments_require_1_confirmation": "बिटकॉइन भुगतान के लिए 1 पुष्टिकरण की आवश्यकता होती है, जिसमें 20 मिनट या अधिक समय लग सकता है। आपके धैर्य के लिए धन्यवाद! भुगतान की पुष्टि होने पर आपको ईमेल किया जाएगा।",
"block_remaining": "1 ब्लॉक शेष",
"Blocks_remaining": "${status} शेष रहते हैं", "Blocks_remaining": "${status} शेष रहते हैं",
"bluetooth": "ब्लूटूथ", "bluetooth": "ब्लूटूथ",
"bright_theme": "उज्ज्वल", "bright_theme": "उज्ज्वल",
@ -139,6 +141,7 @@
"confirm_fee_deduction": "शुल्क कटौती की पुष्टि करें", "confirm_fee_deduction": "शुल्क कटौती की पुष्टि करें",
"confirm_fee_deduction_content": "क्या आप आउटपुट से शुल्क में कटौती करने के लिए सहमत हैं?", "confirm_fee_deduction_content": "क्या आप आउटपुट से शुल्क में कटौती करने के लिए सहमत हैं?",
"confirm_sending": "भेजने की पुष्टि करें", "confirm_sending": "भेजने की पुष्टि करें",
"confirm_silent_payments_switch_node": "वर्तमान में मूक भुगतान को स्कैन करने के लिए नोड्स को स्विच करना आवश्यक है",
"confirmations": "पुष्टिकरण", "confirmations": "पुष्टिकरण",
"confirmed": "पुष्टि की गई शेष राशिी", "confirmed": "पुष्टि की गई शेष राशिी",
"confirmed_tx": "की पुष्टि", "confirmed_tx": "की पुष्टि",
@ -221,6 +224,7 @@
"electrum_address_disclaimer": "हर बार जब आप एक का उपयोग करते हैं तो हम नए पते उत्पन्न करते हैं, लेकिन पिछले पते काम करना जारी रखते हैं", "electrum_address_disclaimer": "हर बार जब आप एक का उपयोग करते हैं तो हम नए पते उत्पन्न करते हैं, लेकिन पिछले पते काम करना जारी रखते हैं",
"email_address": "ईमेल पता", "email_address": "ईमेल पता",
"enable_replace_by_fee": "प्रतिस्थापित-दर-शुल्क सक्षम करें", "enable_replace_by_fee": "प्रतिस्थापित-दर-शुल्क सक्षम करें",
"enable_silent_payments_scanning": "मूक भुगतान स्कैनिंग सक्षम करें",
"enabled": "सक्रिय", "enabled": "सक्रिय",
"enter_amount": "राशि दर्ज करें", "enter_amount": "राशि दर्ज करें",
"enter_backup_password": "यहां बैकअप पासवर्ड डालें", "enter_backup_password": "यहां बैकअप पासवर्ड डालें",
@ -278,6 +282,7 @@
"extracted_address_content": "आपको धनराशि भेजी जाएगी\n${recipient_name}", "extracted_address_content": "आपको धनराशि भेजी जाएगी\n${recipient_name}",
"failed_authentication": "प्रमाणीकरण विफल. ${state_error}", "failed_authentication": "प्रमाणीकरण विफल. ${state_error}",
"faq": "FAQ", "faq": "FAQ",
"features": "विशेषताएँ",
"fetching": "ला रहा है", "fetching": "ला रहा है",
"fiat_api": "फिएट पैसे API", "fiat_api": "फिएट पैसे API",
"fiat_balance": "फिएट बैलेंस", "fiat_balance": "फिएट बैलेंस",
@ -533,6 +538,7 @@
"save_backup_password_alert": "बैकअप पासवर्ड सेव करें", "save_backup_password_alert": "बैकअप पासवर्ड सेव करें",
"save_to_downloads": "डाउनलोड में सहेजें", "save_to_downloads": "डाउनलोड में सहेजें",
"saved_the_trade_id": "मैंने व्यापार बचा लिया है ID", "saved_the_trade_id": "मैंने व्यापार बचा लिया है ID",
"scan_one_block": "एक ब्लॉक को स्कैन करना",
"scan_qr_code": "स्कैन क्यू आर कोड", "scan_qr_code": "स्कैन क्यू आर कोड",
"scan_qr_code_to_get_address": "पता प्राप्त करने के लिए QR कोड स्कैन करें", "scan_qr_code_to_get_address": "पता प्राप्त करने के लिए QR कोड स्कैन करें",
"scan_qr_on_device": "इस QR कोड को किसी अन्य डिवाइस पर स्कैन करें", "scan_qr_on_device": "इस QR कोड को किसी अन्य डिवाइस पर स्कैन करें",
@ -643,11 +649,22 @@
"sign_up": "साइन अप करें", "sign_up": "साइन अप करें",
"signTransaction": "लेन-देन पर हस्ताक्षर करें", "signTransaction": "लेन-देन पर हस्ताक्षर करें",
"signup_for_card_accept_terms": "कार्ड के लिए साइन अप करें और शर्तें स्वीकार करें।", "signup_for_card_accept_terms": "कार्ड के लिए साइन अप करें और शर्तें स्वीकार करें।",
"silent_payments": "मूक भुगतान",
"silent_payments_always_scan": "मूक भुगतान हमेशा स्कैनिंग सेट करें",
"silent_payments_disclaimer": "नए पते नई पहचान नहीं हैं। यह एक अलग लेबल के साथ एक मौजूदा पहचान का पुन: उपयोग है।",
"silent_payments_display_card": "मूक भुगतान कार्ड दिखाएं",
"silent_payments_scan_from_date": "तिथि से स्कैन करना",
"silent_payments_scan_from_date_or_blockheight": "कृपया उस ब्लॉक ऊंचाई दर्ज करें जिसे आप आने वाले मूक भुगतान के लिए स्कैन करना शुरू करना चाहते हैं, या, इसके बजाय तारीख का उपयोग करें। आप चुन सकते हैं कि क्या वॉलेट हर ब्लॉक को स्कैन करना जारी रखता है, या केवल निर्दिष्ट ऊंचाई की जांच करता है।",
"silent_payments_scan_from_height": "ब्लॉक ऊंचाई से स्कैन करें",
"silent_payments_scanned_tip": "टिप करने के लिए स्कैन किया! (${tip})",
"silent_payments_scanning": "मूक भुगतान स्कैनिंग",
"silent_payments_settings": "मूक भुगतान सेटिंग्स",
"slidable": "फिसलने लायक", "slidable": "फिसलने लायक",
"sort_by": "इसके अनुसार क्रमबद्ध करें", "sort_by": "इसके अनुसार क्रमबद्ध करें",
"spend_key_private": "खर्च करना (निजी)", "spend_key_private": "खर्च करना (निजी)",
"spend_key_public": "खर्च करना (जनता)", "spend_key_public": "खर्च करना (जनता)",
"status": "स्थिति: ", "status": "स्थिति: ",
"string_default": "गलती करना",
"subaddress_title": "उपखंड सूची", "subaddress_title": "उपखंड सूची",
"subaddresses": "उप पते", "subaddresses": "उप पते",
"submit_request": "एक अनुरोध सबमिट करें", "submit_request": "एक अनुरोध सबमिट करें",
@ -672,10 +689,13 @@
"sync_status_starting_sync": "सिताज़ा करना", "sync_status_starting_sync": "सिताज़ा करना",
"sync_status_syncronized": "सिंक्रनाइज़", "sync_status_syncronized": "सिंक्रनाइज़",
"sync_status_syncronizing": "सिंक्रनाइज़ करने", "sync_status_syncronizing": "सिंक्रनाइज़ करने",
"sync_status_timed_out": "समय समााप्त",
"sync_status_unsupported": "असमर्थित नोड",
"syncing_wallet_alert_content": "आपकी शेष राशि और लेनदेन सूची तब तक पूरी नहीं हो सकती जब तक कि शीर्ष पर \"सिंक्रनाइज़्ड\" न लिखा हो। अधिक जानने के लिए क्लिक/टैप करें।", "syncing_wallet_alert_content": "आपकी शेष राशि और लेनदेन सूची तब तक पूरी नहीं हो सकती जब तक कि शीर्ष पर \"सिंक्रनाइज़्ड\" न लिखा हो। अधिक जानने के लिए क्लिक/टैप करें।",
"syncing_wallet_alert_title": "आपका वॉलेट सिंक हो रहा है", "syncing_wallet_alert_title": "आपका वॉलेट सिंक हो रहा है",
"template": "खाका", "template": "खाका",
"template_name": "टेम्पलेट नाम", "template_name": "टेम्पलेट नाम",
"testnet_coins_no_value": "टेस्टनेट सिक्कों का कोई मूल्य नहीं है",
"third_intro_content": "Yats Cake Wallet के बाहर भी रहता है। धरती पर किसी भी वॉलेट पते को Yat से बदला जा सकता है!", "third_intro_content": "Yats Cake Wallet के बाहर भी रहता है। धरती पर किसी भी वॉलेट पते को Yat से बदला जा सकता है!",
"third_intro_title": "Yat दूसरों के साथ अच्छा खेलता है", "third_intro_title": "Yat दूसरों के साथ अच्छा खेलता है",
"thorchain_contract_address_not_supported": "थोरचेन एक अनुबंध पते पर भेजने का समर्थन नहीं करता है", "thorchain_contract_address_not_supported": "थोरचेन एक अनुबंध पते पर भेजने का समर्थन नहीं करता है",
@ -751,13 +771,16 @@
"trusted": "भरोसा", "trusted": "भरोसा",
"tx_commit_exception_no_dust_on_change": "लेनदेन को इस राशि से खारिज कर दिया जाता है। इन सिक्कों के साथ आप चेंज या ${min} के बिना ${max} को भेज सकते हैं जो परिवर्तन लौटाता है।", "tx_commit_exception_no_dust_on_change": "लेनदेन को इस राशि से खारिज कर दिया जाता है। इन सिक्कों के साथ आप चेंज या ${min} के बिना ${max} को भेज सकते हैं जो परिवर्तन लौटाता है।",
"tx_commit_failed": "लेन -देन प्रतिबद्ध विफल। कृपया संपर्क समर्थन करें।", "tx_commit_failed": "लेन -देन प्रतिबद्ध विफल। कृपया संपर्क समर्थन करें।",
"tx_invalid_input": "आप इस प्रकार के भुगतान के लिए गलत इनपुट प्रकार का उपयोग कर रहे हैं",
"tx_no_dust_exception": "लेनदेन को बहुत छोटी राशि भेजकर अस्वीकार कर दिया जाता है। कृपया राशि बढ़ाने का प्रयास करें।", "tx_no_dust_exception": "लेनदेन को बहुत छोटी राशि भेजकर अस्वीकार कर दिया जाता है। कृपया राशि बढ़ाने का प्रयास करें।",
"tx_not_enough_inputs_exception": "पर्याप्त इनपुट उपलब्ध नहीं है। कृपया सिक्का नियंत्रण के तहत अधिक चुनें", "tx_not_enough_inputs_exception": "पर्याप्त इनपुट उपलब्ध नहीं है। कृपया सिक्का नियंत्रण के तहत अधिक चुनें",
"tx_rejected_bip68_final": "लेन -देन में अपुष्ट इनपुट हैं और शुल्क द्वारा प्रतिस्थापित करने में विफल रहे हैं।",
"tx_rejected_dust_change": "नेटवर्क नियमों, कम परिवर्तन राशि (धूल) द्वारा खारिज किए गए लेनदेन। सभी भेजने या राशि को कम करने का प्रयास करें।", "tx_rejected_dust_change": "नेटवर्क नियमों, कम परिवर्तन राशि (धूल) द्वारा खारिज किए गए लेनदेन। सभी भेजने या राशि को कम करने का प्रयास करें।",
"tx_rejected_dust_output": "नेटवर्क नियमों, कम आउटपुट राशि (धूल) द्वारा खारिज किए गए लेनदेन। कृपया राशि बढ़ाएं।", "tx_rejected_dust_output": "नेटवर्क नियमों, कम आउटपुट राशि (धूल) द्वारा खारिज किए गए लेनदेन। कृपया राशि बढ़ाएं।",
"tx_rejected_dust_output_send_all": "नेटवर्क नियमों, कम आउटपुट राशि (धूल) द्वारा खारिज किए गए लेनदेन। कृपया सिक्का नियंत्रण के तहत चुने गए सिक्कों के संतुलन की जाँच करें।", "tx_rejected_dust_output_send_all": "नेटवर्क नियमों, कम आउटपुट राशि (धूल) द्वारा खारिज किए गए लेनदेन। कृपया सिक्का नियंत्रण के तहत चुने गए सिक्कों के संतुलन की जाँच करें।",
"tx_rejected_vout_negative": "इस लेनदेन की फीस के लिए भुगतान करने के लिए पर्याप्त शेष राशि नहीं है। कृपया सिक्के नियंत्रण के तहत सिक्कों के संतुलन की जाँच करें।", "tx_rejected_vout_negative": "इस लेनदेन की फीस के लिए भुगतान करने के लिए पर्याप्त शेष राशि नहीं है। कृपया सिक्के नियंत्रण के तहत सिक्कों के संतुलन की जाँच करें।",
"tx_wrong_balance_exception": "इस राशि को भेजने के लिए आपके पास पर्याप्त ${currency} नहीं है।", "tx_wrong_balance_exception": "इस राशि को भेजने के लिए आपके पास पर्याप्त ${currency} नहीं है।",
"tx_wrong_balance_with_amount_exception": "आपके पास पर्याप्त नहीं है${currency} ${amount} की कुल राशि भेजने के लिए",
"tx_zero_fee_exception": "0 शुल्क के साथ लेनदेन नहीं भेज सकते। नवीनतम अनुमानों के लिए दर बढ़ाने या अपने कनेक्शन की जांच करने का प्रयास करें।", "tx_zero_fee_exception": "0 शुल्क के साथ लेनदेन नहीं भेज सकते। नवीनतम अनुमानों के लिए दर बढ़ाने या अपने कनेक्शन की जांच करने का प्रयास करें।",
"unavailable_balance": "अनुपलब्ध शेष", "unavailable_balance": "अनुपलब्ध शेष",
"unavailable_balance_description": "अनुपलब्ध शेष राशि: इस कुल में वे धनराशि शामिल हैं जो लंबित लेनदेन में बंद हैं और जिन्हें आपने अपनी सिक्का नियंत्रण सेटिंग्स में सक्रिय रूप से जमा कर रखा है। लॉक किए गए शेष उनके संबंधित लेन-देन पूरे होने के बाद उपलब्ध हो जाएंगे, जबकि जमे हुए शेष लेन-देन के लिए अप्राप्य रहेंगे जब तक कि आप उन्हें अनफ्रीज करने का निर्णय नहीं लेते।", "unavailable_balance_description": "अनुपलब्ध शेष राशि: इस कुल में वे धनराशि शामिल हैं जो लंबित लेनदेन में बंद हैं और जिन्हें आपने अपनी सिक्का नियंत्रण सेटिंग्स में सक्रिय रूप से जमा कर रखा है। लॉक किए गए शेष उनके संबंधित लेन-देन पूरे होने के बाद उपलब्ध हो जाएंगे, जबकि जमे हुए शेष लेन-देन के लिए अप्राप्य रहेंगे जब तक कि आप उन्हें अनफ्रीज करने का निर्णय नहीं लेते।",
@ -811,6 +834,7 @@
"warning": "चेतावनी", "warning": "चेतावनी",
"welcome": "स्वागत हे सेवा मेरे", "welcome": "स्वागत हे सेवा मेरे",
"welcome_to_cakepay": "केकपे में आपका स्वागत है!", "welcome_to_cakepay": "केकपे में आपका स्वागत है!",
"what_is_silent_payments": "मूक भुगतान क्या है?",
"widgets_address": "पता", "widgets_address": "पता",
"widgets_or": "या", "widgets_or": "या",
"widgets_restore_from_blockheight": "ब्लॉकचेन से पुनर्स्थापित करें", "widgets_restore_from_blockheight": "ब्लॉकचेन से पुनर्स्थापित करें",

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