mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-11-17 01:37:40 +00:00
Merge branch 'main' of https://github.com/cake-tech/cake_wallet into mweb
This commit is contained in:
commit
ac1fe6b221
122 changed files with 3920 additions and 1259 deletions
2
.github/workflows/pr_test_build.yml
vendored
2
.github/workflows/pr_test_build.yml
vendored
|
@ -42,7 +42,7 @@ jobs:
|
|||
- name: Flutter action
|
||||
uses: subosito/flutter-action@v1
|
||||
with:
|
||||
flutter-version: "3.19.5"
|
||||
flutter-version: "3.19.6"
|
||||
channel: stable
|
||||
|
||||
- name: Install package dependencies
|
||||
|
|
65
assets/images/cards.svg
Normal file
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
BIN
assets/images/tbtc.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
|
@ -1,3 +1,2 @@
|
|||
Add Tron wallet
|
||||
Hardware wallets enhancements
|
||||
Bug fixes
|
||||
Bitcoin Silent Payments
|
||||
Bug fixes and generic enhancements
|
||||
|
|
|
@ -3,8 +3,8 @@ import 'dart:convert';
|
|||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
import 'package:cw_bitcoin/script_hash.dart' as sh;
|
||||
|
||||
class BitcoinAddressRecord {
|
||||
BitcoinAddressRecord(
|
||||
abstract class BaseBitcoinAddressRecord {
|
||||
BaseBitcoinAddressRecord(
|
||||
this.address, {
|
||||
required this.index,
|
||||
this.isHidden = false,
|
||||
|
@ -13,15 +13,62 @@ class BitcoinAddressRecord {
|
|||
String name = '',
|
||||
bool isUsed = false,
|
||||
required this.type,
|
||||
String? scriptHash,
|
||||
required this.network,
|
||||
}) : _txCount = txCount,
|
||||
_balance = balance,
|
||||
_name = name,
|
||||
_isUsed = isUsed,
|
||||
scriptHash = scriptHash ?? sh.scriptHash(address, network: network);
|
||||
_isUsed = isUsed;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
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;
|
||||
|
||||
@override
|
||||
int get hashCode => address.hashCode;
|
||||
|
||||
BitcoinAddressType type;
|
||||
|
||||
String updateScriptHash(BasedUtxoNetwork network) {
|
||||
String getScriptHash(BasedUtxoNetwork network) {
|
||||
if (scriptHash != null) return scriptHash!;
|
||||
scriptHash = sh.scriptHash(address, network: network);
|
||||
return scriptHash!;
|
||||
}
|
||||
|
||||
@override
|
||||
String toJSON() => json.encode({
|
||||
'address': address,
|
||||
'index': index,
|
||||
|
@ -91,3 +109,57 @@ class BitcoinAddressRecord {
|
|||
'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,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -9,6 +9,8 @@ class BitcoinReceivePageOption implements ReceivePageOption {
|
|||
static const p2pkh = BitcoinReceivePageOption._('Legacy (P2PKH)');
|
||||
static const mweb = BitcoinReceivePageOption._('MWEB');
|
||||
|
||||
static const silent_payments = BitcoinReceivePageOption._('Silent Payments');
|
||||
|
||||
const BitcoinReceivePageOption._(this.value);
|
||||
|
||||
final String value;
|
||||
|
@ -18,6 +20,7 @@ class BitcoinReceivePageOption implements ReceivePageOption {
|
|||
}
|
||||
|
||||
static const all = [
|
||||
BitcoinReceivePageOption.silent_payments,
|
||||
BitcoinReceivePageOption.p2wpkh,
|
||||
BitcoinReceivePageOption.p2tr,
|
||||
BitcoinReceivePageOption.p2wsh,
|
||||
|
@ -30,6 +33,24 @@ class BitcoinReceivePageOption implements ReceivePageOption {
|
|||
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) {
|
||||
switch (type) {
|
||||
case SegwitAddresType.p2tr:
|
||||
|
@ -42,6 +63,8 @@ class BitcoinReceivePageOption implements ReceivePageOption {
|
|||
return BitcoinReceivePageOption.p2pkh;
|
||||
case P2shAddressType.p2wpkhInP2sh:
|
||||
return BitcoinReceivePageOption.p2sh;
|
||||
case SilentPaymentsAddresType.p2sp:
|
||||
return BitcoinReceivePageOption.silent_payments;
|
||||
case SegwitAddresType.p2wpkh:
|
||||
default:
|
||||
return BitcoinReceivePageOption.p2wpkh;
|
||||
|
|
|
@ -2,13 +2,66 @@ import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
|||
import 'package:cw_core/unspent_transaction_output.dart';
|
||||
|
||||
class BitcoinUnspent extends Unspent {
|
||||
BitcoinUnspent(BitcoinAddressRecord addressRecord, String hash, int value, int vout)
|
||||
BitcoinUnspent(BaseBitcoinAddressRecord addressRecord, String hash, int value, int vout)
|
||||
: bitcoinAddressRecord = addressRecord,
|
||||
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(
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -39,22 +39,28 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
Map<String, int>? initialRegularAddressIndex,
|
||||
Map<String, int>? initialChangeAddressIndex,
|
||||
String? passphrase,
|
||||
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
|
||||
int initialSilentAddressIndex = 0,
|
||||
bool? alwaysScan,
|
||||
}) : super(
|
||||
mnemonic: mnemonic,
|
||||
passphrase: passphrase,
|
||||
xpub: xpub,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
networkType: networkParam == null
|
||||
? bitcoin.bitcoin
|
||||
: networkParam == BitcoinNetwork.mainnet
|
||||
mnemonic: mnemonic,
|
||||
passphrase: passphrase,
|
||||
xpub: xpub,
|
||||
password: password,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
networkType: networkParam == null
|
||||
? bitcoin.bitcoin
|
||||
: bitcoin.testnet,
|
||||
initialAddresses: initialAddresses,
|
||||
initialBalance: initialBalance,
|
||||
seedBytes: seedBytes,
|
||||
currency: CryptoCurrency.btc) {
|
||||
: networkParam == BitcoinNetwork.mainnet
|
||||
? bitcoin.bitcoin
|
||||
: bitcoin.testnet,
|
||||
initialAddresses: initialAddresses,
|
||||
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)
|
||||
// the sideHd derivation path = m/84'/0'/0'/1 (account 1, index unspecified here)
|
||||
// String derivationPath = walletInfo.derivationInfo!.derivationPath!;
|
||||
|
@ -62,14 +68,18 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
// final hd = bitcoin.HDWallet.fromSeed(seedBytes, network: networkType);
|
||||
walletAddresses = BitcoinWalletAddresses(
|
||||
walletInfo,
|
||||
electrumClient: electrumClient,
|
||||
initialAddresses: initialAddresses,
|
||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||
initialSilentAddresses: initialSilentAddresses,
|
||||
initialSilentAddressIndex: initialSilentAddressIndex,
|
||||
mainHd: hd,
|
||||
sideHd: accountHD.derive(1),
|
||||
network: networkParam ?? network,
|
||||
masterHd:
|
||||
seedBytes != null ? bitcoin.HDWallet.fromSeed(seedBytes, network: networkType) : null,
|
||||
);
|
||||
|
||||
autorun((_) {
|
||||
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
|
||||
});
|
||||
|
@ -84,9 +94,11 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
String? addressPageType,
|
||||
BasedUtxoNetwork? network,
|
||||
List<BitcoinAddressRecord>? initialAddresses,
|
||||
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
|
||||
ElectrumBalance? initialBalance,
|
||||
Map<String, int>? initialRegularAddressIndex,
|
||||
Map<String, int>? initialChangeAddressIndex,
|
||||
int initialSilentAddressIndex = 0,
|
||||
}) async {
|
||||
late Uint8List seedBytes;
|
||||
|
||||
|
@ -109,6 +121,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
initialAddresses: initialAddresses,
|
||||
initialSilentAddresses: initialSilentAddresses,
|
||||
initialSilentAddressIndex: initialSilentAddressIndex,
|
||||
initialBalance: initialBalance,
|
||||
seedBytes: seedBytes,
|
||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||
|
@ -123,6 +137,7 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
required WalletInfo walletInfo,
|
||||
required Box<UnspentCoinsInfo> unspentCoinsInfo,
|
||||
required String password,
|
||||
required bool alwaysScan,
|
||||
}) async {
|
||||
final network = walletInfo.network != null
|
||||
? BasedUtxoNetwork.fromName(walletInfo.network!)
|
||||
|
@ -163,12 +178,15 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfo,
|
||||
initialAddresses: snp.addresses,
|
||||
initialSilentAddresses: snp.silentAddresses,
|
||||
initialSilentAddressIndex: snp.silentAddressIndex,
|
||||
initialBalance: snp.balance,
|
||||
seedBytes: seedBytes,
|
||||
initialRegularAddressIndex: snp.regularAddressIndex,
|
||||
initialChangeAddressIndex: snp.changeAddressIndex,
|
||||
addressPageType: snp.addressPageType,
|
||||
networkParam: network,
|
||||
alwaysScan: alwaysScan,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -179,7 +197,8 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
void setLedger(Ledger setLedger, LedgerDevice setLedgerDevice) {
|
||||
_ledger = setLedger;
|
||||
_ledgerDevice = setLedgerDevice;
|
||||
_bitcoinLedgerApp = BitcoinLedgerApp(_ledger!, derivationPath: walletInfo.derivationInfo!.derivationPath!);
|
||||
_bitcoinLedgerApp =
|
||||
BitcoinLedgerApp(_ledger!, derivationPath: walletInfo.derivationInfo!.derivationPath!);
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -202,16 +221,17 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
final publicKeyAndDerivationPath = publicKeys[utxo.ownerDetails.address.pubKeyHash()]!;
|
||||
|
||||
psbtReadyInputs.add(PSBTReadyUtxoWithAddress(
|
||||
utxo: utxo.utxo,
|
||||
rawTx: rawTx,
|
||||
ownerDetails: utxo.ownerDetails,
|
||||
ownerDerivationPath: publicKeyAndDerivationPath.derivationPath,
|
||||
ownerMasterFingerprint: masterFingerprint,
|
||||
ownerPublicKey: publicKeyAndDerivationPath.publicKey,
|
||||
utxo: utxo.utxo,
|
||||
rawTx: rawTx,
|
||||
ownerDetails: utxo.ownerDetails,
|
||||
ownerDerivationPath: publicKeyAndDerivationPath.derivationPath,
|
||||
ownerMasterFingerprint: masterFingerprint,
|
||||
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);
|
||||
return BtcTransaction.fromRaw(hex.encode(rawHex));
|
||||
|
|
|
@ -15,10 +15,12 @@ abstract class BitcoinWalletAddressesBase extends ElectrumWalletAddresses with S
|
|||
required super.mainHd,
|
||||
required super.sideHd,
|
||||
required super.network,
|
||||
required super.electrumClient,
|
||||
super.initialAddresses,
|
||||
super.initialRegularAddressIndex,
|
||||
super.initialChangeAddressIndex,
|
||||
super.initialSilentAddresses,
|
||||
super.initialSilentAddressIndex = 0,
|
||||
super.masterHd,
|
||||
}) : super(walletInfo);
|
||||
|
||||
@override
|
||||
|
|
|
@ -19,10 +19,11 @@ class BitcoinWalletService extends WalletService<
|
|||
BitcoinRestoreWalletFromSeedCredentials,
|
||||
BitcoinRestoreWalletFromWIFCredentials,
|
||||
BitcoinRestoreWalletFromHardware> {
|
||||
BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource);
|
||||
BitcoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource, this.alwaysScan);
|
||||
|
||||
final Box<WalletInfo> walletInfoSource;
|
||||
final Box<UnspentCoinsInfo> unspentCoinsInfoSource;
|
||||
final bool alwaysScan;
|
||||
|
||||
@override
|
||||
WalletType getType() => WalletType.bitcoin;
|
||||
|
@ -55,20 +56,24 @@ class BitcoinWalletService extends WalletService<
|
|||
.firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!;
|
||||
try {
|
||||
final wallet = await BitcoinWalletBase.open(
|
||||
password: password,
|
||||
name: name,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
password: password,
|
||||
name: name,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
alwaysScan: alwaysScan,
|
||||
);
|
||||
await wallet.init();
|
||||
saveBackup(name);
|
||||
return wallet;
|
||||
} catch (_) {
|
||||
await restoreWalletFilesFromBackup(name);
|
||||
final wallet = await BitcoinWalletBase.open(
|
||||
password: password,
|
||||
name: name,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
password: password,
|
||||
name: name,
|
||||
walletInfo: walletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
alwaysScan: alwaysScan,
|
||||
);
|
||||
await wallet.init();
|
||||
return wallet;
|
||||
}
|
||||
|
@ -87,10 +92,12 @@ class BitcoinWalletService extends WalletService<
|
|||
final currentWalletInfo = walletInfoSource.values
|
||||
.firstWhereOrNull((info) => info.id == WalletBase.idFor(currentName, getType()))!;
|
||||
final currentWallet = await BitcoinWalletBase.open(
|
||||
password: password,
|
||||
name: currentName,
|
||||
walletInfo: currentWalletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource);
|
||||
password: password,
|
||||
name: currentName,
|
||||
walletInfo: currentWalletInfo,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
alwaysScan: alwaysScan,
|
||||
);
|
||||
|
||||
await currentWallet.renameWalletFiles(newName);
|
||||
await saveBackup(newName);
|
||||
|
@ -105,12 +112,13 @@ class BitcoinWalletService extends WalletService<
|
|||
@override
|
||||
Future<BitcoinWallet> restoreFromHardwareWallet(BitcoinRestoreWalletFromHardware credentials,
|
||||
{bool? isTestnet}) async {
|
||||
|
||||
final network = isTestnet == true ? BitcoinNetwork.testnet : BitcoinNetwork.mainnet;
|
||||
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,
|
||||
walletInfo: credentials.walletInfo!,
|
||||
unspentCoinsInfo: unspentCoinsInfoSource,
|
||||
|
@ -123,7 +131,7 @@ class BitcoinWalletService extends WalletService<
|
|||
|
||||
@override
|
||||
Future<BitcoinWallet> restoreFromKeys(BitcoinRestoreWalletFromWIFCredentials credentials,
|
||||
{bool? isTestnet}) async =>
|
||||
{bool? isTestnet}) async =>
|
||||
throw UnimplementedError();
|
||||
|
||||
@override
|
||||
|
|
|
@ -41,23 +41,35 @@ class ElectrumClient {
|
|||
|
||||
bool get isConnected => _isConnected;
|
||||
Socket? socket;
|
||||
void Function(bool)? onConnectionStatusChange;
|
||||
void Function(bool?)? onConnectionStatusChange;
|
||||
int _id;
|
||||
final Map<String, SocketTask> _tasks;
|
||||
Map<String, SocketTask> get tasks => _tasks;
|
||||
final Map<String, String> _errors;
|
||||
bool _isConnected;
|
||||
Timer? _aliveTimer;
|
||||
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 {
|
||||
await socket?.close();
|
||||
} catch (_) {}
|
||||
|
||||
socket = await SecureSocket.connect(host, port,
|
||||
timeout: connectionTimeout, onBadCertificate: (_) => true);
|
||||
if (useSSL == false) {
|
||||
socket = await Socket.connect(host, port, timeout: connectionTimeout);
|
||||
} else {
|
||||
socket = await SecureSocket.connect(host, port,
|
||||
timeout: connectionTimeout, onBadCertificate: (_) => true);
|
||||
}
|
||||
_setIsConnected(true);
|
||||
|
||||
socket!.listen((Uint8List event) {
|
||||
|
@ -79,7 +91,7 @@ class ElectrumClient {
|
|||
_setIsConnected(false);
|
||||
}, onDone: () {
|
||||
unterminatedString = '';
|
||||
_setIsConnected(false);
|
||||
_setIsConnected(null);
|
||||
});
|
||||
keepAlive();
|
||||
}
|
||||
|
@ -134,11 +146,12 @@ class ElectrumClient {
|
|||
await callWithTimeout(method: 'server.ping');
|
||||
_setIsConnected(true);
|
||||
} 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) {
|
||||
return result.map((dynamic val) => val.toString()).toList();
|
||||
}
|
||||
|
@ -266,6 +279,18 @@ class ElectrumClient {
|
|||
Future<Map<String, dynamic>> getHeader({required int height}) async =>
|
||||
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}) =>
|
||||
call(method: 'blockchain.estimatefee', params: [p]).then((dynamic result) {
|
||||
if (result is double) {
|
||||
|
@ -308,9 +333,6 @@ class ElectrumClient {
|
|||
});
|
||||
|
||||
Future<List<int>> feeRates({BasedUtxoNetwork? network}) async {
|
||||
if (network == BitcoinNetwork.testnet) {
|
||||
return [1, 1, 1];
|
||||
}
|
||||
try {
|
||||
final topDoubleString = await estimatefee(p: 1);
|
||||
final middleDoubleString = await estimatefee(p: 5);
|
||||
|
@ -333,6 +355,11 @@ class ElectrumClient {
|
|||
// }
|
||||
BehaviorSubject<Map<String, dynamic>>? tipListener;
|
||||
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 {
|
||||
final method = 'blockchain.headers.subscribe';
|
||||
|
@ -345,6 +372,12 @@ class ElectrumClient {
|
|||
return currentTip;
|
||||
}
|
||||
|
||||
BehaviorSubject<Object>? chainTipSubscribe() {
|
||||
_id += 1;
|
||||
return subscribe<Object>(
|
||||
id: 'blockchain.headers.subscribe', method: 'blockchain.headers.subscribe');
|
||||
}
|
||||
|
||||
BehaviorSubject<Object>? scripthashUpdate(String scripthash) {
|
||||
_id += 1;
|
||||
return subscribe<Object>(
|
||||
|
@ -401,7 +434,9 @@ class ElectrumClient {
|
|||
|
||||
Future<void> close() async {
|
||||
_aliveTimer?.cancel();
|
||||
await socket?.close();
|
||||
try {
|
||||
await socket?.close();
|
||||
} catch (_) {}
|
||||
onConnectionStatusChange = null;
|
||||
}
|
||||
|
||||
|
@ -442,17 +477,25 @@ class ElectrumClient {
|
|||
|
||||
_tasks[id]?.subject?.add(params.last);
|
||||
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:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void _setIsConnected(bool isConnected) {
|
||||
void _setIsConnected(bool? isConnected) {
|
||||
if (_isConnected != isConnected) {
|
||||
onConnectionStatusChange?.call(isConnected);
|
||||
}
|
||||
|
||||
_isConnected = isConnected;
|
||||
_isConnected = isConnected ?? false;
|
||||
}
|
||||
|
||||
void _handleResponse(Map<String, dynamic> response) {
|
||||
|
|
|
@ -3,8 +3,11 @@ import 'package:cw_bitcoin/bitcoin_amount_format.dart';
|
|||
import 'package:cw_core/balance.dart';
|
||||
|
||||
class ElectrumBalance extends Balance {
|
||||
const ElectrumBalance({required this.confirmed, required this.unconfirmed, required this.frozen})
|
||||
: super(confirmed, unconfirmed);
|
||||
ElectrumBalance({
|
||||
required this.confirmed,
|
||||
required this.unconfirmed,
|
||||
required this.frozen,
|
||||
}) : super(confirmed, unconfirmed);
|
||||
|
||||
static ElectrumBalance? fromJSON(String? jsonSource) {
|
||||
if (jsonSource == null) {
|
||||
|
@ -19,8 +22,8 @@ class ElectrumBalance extends Balance {
|
|||
frozen: decoded['frozen'] as int? ?? 0);
|
||||
}
|
||||
|
||||
final int confirmed;
|
||||
final int unconfirmed;
|
||||
int confirmed;
|
||||
int unconfirmed;
|
||||
final int frozen;
|
||||
|
||||
@override
|
||||
|
|
|
@ -11,13 +11,11 @@ part 'electrum_transaction_history.g.dart';
|
|||
|
||||
const transactionsHistoryFileName = 'transactions.json';
|
||||
|
||||
class ElectrumTransactionHistory = ElectrumTransactionHistoryBase
|
||||
with _$ElectrumTransactionHistory;
|
||||
class ElectrumTransactionHistory = ElectrumTransactionHistoryBase with _$ElectrumTransactionHistory;
|
||||
|
||||
abstract class ElectrumTransactionHistoryBase
|
||||
extends TransactionHistoryBase<ElectrumTransactionInfo> with Store {
|
||||
ElectrumTransactionHistoryBase(
|
||||
{required this.walletInfo, required String password})
|
||||
ElectrumTransactionHistoryBase({required this.walletInfo, required String password})
|
||||
: _password = password,
|
||||
_height = 0 {
|
||||
transactions = ObservableMap<String, ElectrumTransactionInfo>();
|
||||
|
@ -30,8 +28,7 @@ abstract class ElectrumTransactionHistoryBase
|
|||
Future<void> init() async => await _load();
|
||||
|
||||
@override
|
||||
void addOne(ElectrumTransactionInfo transaction) =>
|
||||
transactions[transaction.id] = transaction;
|
||||
void addOne(ElectrumTransactionInfo transaction) => transactions[transaction.id] = transaction;
|
||||
|
||||
@override
|
||||
void addMany(Map<String, ElectrumTransactionInfo> transactions) =>
|
||||
|
@ -40,11 +37,13 @@ abstract class ElectrumTransactionHistoryBase
|
|||
@override
|
||||
Future<void> save() async {
|
||||
try {
|
||||
final dirPath =
|
||||
await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
||||
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
||||
final path = '$dirPath/$transactionsHistoryFileName';
|
||||
final data =
|
||||
json.encode({'height': _height, 'transactions': transactions});
|
||||
final txjson = {};
|
||||
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);
|
||||
} catch (e) {
|
||||
print('Error while save bitcoin transaction history: ${e.toString()}');
|
||||
|
@ -57,8 +56,7 @@ abstract class ElectrumTransactionHistoryBase
|
|||
}
|
||||
|
||||
Future<Map<String, dynamic>> _read() async {
|
||||
final dirPath =
|
||||
await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
||||
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
||||
final path = '$dirPath/$transactionsHistoryFileName';
|
||||
final content = await read(path: path, password: _password);
|
||||
return json.decode(content) as Map<String, dynamic>;
|
||||
|
@ -84,7 +82,5 @@ abstract class ElectrumTransactionHistoryBase
|
|||
}
|
||||
}
|
||||
|
||||
void _update(ElectrumTransactionInfo transaction) =>
|
||||
transactions[transaction.id] = transaction;
|
||||
|
||||
void _update(ElectrumTransactionInfo transaction) => transactions[transaction.id] = transaction;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
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/bitcoin_address_record.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_info.dart';
|
||||
import 'package:cw_core/format_amount.dart';
|
||||
|
@ -19,6 +18,8 @@ class ElectrumTransactionBundle {
|
|||
}
|
||||
|
||||
class ElectrumTransactionInfo extends TransactionInfo {
|
||||
List<BitcoinSilentPaymentsUnspent>? unspents;
|
||||
|
||||
ElectrumTransactionInfo(this.type,
|
||||
{required String id,
|
||||
required int height,
|
||||
|
@ -29,7 +30,9 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
|||
required TransactionDirection direction,
|
||||
required bool isPending,
|
||||
required DateTime date,
|
||||
required int confirmations}) {
|
||||
required int confirmations,
|
||||
String? to,
|
||||
this.unspents}) {
|
||||
this.id = id;
|
||||
this.height = height;
|
||||
this.amount = amount;
|
||||
|
@ -40,6 +43,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
|||
this.date = date;
|
||||
this.isPending = isPending;
|
||||
this.confirmations = confirmations;
|
||||
this.to = to;
|
||||
}
|
||||
|
||||
factory ElectrumTransactionInfo.fromElectrumVerbose(Map<String, Object> obj, WalletType type,
|
||||
|
@ -153,52 +157,31 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
|||
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) {
|
||||
return ElectrumTransactionInfo(type,
|
||||
id: data['id'] as String,
|
||||
height: data['height'] as int,
|
||||
amount: data['amount'] as int,
|
||||
fee: data['fee'] as int,
|
||||
direction: parseTransactionDirectionFromInt(data['direction'] as int),
|
||||
date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
|
||||
isPending: data['isPending'] as bool,
|
||||
inputAddresses: List<String>.from(data['inputAddresses'] as List),
|
||||
outputAddresses: List<String>.from(data['outputAddresses'] as List),
|
||||
confirmations: data['confirmations'] as int);
|
||||
final inputAddresses = data['inputAddresses'] as List<dynamic>? ?? [];
|
||||
final outputAddresses = data['outputAddresses'] as List<dynamic>? ?? [];
|
||||
final unspents = data['unspents'] as List<dynamic>? ?? [];
|
||||
|
||||
return ElectrumTransactionInfo(
|
||||
type,
|
||||
id: data['id'] as String,
|
||||
height: data['height'] as int,
|
||||
amount: data['amount'] as int,
|
||||
fee: data['fee'] 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;
|
||||
|
@ -244,8 +227,14 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
|||
m['isPending'] = isPending;
|
||||
m['confirmations'] = confirmations;
|
||||
m['fee'] = fee;
|
||||
m['to'] = to;
|
||||
m['unspents'] = unspents?.map((e) => e.toJson()).toList() ?? [];
|
||||
m['inputAddresses'] = inputAddresses;
|
||||
m['outputAddresses'] = outputAddresses;
|
||||
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
|
@ -1,7 +1,7 @@
|
|||
import 'package:bitcoin_base/bitcoin_base.dart';
|
||||
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/electrum.dart';
|
||||
import 'package:cw_core/wallet_addresses.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
|
@ -25,15 +25,17 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
WalletInfo walletInfo, {
|
||||
required this.mainHd,
|
||||
required this.sideHd,
|
||||
required this.electrumClient,
|
||||
required this.network,
|
||||
List<BitcoinAddressRecord>? initialAddresses,
|
||||
Map<String, int>? initialRegularAddressIndex,
|
||||
Map<String, int>? initialChangeAddressIndex,
|
||||
List<BitcoinSilentPaymentAddressRecord>? initialSilentAddresses,
|
||||
int initialSilentAddressIndex = 0,
|
||||
bitcoin.HDWallet? masterHd,
|
||||
BitcoinAddressType? initialAddressPageType,
|
||||
}) : _addresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? []).toSet()),
|
||||
addressesByReceiveType =
|
||||
ObservableList<BitcoinAddressRecord>.of((<BitcoinAddressRecord>[]).toSet()),
|
||||
ObservableList<BaseBitcoinAddressRecord>.of((<BitcoinAddressRecord>[]).toSet()),
|
||||
receiveAddresses = ObservableList<BitcoinAddressRecord>.of((initialAddresses ?? [])
|
||||
.where((addressRecord) => !addressRecord.isHidden && !addressRecord.isUsed)
|
||||
.toSet()),
|
||||
|
@ -46,7 +48,38 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
(walletInfo.addressPageType != null
|
||||
? BitcoinAddressType.fromValue(walletInfo.addressPageType!)
|
||||
: SegwitAddresType.p2wpkh),
|
||||
silentAddresses = ObservableList<BitcoinSilentPaymentAddressRecord>.of(
|
||||
(initialSilentAddresses ?? []).toSet()),
|
||||
currentSilentAddressIndex = initialSilentAddressIndex,
|
||||
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();
|
||||
}
|
||||
|
||||
|
@ -55,27 +88,40 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
static const gap = 20;
|
||||
|
||||
final ObservableList<BitcoinAddressRecord> _addresses;
|
||||
// Matched by addressPageType
|
||||
late ObservableList<BitcoinAddressRecord> addressesByReceiveType;
|
||||
late ObservableList<BaseBitcoinAddressRecord> addressesByReceiveType;
|
||||
final ObservableList<BitcoinAddressRecord> receiveAddresses;
|
||||
final ObservableList<BitcoinAddressRecord> changeAddresses;
|
||||
final ElectrumClient electrumClient;
|
||||
final ObservableList<BitcoinSilentPaymentAddressRecord> silentAddresses;
|
||||
final BasedUtxoNetwork network;
|
||||
final bitcoin.HDWallet mainHd;
|
||||
final bitcoin.HDWallet sideHd;
|
||||
|
||||
@observable
|
||||
SilentPaymentOwner? silentAddress;
|
||||
|
||||
@observable
|
||||
late BitcoinAddressType _addressPageType;
|
||||
|
||||
@computed
|
||||
BitcoinAddressType get addressPageType => _addressPageType;
|
||||
|
||||
@observable
|
||||
String? activeSilentAddress;
|
||||
|
||||
@computed
|
||||
List<BitcoinAddressRecord> get allAddresses => _addresses;
|
||||
|
||||
@override
|
||||
@computed
|
||||
String get address {
|
||||
if (addressPageType == SilentPaymentsAddresType.p2sp) {
|
||||
if (activeSilentAddress != null) {
|
||||
return activeSilentAddress!;
|
||||
}
|
||||
|
||||
return silentAddress.toString();
|
||||
}
|
||||
|
||||
String receiveAddress;
|
||||
|
||||
final typeMatchingReceiveAddresses =
|
||||
|
@ -104,6 +150,18 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
|
||||
@override
|
||||
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);
|
||||
|
||||
previousAddressRecord = addressRecord;
|
||||
|
@ -130,6 +188,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
void set currentChangeAddressIndex(int index) =>
|
||||
currentChangeAddressIndexByType[_addressPageType.toString()] = index;
|
||||
|
||||
int currentSilentAddressIndex;
|
||||
|
||||
@observable
|
||||
BitcoinAddressRecord? previousAddressRecord;
|
||||
|
||||
|
@ -198,7 +258,50 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
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(
|
||||
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 {
|
||||
try {
|
||||
addressesMap.clear();
|
||||
addressesMap[address] = '';
|
||||
addressesMap[address] = 'Active';
|
||||
|
||||
allAddressesMap.clear();
|
||||
_addresses.forEach((addressRecord) {
|
||||
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();
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
|
@ -241,18 +402,41 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
|
||||
@action
|
||||
void updateAddress(String address, String label) {
|
||||
final addressRecord =
|
||||
_addresses.firstWhere((addressRecord) => addressRecord.address == address);
|
||||
addressRecord.setNewName(label);
|
||||
final index = _addresses.indexOf(addressRecord);
|
||||
_addresses.remove(addressRecord);
|
||||
_addresses.insert(index, addressRecord);
|
||||
BaseBitcoinAddressRecord? foundAddress;
|
||||
_addresses.forEach((addressRecord) {
|
||||
if (addressRecord.address == address) {
|
||||
foundAddress = 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
|
||||
void updateAddressesByMatch() {
|
||||
if (addressPageType == SilentPaymentsAddresType.p2sp) {
|
||||
addressesByReceiveType.clear();
|
||||
addressesByReceiveType.addAll(silentAddresses);
|
||||
return;
|
||||
}
|
||||
|
||||
addressesByReceiveType.clear();
|
||||
addressesByReceiveType.addAll(_addresses.where(_isAddressPageTypeMatch).toList());
|
||||
}
|
||||
|
@ -278,7 +462,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
|
||||
@action
|
||||
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 {
|
||||
if (!isHidden) {
|
||||
_validateSideHdAddresses(addressList.toList());
|
||||
|
@ -288,8 +472,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
startIndex: addressList.length, isHidden: isHidden, type: type);
|
||||
addAddresses(newAddresses);
|
||||
|
||||
final addressesWithHistory = await Future.wait(newAddresses
|
||||
.map((addr) => getAddressHistory(addr, _addresses.map((e) => e.address).toSet())));
|
||||
final addressesWithHistory = await Future.wait(newAddresses.map(getAddressHistory));
|
||||
final isLastAddressUsed = addressesWithHistory.last == addressList.last.address;
|
||||
|
||||
if (isLastAddressUsed) {
|
||||
|
@ -355,6 +538,15 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
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) {
|
||||
addrWithTransactions.forEach((element) {
|
||||
if (element.address !=
|
||||
|
@ -377,4 +569,15 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
|
||||
bitcoin.HDWallet _getHd(bool isHidden) => isHidden ? sideHd : mainHd;
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,8 @@ class ElectrumWalletSnapshot {
|
|||
required this.regularAddressIndex,
|
||||
required this.changeAddressIndex,
|
||||
required this.addressPageType,
|
||||
required this.silentAddresses,
|
||||
required this.silentAddressIndex,
|
||||
this.passphrase,
|
||||
this.derivationType,
|
||||
this.derivationPath,
|
||||
|
@ -32,9 +34,11 @@ class ElectrumWalletSnapshot {
|
|||
String? mnemonic;
|
||||
String? xpub;
|
||||
List<BitcoinAddressRecord> addresses;
|
||||
List<BitcoinSilentPaymentAddressRecord> silentAddresses;
|
||||
ElectrumBalance balance;
|
||||
Map<String, int> regularAddressIndex;
|
||||
Map<String, int> changeAddressIndex;
|
||||
int silentAddressIndex;
|
||||
String? passphrase;
|
||||
DerivationType? derivationType;
|
||||
String? derivationPath;
|
||||
|
@ -50,15 +54,23 @@ class ElectrumWalletSnapshot {
|
|||
final passphrase = data['passphrase'] as String? ?? '';
|
||||
final addresses = addressesTmp
|
||||
.whereType<String>()
|
||||
.map((addr) => BitcoinAddressRecord.fromJSON(addr, network))
|
||||
.map((addr) => BitcoinAddressRecord.fromJSON(addr, network: network))
|
||||
.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);
|
||||
var regularAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0};
|
||||
var changeAddressIndexByType = {SegwitAddresType.p2wpkh.toString(): 0};
|
||||
var silentAddressIndex = 0;
|
||||
|
||||
final derivationType =
|
||||
DerivationType.values[(data['derivationTypeIndex'] as int?) ?? DerivationType.electrum.index];
|
||||
final derivationType = DerivationType
|
||||
.values[(data['derivationTypeIndex'] as int?) ?? DerivationType.electrum.index];
|
||||
final derivationPath = data['derivationPath'] as String? ?? "m/0'/0";
|
||||
|
||||
try {
|
||||
|
@ -69,6 +81,7 @@ class ElectrumWalletSnapshot {
|
|||
SegwitAddresType.p2wpkh.toString():
|
||||
int.parse(data['change_address_index'] as String? ?? '0')
|
||||
};
|
||||
silentAddressIndex = int.parse(data['silent_address_index'] as String? ?? '0');
|
||||
} catch (_) {
|
||||
try {
|
||||
regularAddressIndexByType = data["account_index"] as Map<String, int>? ?? {};
|
||||
|
@ -90,6 +103,8 @@ class ElectrumWalletSnapshot {
|
|||
addressPageType: data['address_page_type'] as String?,
|
||||
derivationType: derivationType,
|
||||
derivationPath: derivationPath,
|
||||
silentAddresses: silentAddresses,
|
||||
silentAddressIndex: silentAddressIndex,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import 'package:cw_core/crypto_currency.dart';
|
|||
import 'package:cw_core/exceptions.dart';
|
||||
|
||||
class BitcoinTransactionWrongBalanceException extends TransactionWrongBalanceException {
|
||||
BitcoinTransactionWrongBalanceException() : super(CryptoCurrency.btc);
|
||||
BitcoinTransactionWrongBalanceException({super.amount}) : super(CryptoCurrency.btc);
|
||||
}
|
||||
|
||||
class BitcoinTransactionNoInputsException extends TransactionNoInputsException {}
|
||||
|
@ -27,3 +27,7 @@ class BitcoinTransactionCommitFailedDustOutputSendAll
|
|||
extends TransactionCommitFailedDustOutputSendAll {}
|
||||
|
||||
class BitcoinTransactionCommitFailedVoutNegative extends TransactionCommitFailedVoutNegative {}
|
||||
|
||||
class BitcoinTransactionCommitFailedBIP68Final extends TransactionCommitFailedBIP68Final {}
|
||||
|
||||
class BitcoinTransactionSilentPaymentsNotSupported extends TransactionInputNotSupported {}
|
||||
|
|
|
@ -63,7 +63,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
|
|||
currency: CryptoCurrency.ltc) {
|
||||
walletAddresses = LitecoinWalletAddresses(
|
||||
walletInfo,
|
||||
electrumClient: electrumClient,
|
||||
initialAddresses: initialAddresses,
|
||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||
|
|
|
@ -19,7 +19,6 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with
|
|||
required super.sideHd,
|
||||
required this.mwebHd,
|
||||
required super.network,
|
||||
required super.electrumClient,
|
||||
super.initialAddresses,
|
||||
super.initialRegularAddressIndex,
|
||||
super.initialChangeAddressIndex,
|
||||
|
|
|
@ -79,6 +79,11 @@ class PendingBitcoinTransaction with PendingTransaction {
|
|||
if (error.contains("bad-txns-vout-negative")) {
|
||||
throw BitcoinTransactionCommitFailedVoutNegative();
|
||||
}
|
||||
|
||||
if (error.contains("non-BIP68-final")) {
|
||||
throw BitcoinTransactionCommitFailedBIP68Final();
|
||||
}
|
||||
|
||||
throw BitcoinTransactionCommitFailed(errorMessage: error);
|
||||
}
|
||||
|
||||
|
|
|
@ -37,10 +37,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: asn1lib
|
||||
sha256: c9c85fedbe2188b95133cbe960e16f5f448860f7133330e272edbbca5893ddc6
|
||||
sha256: "58082b3f0dca697204dbab0ef9ff208bfaea7767ea771076af9a343488428dda"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.2"
|
||||
version: "1.5.3"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -87,11 +87,11 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: cake-mweb
|
||||
resolved-ref: "9389210f4c0376ac6f5dfe5aeabc042adc76449c"
|
||||
url: "https://github.com/cake-tech/bitcoin_base.git"
|
||||
ref: cake-update-v3
|
||||
resolved-ref: cc99eedb1d28ee9376dda0465ef72aa627ac6149
|
||||
url: "https://github.com/cake-tech/bitcoin_base"
|
||||
source: git
|
||||
version: "4.2.0"
|
||||
version: "4.2.1"
|
||||
bitcoin_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -104,11 +104,12 @@ packages:
|
|||
blockchain_utils:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: blockchain_utils
|
||||
sha256: "38ef5f4a22441ac4370aed9071dc71c460acffc37c79b344533f67d15f24c13c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.1"
|
||||
path: "."
|
||||
ref: cake-update-v1
|
||||
resolved-ref: cabd7e0e16c4da9920338c76eff3aeb8af0211f3
|
||||
url: "https://github.com/cake-tech/blockchain_utils"
|
||||
source: git
|
||||
version: "2.1.2"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -205,6 +206,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -249,10 +258,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: cryptography
|
||||
sha256: df156c5109286340817d21fa7b62f9140f17915077127dd70f8bd7a2a0997a35
|
||||
sha256: d146b76d33d94548cf035233fbc2f4338c1242fa119013bead807d033fc4ae05
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.0"
|
||||
version: "2.7.0"
|
||||
cw_core:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -303,10 +312,18 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878"
|
||||
sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21"
|
||||
url: "https://pub.dev"
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -361,10 +378,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: functional_data
|
||||
sha256: aefdec4365452283b2a7cf420a3169654d51d3e9553069a22d76680d7a9d7c3d
|
||||
sha256: "76d17dc707c40e552014f5a49c0afcc3f1e3f05e800cd6b7872940bfe41a5039"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
version: "1.2.0"
|
||||
glob:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -425,18 +442,10 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: http
|
||||
sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525"
|
||||
sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
http2:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http2
|
||||
sha256: "9ced024a160b77aba8fb8674e38f70875e321d319e6f303ec18e87bd5a4b0c1d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
version: "1.2.1"
|
||||
http_multi_server:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -481,10 +490,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: json_annotation
|
||||
sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467
|
||||
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.8.1"
|
||||
version: "4.9.0"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -570,10 +579,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: mime
|
||||
sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e
|
||||
sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
version: "1.0.5"
|
||||
mobx:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -618,26 +627,26 @@ packages:
|
|||
dependency: "direct main"
|
||||
description:
|
||||
name: path_provider
|
||||
sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b
|
||||
sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
version: "2.1.3"
|
||||
path_provider_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668"
|
||||
sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.2"
|
||||
version: "2.2.4"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_foundation
|
||||
sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f"
|
||||
sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
version: "2.4.0"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -682,10 +691,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: pointycastle
|
||||
sha256: "70fe966348fe08c34bf929582f1d8247d9d9408130723206472b4687227e4333"
|
||||
sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.8.0"
|
||||
version: "3.9.1"
|
||||
pool:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -726,6 +735,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -775,10 +792,10 @@ packages:
|
|||
dependency: transitive
|
||||
description:
|
||||
name: socks5_proxy
|
||||
sha256: "1d21b5606169654bbf4cfb904e8e6ed897e9f763358709f87310c757096d909a"
|
||||
sha256: "045cbba84f6e2b01c1c77634a63e926352bf110ef5f07fc462c6d43bbd4b6a83"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
version: "1.0.5+dev.2"
|
||||
source_gen:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -803,6 +820,15 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -899,22 +925,30 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web_socket_channel
|
||||
sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
|
||||
sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
version: "2.4.5"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3"
|
||||
sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.9"
|
||||
version: "5.5.0"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -931,6 +965,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dart: ">=3.2.0-0 <4.0.0"
|
||||
flutter: ">=3.10.0"
|
||||
dart: ">=3.3.0 <4.0.0"
|
||||
flutter: ">=3.16.6"
|
||||
|
|
|
@ -32,9 +32,12 @@ dependencies:
|
|||
cryptography: ^2.0.5
|
||||
bitcoin_base:
|
||||
git:
|
||||
url: https://github.com/cake-tech/bitcoin_base.git
|
||||
url: https://github.com/cake-tech/bitcoin_base
|
||||
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_bitcoin:
|
||||
git:
|
||||
|
@ -42,6 +45,10 @@ dependencies:
|
|||
cw_mweb:
|
||||
path: ../cw_mweb
|
||||
grpc: ^3.2.4
|
||||
sp_scanner:
|
||||
git:
|
||||
url: https://github.com/cake-tech/sp_scanner
|
||||
ref: sp_v2.0.0
|
||||
|
||||
|
||||
dev_dependencies:
|
||||
|
|
|
@ -46,7 +46,6 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store {
|
|||
currency: CryptoCurrency.bch) {
|
||||
walletAddresses = BitcoinCashWalletAddresses(
|
||||
walletInfo,
|
||||
electrumClient: electrumClient,
|
||||
initialAddresses: initialAddresses,
|
||||
initialRegularAddressIndex: initialRegularAddressIndex,
|
||||
initialChangeAddressIndex: initialChangeAddressIndex,
|
||||
|
|
|
@ -15,7 +15,6 @@ abstract class BitcoinCashWalletAddressesBase extends ElectrumWalletAddresses wi
|
|||
required super.mainHd,
|
||||
required super.sideHd,
|
||||
required super.network,
|
||||
required super.electrumClient,
|
||||
super.initialAddresses,
|
||||
super.initialRegularAddressIndex,
|
||||
super.initialChangeAddressIndex,
|
||||
|
|
|
@ -31,10 +31,12 @@ dependencies:
|
|||
ref: Add-Support-For-OP-Return-data
|
||||
bitcoin_base:
|
||||
git:
|
||||
url: https://github.com/cake-tech/bitcoin_base.git
|
||||
url: https://github.com/cake-tech/bitcoin_base
|
||||
ref: cake-mweb
|
||||
|
||||
|
||||
blockchain_utils:
|
||||
git:
|
||||
url: https://github.com/cake-tech/blockchain_utils
|
||||
ref: cake-update-v1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
|
|
@ -104,6 +104,7 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
|
|||
CryptoCurrency.digibyte,
|
||||
CryptoCurrency.usdtSol,
|
||||
CryptoCurrency.usdcTrc20,
|
||||
CryptoCurrency.tbtc,
|
||||
];
|
||||
|
||||
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 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 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 =
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
|
||||
CryptoCurrency currencyForWalletType(WalletType type) {
|
||||
CryptoCurrency currencyForWalletType(WalletType type, {bool? isTestnet}) {
|
||||
switch (type) {
|
||||
case WalletType.bitcoin:
|
||||
if (isTestnet == true) {
|
||||
return CryptoCurrency.tbtc;
|
||||
}
|
||||
return CryptoCurrency.btc;
|
||||
case WalletType.monero:
|
||||
return CryptoCurrency.xmr;
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import 'package:cw_core/crypto_currency.dart';
|
||||
|
||||
class TransactionWrongBalanceException implements Exception {
|
||||
TransactionWrongBalanceException(this.currency);
|
||||
TransactionWrongBalanceException(this.currency, {this.amount});
|
||||
|
||||
final CryptoCurrency currency;
|
||||
final int? amount;
|
||||
}
|
||||
|
||||
class TransactionNoInputsException implements Exception {}
|
||||
|
@ -32,3 +33,7 @@ class TransactionCommitFailedDustOutput implements Exception {}
|
|||
class TransactionCommitFailedDustOutputSendAll implements Exception {}
|
||||
|
||||
class TransactionCommitFailedVoutNegative implements Exception {}
|
||||
|
||||
class TransactionCommitFailedBIP68Final implements Exception {}
|
||||
|
||||
class TransactionInputNotSupported implements Exception {}
|
||||
|
|
|
@ -242,3 +242,57 @@ Future<int> getHavenCurrentHeight() async {
|
|||
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;
|
||||
}
|
||||
|
|
|
@ -244,8 +244,12 @@ class Node extends HiveObject with Keyable {
|
|||
|
||||
Future<bool> requestElectrumServer() async {
|
||||
try {
|
||||
await SecureSocket.connect(uri.host, uri.port,
|
||||
timeout: Duration(seconds: 5), onBadCertificate: (_) => true);
|
||||
if (useSSL == 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;
|
||||
} catch (_) {
|
||||
return false;
|
||||
|
|
|
@ -14,6 +14,16 @@ class SyncingSyncStatus extends SyncStatus {
|
|||
|
||||
@override
|
||||
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 {
|
||||
|
@ -21,6 +31,17 @@ class SyncedSyncStatus extends SyncStatus {
|
|||
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 {
|
||||
const NotConnectedSyncStatus();
|
||||
|
||||
|
@ -33,10 +54,7 @@ class AttemptingSyncStatus extends SyncStatus {
|
|||
double progress() => 0.0;
|
||||
}
|
||||
|
||||
class FailedSyncStatus extends SyncStatus {
|
||||
@override
|
||||
double progress() => 1.0;
|
||||
}
|
||||
class FailedSyncStatus extends NotConnectedSyncStatus {}
|
||||
|
||||
class ConnectingSyncStatus extends SyncStatus {
|
||||
@override
|
||||
|
@ -48,7 +66,14 @@ class ConnectedSyncStatus extends SyncStatus {
|
|||
double progress() => 0.0;
|
||||
}
|
||||
|
||||
class LostConnectionSyncStatus extends SyncStatus {
|
||||
class UnsupportedSyncStatus extends NotConnectedSyncStatus {}
|
||||
|
||||
class TimedOutSyncStatus extends NotConnectedSyncStatus {
|
||||
@override
|
||||
double progress() => 1.0;
|
||||
String toString() => 'Timed out';
|
||||
}
|
||||
|
||||
class LostConnectionSyncStatus extends NotConnectedSyncStatus {
|
||||
@override
|
||||
String toString() => 'Reconnecting';
|
||||
}
|
|
@ -16,7 +16,8 @@ class UnspentCoinsInfo extends HiveObject {
|
|||
required this.value,
|
||||
this.keyImage = null,
|
||||
this.isChange = false,
|
||||
this.accountIndex = 0
|
||||
this.accountIndex = 0,
|
||||
this.isSilentPayment = false,
|
||||
});
|
||||
|
||||
static const typeId = UNSPENT_COINS_INFO_TYPE_ID;
|
||||
|
@ -56,6 +57,9 @@ class UnspentCoinsInfo extends HiveObject {
|
|||
@HiveField(10, defaultValue: 0)
|
||||
int accountIndex;
|
||||
|
||||
@HiveField(11, defaultValue: false)
|
||||
bool? isSilentPayment;
|
||||
|
||||
String get note => noteRaw ?? '';
|
||||
|
||||
set note(String value) => noteRaw = value;
|
||||
|
|
|
@ -17,5 +17,6 @@ class Unspent {
|
|||
int? confirmations;
|
||||
String note;
|
||||
|
||||
bool get isP2wpkh => address.startsWith('bc') || address.startsWith('ltc');
|
||||
bool get isP2wpkh =>
|
||||
address.startsWith('bc') || address.startsWith('tb') || address.startsWith('ltc');
|
||||
}
|
||||
|
|
|
@ -24,7 +24,7 @@ abstract class WalletBase<BalanceType extends Balance, HistoryType extends Trans
|
|||
|
||||
WalletType get type => walletInfo.type;
|
||||
|
||||
CryptoCurrency get currency => currencyForWalletType(type);
|
||||
CryptoCurrency get currency => currencyForWalletType(type, isTestnet: isTestnet);
|
||||
|
||||
String get id => walletInfo.id;
|
||||
|
||||
|
|
|
@ -66,21 +66,21 @@ class DerivationInfo extends HiveObject {
|
|||
@HiveType(typeId: WalletInfo.typeId)
|
||||
class WalletInfo extends HiveObject {
|
||||
WalletInfo(
|
||||
this.id,
|
||||
this.name,
|
||||
this.type,
|
||||
this.isRecovery,
|
||||
this.restoreHeight,
|
||||
this.timestamp,
|
||||
this.dirPath,
|
||||
this.path,
|
||||
this.address,
|
||||
this.yatEid,
|
||||
this.yatLastUsedAddressRaw,
|
||||
this.showIntroCakePayCard,
|
||||
this.derivationInfo,
|
||||
this.hardwareWalletType,
|
||||
): _yatLastUsedAddressController = StreamController<String>.broadcast();
|
||||
this.id,
|
||||
this.name,
|
||||
this.type,
|
||||
this.isRecovery,
|
||||
this.restoreHeight,
|
||||
this.timestamp,
|
||||
this.dirPath,
|
||||
this.path,
|
||||
this.address,
|
||||
this.yatEid,
|
||||
this.yatLastUsedAddressRaw,
|
||||
this.showIntroCakePayCard,
|
||||
this.derivationInfo,
|
||||
this.hardwareWalletType,
|
||||
) : _yatLastUsedAddressController = StreamController<String>.broadcast();
|
||||
|
||||
factory WalletInfo.external({
|
||||
required String id,
|
||||
|
@ -207,4 +207,9 @@ class WalletInfo extends HiveObject {
|
|||
Stream<String> get yatLastUsedAddressStream => _yatLastUsedAddressController.stream;
|
||||
|
||||
StreamController<String> _yatLastUsedAddressController;
|
||||
|
||||
Future<void> updateRestoreHeight(int height) async {
|
||||
restoreHeight = height;
|
||||
await save();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -173,11 +173,14 @@ String walletTypeToDisplayName(WalletType type) {
|
|||
}
|
||||
}
|
||||
|
||||
CryptoCurrency walletTypeToCryptoCurrency(WalletType type) {
|
||||
CryptoCurrency walletTypeToCryptoCurrency(WalletType type, {bool isTestnet = false}) {
|
||||
switch (type) {
|
||||
case WalletType.monero:
|
||||
return CryptoCurrency.xmr;
|
||||
case WalletType.bitcoin:
|
||||
if (isTestnet) {
|
||||
return CryptoCurrency.tbtc;
|
||||
}
|
||||
return CryptoCurrency.btc;
|
||||
case WalletType.litecoin:
|
||||
return CryptoCurrency.ltc;
|
||||
|
|
|
@ -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"
|
|
@ -15,8 +15,14 @@ dependencies:
|
|||
path: ../cw_core
|
||||
cw_evm:
|
||||
path: ../cw_evm
|
||||
on_chain: ^3.0.1
|
||||
blockchain_utils: ^2.1.1
|
||||
on_chain:
|
||||
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
|
||||
bip39: ^1.0.6
|
||||
hive: ^2.2.3
|
||||
|
|
|
@ -142,27 +142,9 @@ Then we need to generate localization files.
|
|||
|
||||
`$ 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:
|
||||
|
||||
`$ flutter packages pub run build_runner build --delete-conflicting-outputs`
|
||||
`$ ./model_generator.sh`
|
||||
|
||||
### 9. Build!
|
||||
|
||||
|
|
|
@ -147,6 +147,8 @@ PODS:
|
|||
- shared_preferences_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- sp_scanner (0.0.1):
|
||||
- Flutter
|
||||
- SwiftProtobuf (1.25.2)
|
||||
- SwiftyGif (5.4.4)
|
||||
- Toast (4.1.0)
|
||||
|
@ -188,6 +190,7 @@ DEPENDENCIES:
|
|||
- sensitive_clipboard (from `.symlinks/plugins/sensitive_clipboard/ios`)
|
||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||
- 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`)
|
||||
- UnstoppableDomainsResolution (~> 4.0.0)
|
||||
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
|
||||
|
@ -259,6 +262,8 @@ EXTERNAL SOURCES:
|
|||
:path: ".symlinks/plugins/share_plus/ios"
|
||||
shared_preferences_foundation:
|
||||
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
|
||||
sp_scanner:
|
||||
:path: ".symlinks/plugins/sp_scanner/ios"
|
||||
uni_links:
|
||||
:path: ".symlinks/plugins/uni_links/ios"
|
||||
url_launcher_ios:
|
||||
|
@ -302,6 +307,7 @@ SPEC CHECKSUMS:
|
|||
sensitive_clipboard: d4866e5d176581536c27bb1618642ee83adca986
|
||||
share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68
|
||||
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||
sp_scanner: eaa617fa827396b967116b7f1f43549ca62e9a12
|
||||
SwiftProtobuf: 407a385e97fd206c4fbe880cc84123989167e0d1
|
||||
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
|
||||
Toast: ec33c32b8688982cecc6348adeae667c1b9938da
|
||||
|
|
|
@ -121,20 +121,12 @@ class CWBitcoin extends Bitcoin {
|
|||
priority: priority != null ? priority as BitcoinTransactionPriority : null,
|
||||
feeRate: feeRate);
|
||||
|
||||
@override
|
||||
List<String> getAddresses(Object wallet) {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
return bitcoinWallet.walletAddresses.addressesByReceiveType
|
||||
.map((BitcoinAddressRecord addr) => addr.address)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@override
|
||||
@computed
|
||||
List<ElectrumSubAddress> getSubAddresses(Object wallet) {
|
||||
final electrumWallet = wallet as ElectrumWallet;
|
||||
return electrumWallet.walletAddresses.addressesByReceiveType
|
||||
.map((BitcoinAddressRecord addr) => ElectrumSubAddress(
|
||||
.map((BaseBitcoinAddressRecord addr) => ElectrumSubAddress(
|
||||
id: addr.index,
|
||||
name: addr.name,
|
||||
address: addr.address,
|
||||
|
@ -207,12 +199,12 @@ class CWBitcoin extends Bitcoin {
|
|||
|
||||
Future<void> updateUnspents(Object wallet) async {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
await bitcoinWallet.updateUnspent();
|
||||
await bitcoinWallet.updateAllUnspents();
|
||||
}
|
||||
|
||||
WalletService createBitcoinWalletService(
|
||||
Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource) {
|
||||
return BitcoinWalletService(walletInfoSource, unspentCoinSource);
|
||||
Box<WalletInfo> walletInfoSource, Box<UnspentCoinsInfo> unspentCoinSource, bool alwaysScan) {
|
||||
return BitcoinWalletService(walletInfoSource, unspentCoinSource, alwaysScan);
|
||||
}
|
||||
|
||||
WalletService createLitecoinWalletService(
|
||||
|
@ -247,6 +239,12 @@ class CWBitcoin extends Bitcoin {
|
|||
return BitcoinReceivePageOption.fromType(bitcoinWallet.walletAddresses.addressPageType);
|
||||
}
|
||||
|
||||
@override
|
||||
bool hasSelectedSilentPayments(Object wallet) {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
return bitcoinWallet.walletAddresses.addressPageType == SilentPaymentsAddresType.p2sp;
|
||||
}
|
||||
|
||||
@override
|
||||
List<ReceivePageOption> getBitcoinReceivePageOptions() => BitcoinReceivePageOption.all;
|
||||
|
||||
|
@ -467,4 +465,137 @@ class CWBitcoin extends Bitcoin {
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ class AddressValidator extends TextValidator {
|
|||
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}\$';
|
||||
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:
|
||||
return '^${P2pkhAddress.regex.pattern}\$|^${P2shAddress.regex.pattern}\$|^${P2wpkhAddress.regex.pattern}\$|${P2trAddress.regex.pattern}\$|^${P2wshAddress.regex.pattern}\$|^${MwebAddress.regex.pattern}\$';
|
||||
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]|^)((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)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:
|
||||
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]|\$)'
|
||||
|
|
|
@ -3,7 +3,13 @@ import 'package:cw_core/sync_status.dart';
|
|||
|
||||
String syncStatusTitle(SyncStatus syncStatus) {
|
||||
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) {
|
||||
|
@ -34,5 +40,17 @@ String syncStatusTitle(SyncStatus syncStatus) {
|
|||
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 '';
|
||||
}
|
25
lib/di.dart
25
lib/di.dart
|
@ -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/bitcoin/bitcoin.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/onramper/onramper_buy_provider.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/key_service.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/wc_bottom_sheet_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_template_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/domain_lookups_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/privacy_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/trocador_providers_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_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/desktop_sidebar_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/receive_option_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/privacy_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/setup_pin_code_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: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;
|
||||
|
||||
var _isSetupFinished = false;
|
||||
|
@ -745,6 +746,9 @@ Future<void> setup({
|
|||
return DisplaySettingsViewModel(getIt.get<SettingsStore>());
|
||||
});
|
||||
|
||||
getIt.registerFactory(() =>
|
||||
SilentPaymentsSettingsViewModel(getIt.get<SettingsStore>(), getIt.get<AppStore>().wallet!));
|
||||
|
||||
getIt.registerFactory(() {
|
||||
return PrivacySettingsViewModel(getIt.get<SettingsStore>(), getIt.get<AppStore>().wallet!);
|
||||
});
|
||||
|
@ -806,6 +810,9 @@ Future<void> setup({
|
|||
|
||||
getIt.registerFactory(() => DisplaySettingsPage(getIt.get<DisplaySettingsViewModel>()));
|
||||
|
||||
getIt.registerFactory(
|
||||
() => SilentPaymentsSettingsPage(getIt.get<SilentPaymentsSettingsViewModel>()));
|
||||
|
||||
getIt.registerFactory(() => OtherSettingsPage(getIt.get<OtherSettingsViewModel>()));
|
||||
|
||||
getIt.registerFactory(() => NanoChangeRepPage(
|
||||
|
@ -893,7 +900,11 @@ Future<void> setup({
|
|||
case WalletType.monero:
|
||||
return monero!.createMoneroWalletService(_walletInfoSource, _unspentCoinsInfoSource);
|
||||
case WalletType.bitcoin:
|
||||
return bitcoin!.createBitcoinWalletService(_walletInfoSource, _unspentCoinsInfoSource);
|
||||
return bitcoin!.createBitcoinWalletService(
|
||||
_walletInfoSource,
|
||||
_unspentCoinsInfoSource,
|
||||
getIt.get<SettingsStore>().silentPaymentsAlwaysScan,
|
||||
);
|
||||
case WalletType.litecoin:
|
||||
return bitcoin!.createLitecoinWalletService(_walletInfoSource, _unspentCoinsInfoSource);
|
||||
case WalletType.ethereum:
|
||||
|
@ -1089,7 +1100,7 @@ Future<void> setup({
|
|||
|
||||
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>()));
|
||||
|
||||
|
|
|
@ -24,8 +24,9 @@ import 'package:collection/collection.dart';
|
|||
|
||||
const newCakeWalletMoneroUri = 'xmr-node.cakewallet.com:18081';
|
||||
const cakeWalletBitcoinElectrumUri = 'electrum.cakewallet.com:50002';
|
||||
const publicBitcoinTestnetElectrumAddress = 'electrum.blockstream.info';
|
||||
const publicBitcoinTestnetElectrumPort = '60002';
|
||||
const cakeWalletSilentPaymentsElectrsUri = 'electrs.cakewallet.com:50001';
|
||||
const publicBitcoinTestnetElectrumAddress = 'electrs.cakewallet.com';
|
||||
const publicBitcoinTestnetElectrumPort = '50002';
|
||||
const publicBitcoinTestnetElectrumUri =
|
||||
'$publicBitcoinTestnetElectrumAddress:$publicBitcoinTestnetElectrumPort';
|
||||
const cakeWalletLitecoinElectrumUri = 'ltc-electrum.cakewallet.com:50002';
|
||||
|
@ -224,6 +225,9 @@ Future<void> defaultSettingsMigration(
|
|||
await addTronNodeList(nodes: nodes);
|
||||
await changeTronCurrentNodeToDefault(sharedPreferences: sharedPreferences, nodes: nodes);
|
||||
break;
|
||||
case 34:
|
||||
await _addElectRsNode(nodes, sharedPreferences);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -790,7 +794,8 @@ Future<void> changeDefaultBitcoinNode(
|
|||
final needToReplaceCurrentBitcoinNode =
|
||||
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);
|
||||
|
||||
|
@ -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(
|
||||
Box<Node> nodeSource, Box<Node> powNodeSource, SharedPreferences sharedPreferences) async {
|
||||
final currentMoneroNodeId = sharedPreferences.getInt(PreferencesKey.currentNodeIdKey);
|
||||
|
@ -845,14 +870,19 @@ Future<void> checkCurrentNodes(
|
|||
}
|
||||
|
||||
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);
|
||||
final cakeWalletElectrumTestnet =
|
||||
Node(uri: publicBitcoinTestnetElectrumUri, type: WalletType.bitcoin, useSSL: false);
|
||||
await nodeSource.add(cakeWalletElectrumTestnet);
|
||||
await sharedPreferences.setInt(
|
||||
PreferencesKey.currentBitcoinElectrumSererIdKey, cakeWalletElectrum.key as int);
|
||||
}
|
||||
|
||||
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 sharedPreferences.setInt(
|
||||
PreferencesKey.currentLitecoinElectrumSererIdKey, cakeWalletElectrum.key as int);
|
||||
|
@ -887,7 +917,8 @@ Future<void> checkCurrentNodes(
|
|||
}
|
||||
|
||||
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 sharedPreferences.setInt(PreferencesKey.currentBitcoinCashNodeIdKey, node.key as int);
|
||||
}
|
||||
|
@ -921,7 +952,11 @@ Future<void> resetBitcoinElectrumServer(
|
|||
.firstWhereOrNull((node) => node.uriRaw.toString() == cakeWalletBitcoinElectrumUri);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -44,6 +44,8 @@ class PreferencesKey {
|
|||
static const polygonTransactionPriority = 'current_fee_priority_polygon';
|
||||
static const bitcoinCashTransactionPriority = 'current_fee_priority_bitcoin_cash';
|
||||
static const customBitcoinFeeRate = 'custom_electrum_fee_rate';
|
||||
static const silentPaymentsCardDisplay = 'silentPaymentsCardDisplay';
|
||||
static const silentPaymentsAlwaysScan = 'silentPaymentsAlwaysScan';
|
||||
static const shouldShowReceiveWarning = 'should_show_receive_warning';
|
||||
static const shouldShowYatPopup = 'should_show_yat_popup';
|
||||
static const shouldShowRepWarning = 'should_show_rep_warning';
|
||||
|
|
|
@ -202,7 +202,7 @@ Future<void> initializeAppConfigs() async {
|
|||
transactionDescriptions: transactionDescriptions,
|
||||
secureStorage: secureStorage,
|
||||
anonpayInvoiceInfo: anonpayInvoiceInfo,
|
||||
initialMigrationVersion: 33,
|
||||
initialMigrationVersion: 34,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,15 +3,19 @@ import 'dart:async';
|
|||
import 'package:connectivity_plus/connectivity_plus.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/sync_status.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
|
||||
Timer? _checkConnectionTimer;
|
||||
|
||||
void startCheckConnectionReaction(
|
||||
WalletBase wallet, SettingsStore settingsStore,
|
||||
void startCheckConnectionReaction(WalletBase wallet, SettingsStore settingsStore,
|
||||
{int timeInterval = 5}) {
|
||||
_checkConnectionTimer?.cancel();
|
||||
_checkConnectionTimer =
|
||||
Timer.periodic(Duration(seconds: timeInterval), (_) async {
|
||||
_checkConnectionTimer = Timer.periodic(Duration(seconds: timeInterval), (_) async {
|
||||
if (wallet.type == WalletType.bitcoin && wallet.syncStatus is SyncingSyncStatus) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final connectivityResult = await (Connectivity().checkConnectivity());
|
||||
|
||||
|
@ -20,14 +24,11 @@ void startCheckConnectionReaction(
|
|||
return;
|
||||
}
|
||||
|
||||
if (wallet.syncStatus is LostConnectionSyncStatus ||
|
||||
wallet.syncStatus is FailedSyncStatus) {
|
||||
final alive =
|
||||
await settingsStore.getCurrentNode(wallet.type).requestNode();
|
||||
if (wallet.syncStatus is LostConnectionSyncStatus || wallet.syncStatus is FailedSyncStatus) {
|
||||
final alive = await settingsStore.getCurrentNode(wallet.type).requestNode();
|
||||
|
||||
if (alive) {
|
||||
await wallet.connectToNode(
|
||||
node: settingsStore.getCurrentNode(wallet.type));
|
||||
await wallet.connectToNode(node: settingsStore.getCurrentNode(wallet.type));
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
|
@ -12,12 +12,10 @@ import 'package:wakelock_plus/wakelock_plus.dart';
|
|||
ReactionDisposer? _onWalletSyncStatusChangeReaction;
|
||||
|
||||
void startWalletSyncStatusChangeReaction(
|
||||
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>,
|
||||
TransactionInfo> wallet,
|
||||
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet,
|
||||
FiatConversionStore fiatConversionStore) {
|
||||
_onWalletSyncStatusChangeReaction?.reaction.dispose();
|
||||
_onWalletSyncStatusChangeReaction =
|
||||
reaction((_) => wallet.syncStatus, (SyncStatus status) async {
|
||||
_onWalletSyncStatusChangeReaction = reaction((_) => wallet.syncStatus, (SyncStatus status) async {
|
||||
try {
|
||||
if (status is ConnectedSyncStatus) {
|
||||
await wallet.startSync();
|
||||
|
@ -32,7 +30,7 @@ void startWalletSyncStatusChangeReaction(
|
|||
if (status is SyncedSyncStatus || status is FailedSyncStatus) {
|
||||
await WakelockPlus.disable();
|
||||
}
|
||||
} catch(e) {
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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/privacy_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/trocador_providers_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),
|
||||
onWillPop: () async => false));
|
||||
|
||||
case Routes.silentPaymentsSettings:
|
||||
return CupertinoPageRoute<void>(
|
||||
fullscreenDialog: true, builder: (_) => getIt.get<SilentPaymentsSettingsPage>());
|
||||
|
||||
case Routes.connectionSync:
|
||||
return CupertinoPageRoute<void>(
|
||||
fullscreenDialog: true, builder: (_) => getIt.get<ConnectionSyncPage>());
|
||||
|
|
|
@ -81,6 +81,7 @@ class Routes {
|
|||
static const ioniaMoreOptionsPage = '/ionia_more_options_page';
|
||||
static const ioniaCustomRedeemPage = '/ionia_custom_redeem_page';
|
||||
static const webViewPage = '/web_view_page';
|
||||
static const silentPaymentsSettings = '/silent_payments_settings';
|
||||
static const connectionSync = '/connection_sync_page';
|
||||
static const securityBackupPage = '/security_and_backup_page';
|
||||
static const privacyPage = '/privacy_page';
|
||||
|
|
|
@ -4,7 +4,7 @@ import 'package:cake_wallet/entities/preferences_key.dart';
|
|||
import 'package:cake_wallet/di.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/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/widgets/gradient_background.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/utils/device_info.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/routes.dart';
|
||||
import 'package:cake_wallet/src/screens/yat_emoji_id.dart';
|
||||
|
@ -330,10 +330,10 @@ class _DashboardPageView extends BasePage {
|
|||
if (dashboardViewModel.shouldShowMarketPlaceInDashboard) {
|
||||
pages.add(
|
||||
Semantics(
|
||||
label: S.of(context).market_place,
|
||||
child: MarketPlacePage(
|
||||
label: 'Cake ${S.of(context).features}',
|
||||
child: CakeFeaturesPage(
|
||||
dashboardViewModel: dashboardViewModel,
|
||||
marketPlaceViewModel: getIt.get<MarketPlaceViewModel>(),
|
||||
cakeFeaturesViewModel: getIt.get<CakeFeaturesViewModel>(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import 'package:cake_wallet/di.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/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/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_mobx/flutter_mobx.dart';
|
||||
|
||||
|
@ -74,9 +74,9 @@ class DesktopDashboardActions extends StatelessWidget {
|
|||
],
|
||||
),
|
||||
Expanded(
|
||||
child: MarketPlacePage(
|
||||
child: CakeFeaturesPage(
|
||||
dashboardViewModel: dashboardViewModel,
|
||||
marketPlaceViewModel: getIt.get<MarketPlaceViewModel>(),
|
||||
cakeFeaturesViewModel: getIt.get<CakeFeaturesViewModel>(),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -30,6 +30,7 @@ class DesktopWalletSelectionDropDown extends StatefulWidget {
|
|||
class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionDropDown> {
|
||||
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 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 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);
|
||||
|
@ -68,8 +69,11 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
|||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxWidth: 500),
|
||||
child: DropDownItemWidget(
|
||||
title: wallet.name,
|
||||
image: wallet.isEnabled ? _imageFor(type: wallet.type) : nonWalletTypeIcon),
|
||||
title: wallet.name,
|
||||
image: wallet.isEnabled
|
||||
? _imageFor(type: wallet.type, isTestnet: wallet.isTestnet)
|
||||
: nonWalletTypeIcon,
|
||||
),
|
||||
),
|
||||
onSelected: () => _onSelectedWallet(wallet),
|
||||
))
|
||||
|
@ -120,16 +124,16 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
|||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
final confirmed = await showPopUp<bool>(
|
||||
context: context,
|
||||
builder: (dialogContext) {
|
||||
return AlertWithTwoActions(
|
||||
alertTitle: S.of(context).change_wallet_alert_title,
|
||||
alertContent: S.of(context).change_wallet_alert_content(selectedWallet.name),
|
||||
leftButtonText: S.of(context).cancel,
|
||||
rightButtonText: S.of(context).change,
|
||||
actionLeftButton: () => Navigator.of(dialogContext).pop(false),
|
||||
actionRightButton: () => Navigator.of(dialogContext).pop(true));
|
||||
}) ??
|
||||
context: context,
|
||||
builder: (dialogContext) {
|
||||
return AlertWithTwoActions(
|
||||
alertTitle: S.of(context).change_wallet_alert_title,
|
||||
alertContent: S.of(context).change_wallet_alert_content(selectedWallet.name),
|
||||
leftButtonText: S.of(context).cancel,
|
||||
rightButtonText: S.of(context).change,
|
||||
actionLeftButton: () => Navigator.of(dialogContext).pop(false),
|
||||
actionRightButton: () => Navigator.of(dialogContext).pop(true));
|
||||
}) ??
|
||||
false;
|
||||
|
||||
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) {
|
||||
case WalletType.bitcoin:
|
||||
if (isTestnet == true) {
|
||||
return tBitcoinIcon;
|
||||
}
|
||||
return bitcoinIcon;
|
||||
case WalletType.monero:
|
||||
return moneroIcon;
|
||||
|
@ -160,7 +167,7 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
|||
return polygonIcon;
|
||||
case WalletType.solana:
|
||||
return solanaIcon;
|
||||
case WalletType.tron:
|
||||
case WalletType.tron:
|
||||
return tronIcon;
|
||||
default:
|
||||
return nonWalletTypeIcon;
|
||||
|
@ -168,24 +175,25 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
|||
}
|
||||
|
||||
Future<void> _loadWallet(WalletListItem wallet) async {
|
||||
widget._authService.authenticateAction(context,
|
||||
onAuthSuccess: (isAuthenticatedSuccessfully) async {
|
||||
if (!isAuthenticatedSuccessfully) {
|
||||
return;
|
||||
}
|
||||
widget._authService.authenticateAction(
|
||||
context,
|
||||
onAuthSuccess: (isAuthenticatedSuccessfully) async {
|
||||
if (!isAuthenticatedSuccessfully) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
if (context.mounted) {
|
||||
changeProcessText(S.of(context).wallet_list_loading_wallet(wallet.name));
|
||||
try {
|
||||
if (context.mounted) {
|
||||
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:
|
||||
widget.walletListViewModel.shouldRequireTOTP2FAForAccessingWallet,
|
||||
|
@ -198,17 +206,16 @@ class _DesktopWalletSelectionDropDownState extends State<DesktopWalletSelectionD
|
|||
context,
|
||||
route: Routes.newWallet,
|
||||
arguments: widget.walletListViewModel.currentWalletType,
|
||||
conditionToDetermineIfToUse2FA: widget
|
||||
.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
|
||||
conditionToDetermineIfToUse2FA:
|
||||
widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
|
||||
);
|
||||
} else {
|
||||
widget._authService.authenticateAction(
|
||||
context,
|
||||
route: Routes.newWalletType,
|
||||
conditionToDetermineIfToUse2FA: widget
|
||||
.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
|
||||
conditionToDetermineIfToUse2FA:
|
||||
widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets,
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -187,6 +187,11 @@ class AddressPage extends BasePage {
|
|||
}
|
||||
|
||||
reaction((_) => receiveOptionViewModel.selectedReceiveOption, (ReceivePageOption option) {
|
||||
if (bitcoin!.isBitcoinReceivePageOption(option)) {
|
||||
addressListViewModel.setAddressType(bitcoin!.getOptionToType(option));
|
||||
return;
|
||||
}
|
||||
|
||||
switch (option) {
|
||||
case ReceivePageOption.anonPayInvoice:
|
||||
Navigator.pushNamed(
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
import 'dart:math';
|
||||
|
||||
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/reactions/wallet_connect.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/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/screens/exchange_trade/information_page.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/standard_switch.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/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:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class BalancePage extends StatelessWidget {
|
||||
BalancePage({
|
||||
|
@ -221,30 +225,136 @@ class CryptoBalanceWidget extends StatelessWidget {
|
|||
itemBuilder: (__, index) {
|
||||
final balance =
|
||||
dashboardViewModel.balanceViewModel.formattedBalances.elementAt(index);
|
||||
return BalanceRowWidget(
|
||||
availableBalanceLabel:
|
||||
'${dashboardViewModel.balanceViewModel.availableBalanceLabel}',
|
||||
availableBalance: balance.availableBalance,
|
||||
availableFiatBalance: balance.fiatAvailableBalance,
|
||||
additionalBalanceLabel:
|
||||
'${dashboardViewModel.balanceViewModel.additionalBalanceLabel}',
|
||||
additionalBalance: balance.additionalBalance,
|
||||
additionalFiatBalance: balance.fiatAdditionalBalance,
|
||||
frozenBalance: balance.frozenBalance,
|
||||
frozenFiatBalance: balance.fiatFrozenBalance,
|
||||
currency: balance.asset,
|
||||
hasAdditionalBalance:
|
||||
dashboardViewModel.balanceViewModel.hasAdditionalBalance,
|
||||
);
|
||||
return Observer(builder: (_) {
|
||||
return BalanceRowWidget(
|
||||
availableBalanceLabel:
|
||||
'${dashboardViewModel.balanceViewModel.availableBalanceLabel}',
|
||||
availableBalance: balance.availableBalance,
|
||||
availableFiatBalance: balance.fiatAvailableBalance,
|
||||
additionalBalanceLabel:
|
||||
'${dashboardViewModel.balanceViewModel.additionalBalanceLabel}',
|
||||
additionalBalance: balance.additionalBalance,
|
||||
additionalFiatBalance: balance.fiatAdditionalBalance,
|
||||
frozenBalance: balance.frozenBalance,
|
||||
frozenFiatBalance: balance.fiatFrozenBalance,
|
||||
currency: balance.asset,
|
||||
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 {
|
||||
|
@ -259,6 +369,7 @@ class BalanceRowWidget extends StatelessWidget {
|
|||
required this.frozenFiatBalance,
|
||||
required this.currency,
|
||||
required this.hasAdditionalBalance,
|
||||
required this.isTestnet,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
@ -272,6 +383,7 @@ class BalanceRowWidget extends StatelessWidget {
|
|||
final String frozenFiatBalance;
|
||||
final CryptoCurrency currency;
|
||||
final bool hasAdditionalBalance;
|
||||
final bool isTestnet;
|
||||
|
||||
// void _showBalanceDescription(BuildContext context) {
|
||||
// showPopUp<void>(
|
||||
|
@ -346,14 +458,24 @@ class BalanceRowWidget extends StatelessWidget {
|
|||
maxLines: 1,
|
||||
textAlign: TextAlign.start),
|
||||
SizedBox(height: 6),
|
||||
Text('${availableFiatBalance}',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).extension<BalancePageTheme>()!.textColor,
|
||||
height: 1)),
|
||||
if (isTestnet)
|
||||
Text(S.current.testnet_coins_no_value,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
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: Column(
|
||||
children: [
|
||||
Container(
|
||||
clipBehavior: Clip.antiAlias,
|
||||
decoration: BoxDecoration(shape: BoxShape.circle),
|
||||
child: CakeImageWidget(
|
||||
imageUrl: currency.iconPath,
|
||||
height: 40,
|
||||
width: 40,
|
||||
displayOnError: Container(
|
||||
height: 30.0,
|
||||
width: 30.0,
|
||||
child: Center(
|
||||
child: Text(
|
||||
currency.title.substring(0, min(currency.title.length, 2)),
|
||||
style: TextStyle(fontSize: 11),
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.grey.shade400,
|
||||
CakeImageWidget(
|
||||
imageUrl: currency.iconPath,
|
||||
height: 40,
|
||||
width: 40,
|
||||
displayOnError: Container(
|
||||
height: 30.0,
|
||||
width: 30.0,
|
||||
child: Center(
|
||||
child: Text(
|
||||
currency.title.substring(0, min(currency.title.length, 2)),
|
||||
style: TextStyle(fontSize: 11),
|
||||
),
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Colors.grey.shade400,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
|
@ -449,17 +567,18 @@ class BalanceRowWidget extends StatelessWidget {
|
|||
textAlign: TextAlign.center,
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Text(
|
||||
frozenFiatBalance,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).extension<BalancePageTheme>()!.textColor,
|
||||
height: 1,
|
||||
if (!isTestnet)
|
||||
Text(
|
||||
frozenFiatBalance,
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).extension<BalancePageTheme>()!.textColor,
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -493,17 +612,18 @@ class BalanceRowWidget extends StatelessWidget {
|
|||
textAlign: TextAlign.center,
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
Text(
|
||||
'${additionalFiatBalance}',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).extension<BalancePageTheme>()!.textColor,
|
||||
height: 1,
|
||||
if (!isTestnet)
|
||||
Text(
|
||||
'${additionalFiatBalance}',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontFamily: 'Lato',
|
||||
fontWeight: FontWeight.w400,
|
||||
color: Theme.of(context).extension<BalancePageTheme>()!.textColor,
|
||||
height: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
|
|
@ -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/utils/show_pop_up.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:cw_core/wallet_type.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/cake_features_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
class MarketPlacePage extends StatelessWidget {
|
||||
MarketPlacePage({
|
||||
class CakeFeaturesPage extends StatelessWidget {
|
||||
CakeFeaturesPage({
|
||||
required this.dashboardViewModel,
|
||||
required this.marketPlaceViewModel,
|
||||
required this.cakeFeaturesViewModel,
|
||||
});
|
||||
|
||||
final DashboardViewModel dashboardViewModel;
|
||||
final MarketPlaceViewModel marketPlaceViewModel;
|
||||
final CakeFeaturesViewModel cakeFeaturesViewModel;
|
||||
final _scrollController = ScrollController();
|
||||
|
||||
@override
|
||||
|
@ -37,7 +34,7 @@ class MarketPlacePage extends StatelessWidget {
|
|||
children: [
|
||||
SizedBox(height: 50),
|
||||
Text(
|
||||
S.of(context).market_place,
|
||||
'Cake ${S.of(context).features}',
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w500,
|
||||
|
@ -59,15 +56,21 @@ class MarketPlacePage extends StatelessWidget {
|
|||
// ),
|
||||
SizedBox(height: 20),
|
||||
DashBoardRoundedCardWidget(
|
||||
onTap: () => _launchUrl("buy.cakepay.com"),
|
||||
title: S.of(context).cake_pay_web_cards_title,
|
||||
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(
|
||||
title: "NanoGPT",
|
||||
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 {
|
||||
launchUrl(
|
||||
Uri.https(url),
|
||||
mode: LaunchMode.externalApplication,
|
||||
);
|
||||
} catch (e) {
|
||||
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);
|
||||
});
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
|
@ -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_actions.dart';
|
||||
import 'package:cake_wallet/themes/extensions/menu_theme.dart';
|
||||
|
@ -180,6 +181,11 @@ class MenuWidgetState extends State<MenuWidget> {
|
|||
|
||||
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;
|
||||
|
||||
return SettingActionButton(
|
||||
|
|
|
@ -10,8 +10,7 @@ import 'package:flutter_mobx/flutter_mobx.dart';
|
|||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
|
||||
class PresentReceiveOptionPicker extends StatelessWidget {
|
||||
PresentReceiveOptionPicker(
|
||||
{required this.receiveOptionViewModel, required this.color});
|
||||
PresentReceiveOptionPicker({required this.receiveOptionViewModel, required this.color});
|
||||
|
||||
final ReceiveOptionViewModel receiveOptionViewModel;
|
||||
final Color color;
|
||||
|
@ -43,17 +42,17 @@ class PresentReceiveOptionPicker extends StatelessWidget {
|
|||
Text(
|
||||
S.current.receive,
|
||||
style: TextStyle(
|
||||
fontSize: 18.0,
|
||||
fontWeight: FontWeight.bold,
|
||||
fontFamily: 'Lato',
|
||||
color: color),
|
||||
fontSize: 18.0, fontWeight: FontWeight.bold, fontFamily: 'Lato', color: color),
|
||||
),
|
||||
Observer(
|
||||
builder: (_) => Text(receiveOptionViewModel.selectedReceiveOption.toString(),
|
||||
style: TextStyle(
|
||||
fontSize: 10.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: color)))
|
||||
builder: (_) => Text(
|
||||
receiveOptionViewModel.selectedReceiveOption
|
||||
.toString()
|
||||
.replaceAll(RegExp(r'silent payments', caseSensitive: false),
|
||||
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),
|
||||
|
@ -73,65 +72,75 @@ class PresentReceiveOptionPicker extends StatelessWidget {
|
|||
backgroundColor: Colors.transparent,
|
||||
body: Stack(
|
||||
alignment: AlignmentDirectional.center,
|
||||
children:[ AlertBackground(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Spacer(),
|
||||
Container(
|
||||
margin: EdgeInsets.symmetric(horizontal: 24),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 24, bottom: 24),
|
||||
child: (ListView.separated(
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
itemCount: receiveOptionViewModel.options.length,
|
||||
itemBuilder: (_, index) {
|
||||
final option = receiveOptionViewModel.options[index];
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
Navigator.pop(popUpContext);
|
||||
children: [
|
||||
AlertBackground(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Spacer(),
|
||||
Container(
|
||||
margin: EdgeInsets.symmetric(horizontal: 24),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(30),
|
||||
color: Theme.of(context).colorScheme.background,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 24, bottom: 24),
|
||||
child: (ListView.separated(
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
itemCount: receiveOptionViewModel.options.length,
|
||||
itemBuilder: (_, index) {
|
||||
final option = receiveOptionViewModel.options[index];
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
Navigator.pop(popUpContext);
|
||||
|
||||
receiveOptionViewModel.selectReceiveOption(option);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 24, right: 24),
|
||||
child: Observer(builder: (_) {
|
||||
final value = receiveOptionViewModel.selectedReceiveOption;
|
||||
receiveOptionViewModel.selectReceiveOption(option);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 24, right: 24),
|
||||
child: Observer(builder: (_) {
|
||||
final value = receiveOptionViewModel.selectedReceiveOption;
|
||||
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(option.toString(),
|
||||
textAlign: TextAlign.left,
|
||||
style: textSmall(
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor,
|
||||
).copyWith(
|
||||
fontWeight:
|
||||
value == option ? FontWeight.w800 : FontWeight.w500,
|
||||
)),
|
||||
RoundedCheckbox(
|
||||
value: value == option,
|
||||
)
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (_, index) => SizedBox(height: 30),
|
||||
)),
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
option
|
||||
.toString()
|
||||
.replaceAll(
|
||||
RegExp(r'silent payments', caseSensitive: false),
|
||||
S.current.silent_payments)
|
||||
.replaceAll(RegExp(r'default', caseSensitive: false),
|
||||
S.current.string_default),
|
||||
textAlign: TextAlign.left,
|
||||
style: textSmall(
|
||||
color: Theme.of(context)
|
||||
.extension<CakeTextTheme>()!
|
||||
.titleColor,
|
||||
).copyWith(
|
||||
fontWeight:
|
||||
value == option ? FontWeight.w800 : FontWeight.w500,
|
||||
)),
|
||||
RoundedCheckbox(
|
||||
value: value == option,
|
||||
)
|
||||
],
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
},
|
||||
separatorBuilder: (_, index) => SizedBox(height: 30),
|
||||
)),
|
||||
),
|
||||
),
|
||||
),
|
||||
Spacer()
|
||||
],
|
||||
Spacer()
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
AlertCloseButton(onTap: () => Navigator.of(popUpContext).pop(), bottom: 40)
|
||||
],
|
||||
),
|
||||
|
|
|
@ -67,8 +67,7 @@ class ReceivePage extends BasePage {
|
|||
|
||||
@override
|
||||
Widget Function(BuildContext, Widget) get rootWrapper =>
|
||||
(BuildContext context, Widget scaffold) =>
|
||||
GradientBackground(scaffold: scaffold);
|
||||
(BuildContext context, Widget scaffold) => GradientBackground(scaffold: scaffold);
|
||||
|
||||
@override
|
||||
Widget trailing(BuildContext context) {
|
||||
|
@ -99,115 +98,144 @@ class ReceivePage extends BasePage {
|
|||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return KeyboardActions(
|
||||
config: KeyboardActionsConfig(
|
||||
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
|
||||
keyboardBarColor: Theme.of(context).extension<KeyboardTheme>()!.keyboardBarColor,
|
||||
nextFocus: false,
|
||||
actions: [
|
||||
KeyboardActionsItem(
|
||||
focusNode: _cryptoAmountFocus,
|
||||
toolbarButtons: [(_) => KeyboardDoneButton()],
|
||||
)
|
||||
]),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(24, 50, 24, 24),
|
||||
child: QRWidget(
|
||||
addressListViewModel: addressListViewModel,
|
||||
formKey: _formKey,
|
||||
heroTag: _heroTag,
|
||||
amountTextFieldFocusNode: _cryptoAmountFocus,
|
||||
amountController: _amountController,
|
||||
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,
|
||||
);
|
||||
})),
|
||||
],
|
||||
return KeyboardActions(
|
||||
config: KeyboardActionsConfig(
|
||||
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
|
||||
keyboardBarColor: Theme.of(context).extension<KeyboardTheme>()!.keyboardBarColor,
|
||||
nextFocus: false,
|
||||
actions: [
|
||||
KeyboardActionsItem(
|
||||
focusNode: _cryptoAmountFocus,
|
||||
toolbarButtons: [(_) => KeyboardDoneButton()],
|
||||
)
|
||||
]),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(24, 50, 24, 24),
|
||||
child: QRWidget(
|
||||
addressListViewModel: addressListViewModel,
|
||||
formKey: _formKey,
|
||||
heroTag: _heroTag,
|
||||
amountTextFieldFocusNode: _cryptoAmountFocus,
|
||||
amountController: _amountController,
|
||||
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) {
|
||||
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)),
|
||||
),
|
||||
],
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,18 +15,22 @@ class AddressCell extends StatelessWidget {
|
|||
required this.textColor,
|
||||
this.onTap,
|
||||
this.onEdit,
|
||||
this.onDelete,
|
||||
this.txCount,
|
||||
this.balance,
|
||||
this.isChange = false,
|
||||
this.hasBalance = false});
|
||||
|
||||
factory AddressCell.fromItem(WalletAddressListItem item,
|
||||
{required bool isCurrent,
|
||||
required Color backgroundColor,
|
||||
required Color textColor,
|
||||
Function(String)? onTap,
|
||||
bool hasBalance = false,
|
||||
Function()? onEdit}) =>
|
||||
factory AddressCell.fromItem(
|
||||
WalletAddressListItem item, {
|
||||
required bool isCurrent,
|
||||
required Color backgroundColor,
|
||||
required Color textColor,
|
||||
Function(String)? onTap,
|
||||
bool hasBalance = false,
|
||||
Function()? onEdit,
|
||||
Function()? onDelete,
|
||||
}) =>
|
||||
AddressCell(
|
||||
address: item.address,
|
||||
name: item.name ?? '',
|
||||
|
@ -36,6 +40,7 @@ class AddressCell extends StatelessWidget {
|
|||
textColor: textColor,
|
||||
onTap: onTap,
|
||||
onEdit: onEdit,
|
||||
onDelete: onDelete,
|
||||
txCount: item.txCount,
|
||||
balance: item.balance,
|
||||
isChange: item.isChange,
|
||||
|
@ -49,6 +54,7 @@ class AddressCell extends StatelessWidget {
|
|||
final Color textColor;
|
||||
final Function(String)? onTap;
|
||||
final Function()? onEdit;
|
||||
final Function()? onDelete;
|
||||
final int? txCount;
|
||||
final String? balance;
|
||||
final bool isChange;
|
||||
|
@ -64,7 +70,8 @@ class AddressCell extends StatelessWidget {
|
|||
} else {
|
||||
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,
|
||||
children: [
|
||||
Text(
|
||||
'Balance: $balance',
|
||||
'${S.of(context).balance}: $balance',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
|
@ -180,7 +187,7 @@ class AddressCell extends StatelessWidget {
|
|||
|
||||
ActionPane _actionPane(BuildContext context) => ActionPane(
|
||||
motion: const ScrollMotion(),
|
||||
extentRatio: 0.3,
|
||||
extentRatio: onDelete != null ? 0.4 : 0.3,
|
||||
children: [
|
||||
SlidableAction(
|
||||
onPressed: (_) => onEdit?.call(),
|
||||
|
@ -189,6 +196,14 @@ class AddressCell extends StatelessWidget {
|
|||
icon: Icons.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,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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_mobx/flutter_mobx.dart';
|
||||
import 'package:cake_wallet/view_model/rescan_view_model.dart';
|
||||
|
@ -11,7 +15,8 @@ class RescanPage extends BasePage {
|
|||
: _blockchainHeightWidgetKey = GlobalKey<BlockchainHeightState>();
|
||||
|
||||
@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 RescanViewModel _rescanViewModel;
|
||||
|
||||
|
@ -19,20 +24,28 @@ class RescanPage extends BasePage {
|
|||
Widget body(BuildContext context) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||
child:
|
||||
Column(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
|
||||
BlockchainHeightWidget(key: _blockchainHeightWidgetKey,
|
||||
onHeightOrDateEntered: (value) =>
|
||||
_rescanViewModel.isButtonEnabled = value),
|
||||
child: Column(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
|
||||
Observer(
|
||||
builder: (_) => BlockchainHeightWidget(
|
||||
key: _blockchainHeightWidgetKey,
|
||||
onHeightOrDateEntered: (value) => _rescanViewModel.isButtonEnabled = value,
|
||||
isSilentPaymentsScan: _rescanViewModel.isSilentPaymentsScan,
|
||||
doSingleScan: _rescanViewModel.doSingleScan,
|
||||
toggleSingleScan: () =>
|
||||
_rescanViewModel.doSingleScan = !_rescanViewModel.doSingleScan,
|
||||
)),
|
||||
Observer(
|
||||
builder: (_) => LoadingPrimaryButton(
|
||||
isLoading:
|
||||
_rescanViewModel.state == RescanWalletState.rescaning,
|
||||
isLoading: _rescanViewModel.state == RescanWalletState.rescaning,
|
||||
text: S.of(context).rescan,
|
||||
onPressed: () async {
|
||||
await _rescanViewModel.rescanCurrentWallet(
|
||||
restoreHeight:
|
||||
_blockchainHeightWidgetKey.currentState!.height);
|
||||
if (_rescanViewModel.isSilentPaymentsScan) {
|
||||
return _toggleSilentPaymentsScanning(context);
|
||||
}
|
||||
|
||||
_rescanViewModel.rescanCurrentWallet(
|
||||
restoreHeight: _blockchainHeightWidgetKey.currentState!.height);
|
||||
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/core/auth_service.dart';
|
||||
import 'package:cake_wallet/entities/contact_record.dart';
|
||||
import 'package:cake_wallet/core/execution_state.dart';
|
||||
|
@ -420,6 +421,10 @@ class SendPage extends BasePage {
|
|||
return;
|
||||
}
|
||||
|
||||
if (sendViewModel.isElectrumWallet) {
|
||||
bitcoin!.updateFeeRates(sendViewModel.wallet);
|
||||
}
|
||||
|
||||
reaction((_) => sendViewModel.state, (ExecutionState state) {
|
||||
if (dialogContext != null && dialogContext?.mounted == true) {
|
||||
Navigator.of(dialogContext!).pop();
|
||||
|
|
|
@ -39,7 +39,9 @@ class ConnectionSyncPage extends BasePage {
|
|||
),
|
||||
if (dashboardViewModel.hasRescan) ...[
|
||||
SettingsCellWithArrow(
|
||||
title: S.current.rescan,
|
||||
title: dashboardViewModel.hasSilentPayments
|
||||
? S.current.silent_payments_scanning
|
||||
: S.current.rescan,
|
||||
handler: (context) => Navigator.of(context).pushNamed(Routes.rescan),
|
||||
),
|
||||
if (DeviceInfo.instance.isMobile && FeatureFlag.isBackgroundSyncEnabled) ...[
|
||||
|
|
50
lib/src/screens/settings/silent_payments_settings.dart
Normal file
50
lib/src/screens/settings/silent_payments_settings.dart
Normal 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),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -57,6 +57,7 @@ class UnspentCoinsListFormState extends State<UnspentCoinsListForm> {
|
|||
isSending: item.isSending,
|
||||
isFrozen: item.isFrozen,
|
||||
isChange: item.isChange,
|
||||
isSilentPayment: item.isSilentPayment,
|
||||
onCheckBoxTap: item.isFrozen
|
||||
? null
|
||||
: () async {
|
||||
|
|
|
@ -12,6 +12,7 @@ class UnspentCoinsListItem extends StatelessWidget {
|
|||
required this.isSending,
|
||||
required this.isFrozen,
|
||||
required this.isChange,
|
||||
required this.isSilentPayment,
|
||||
this.onCheckBoxTap,
|
||||
});
|
||||
|
||||
|
@ -21,18 +22,16 @@ class UnspentCoinsListItem extends StatelessWidget {
|
|||
final bool isSending;
|
||||
final bool isFrozen;
|
||||
final bool isChange;
|
||||
final bool isSilentPayment;
|
||||
final Function()? onCheckBoxTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final unselectedItemColor = Theme.of(context).cardColor;
|
||||
final selectedItemColor = Theme.of(context).primaryColor;
|
||||
final itemColor = isSending
|
||||
? selectedItemColor
|
||||
: unselectedItemColor;
|
||||
final amountColor = isSending
|
||||
? Colors.white
|
||||
: Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor;
|
||||
final itemColor = isSending ? selectedItemColor : unselectedItemColor;
|
||||
final amountColor =
|
||||
isSending ? Colors.white : Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor;
|
||||
final addressColor = isSending
|
||||
? Colors.white.withOpacity(0.5)
|
||||
: 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -96,6 +96,7 @@ class WalletListBody extends StatefulWidget {
|
|||
class WalletListBodyState extends State<WalletListBody> {
|
||||
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 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 nonWalletTypeIcon = Image.asset('assets/images/close.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,
|
||||
children: <Widget>[
|
||||
wallet.isEnabled
|
||||
? _imageFor(type: wallet.type)
|
||||
? _imageFor(
|
||||
type: wallet.type,
|
||||
isTestnet: wallet.isTestnet,
|
||||
)
|
||||
: nonWalletTypeIcon,
|
||||
SizedBox(width: 10),
|
||||
Flexible(
|
||||
|
@ -297,9 +301,12 @@ class WalletListBodyState extends State<WalletListBody> {
|
|||
);
|
||||
}
|
||||
|
||||
Image _imageFor({required WalletType type}) {
|
||||
Image _imageFor({required WalletType type, bool? isTestnet}) {
|
||||
switch (type) {
|
||||
case WalletType.bitcoin:
|
||||
if (isTestnet == true) {
|
||||
return tBitcoinIcon;
|
||||
}
|
||||
return bitcoinIcon;
|
||||
case WalletType.monero:
|
||||
return moneroIcon;
|
||||
|
|
|
@ -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/utils/date_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
@ -12,13 +14,19 @@ class BlockchainHeightWidget extends StatefulWidget {
|
|||
this.onHeightChange,
|
||||
this.focusNode,
|
||||
this.onHeightOrDateEntered,
|
||||
this.hasDatePicker = true})
|
||||
: super(key: key);
|
||||
this.hasDatePicker = true,
|
||||
this.isSilentPaymentsScan = false,
|
||||
this.toggleSingleScan,
|
||||
this.doSingleScan = false,
|
||||
}) : super(key: key);
|
||||
|
||||
final Function(int)? onHeightChange;
|
||||
final Function(bool)? onHeightOrDateEntered;
|
||||
final FocusNode? focusNode;
|
||||
final bool hasDatePicker;
|
||||
final bool isSilentPaymentsScan;
|
||||
final bool doSingleScan;
|
||||
final Function()? toggleSingleScan;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => BlockchainHeightState();
|
||||
|
@ -64,9 +72,10 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
|
|||
child: BaseTextFormField(
|
||||
focusNode: widget.focusNode,
|
||||
controller: restoreHeightController,
|
||||
keyboardType: TextInputType.numberWithOptions(
|
||||
signed: false, decimal: false),
|
||||
hintText: S.of(context).widgets_restore_from_blockheight,
|
||||
keyboardType: TextInputType.numberWithOptions(signed: false, decimal: false),
|
||||
hintText: widget.isSilentPaymentsScan
|
||||
? 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(
|
||||
fontSize: 16.0,
|
||||
fontWeight: FontWeight.w500,
|
||||
color:
|
||||
Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||
color: Theme.of(context).extension<CakeTextTheme>()!.titleColor),
|
||||
),
|
||||
),
|
||||
Row(
|
||||
|
@ -91,22 +99,47 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
|
|||
child: IgnorePointer(
|
||||
child: BaseTextFormField(
|
||||
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: EdgeInsets.only(left: 40, right: 40, top: 24),
|
||||
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,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Theme.of(context).hintColor
|
||||
),
|
||||
fontSize: 12, fontWeight: FontWeight.normal, color: Theme.of(context).hintColor),
|
||||
),
|
||||
)
|
||||
]
|
||||
|
@ -123,7 +156,12 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
|
|||
lastDate: now);
|
||||
|
||||
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(() {
|
||||
dateController.text = DateFormat('yyyy-MM-dd').format(date);
|
||||
restoreHeightController.text = '$height';
|
||||
|
|
|
@ -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:flutter/material.dart';
|
||||
import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
|
||||
class DashBoardRoundedCardWidget extends StatelessWidget {
|
||||
DashBoardRoundedCardWidget({
|
||||
required this.onTap,
|
||||
required this.title,
|
||||
required this.subTitle,
|
||||
this.hint,
|
||||
this.svgPicture,
|
||||
this.icon,
|
||||
this.onClose,
|
||||
this.customBorder,
|
||||
});
|
||||
|
||||
final VoidCallback onTap;
|
||||
final VoidCallback? onClose;
|
||||
final String title;
|
||||
final String subTitle;
|
||||
final Widget? hint;
|
||||
final SvgPicture? svgPicture;
|
||||
final Icon? icon;
|
||||
final double? customBorder;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -26,34 +35,56 @@ class DashBoardRoundedCardWidget extends StatelessWidget {
|
|||
child: Stack(
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.fromLTRB(20, 20, 40, 20),
|
||||
padding: EdgeInsets.all(20),
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).extension<SyncIndicatorTheme>()!.syncedBackgroundColor,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
borderRadius: BorderRadius.circular(customBorder ?? 20),
|
||||
border: Border.all(
|
||||
color: Theme.of(context).extension<BalancePageTheme>()!.cardBorderColor,
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
title,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).extension<DashboardPageTheme>()!.cardTextColor,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.w900,
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
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),
|
||||
Text(
|
||||
subTitle,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).extension<DashboardPageTheme>()!.cardTextColor,
|
||||
fontWeight: FontWeight.w500,
|
||||
fontFamily: 'Lato'),
|
||||
)
|
||||
if (hint != null) ...[
|
||||
SizedBox(height: 10),
|
||||
hint!,
|
||||
]
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -17,6 +17,7 @@ class SettingActions {
|
|||
connectionSettingAction,
|
||||
walletSettingAction,
|
||||
addressBookSettingAction,
|
||||
silentPaymentsSettingAction,
|
||||
securityBackupSettingAction,
|
||||
privacySettingAction,
|
||||
displaySettingAction,
|
||||
|
@ -35,6 +36,15 @@ class SettingActions {
|
|||
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._(
|
||||
name: (context) => S.of(context).connection_sync,
|
||||
image: 'assets/images/nodes_menu.png',
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart';
|
||||
import 'package:cake_wallet/core/secure_storage.dart';
|
||||
|
@ -108,6 +107,8 @@ abstract class SettingsStoreBase with Store {
|
|||
required this.lookupsOpenAlias,
|
||||
required this.lookupsENS,
|
||||
required this.customBitcoinFeeRate,
|
||||
required this.silentPaymentsCardDisplay,
|
||||
required this.silentPaymentsAlwaysScan,
|
||||
TransactionPriority? initialBitcoinTransactionPriority,
|
||||
TransactionPriority? initialMoneroTransactionPriority,
|
||||
TransactionPriority? initialHavenTransactionPriority,
|
||||
|
@ -518,6 +519,16 @@ abstract class SettingsStoreBase with Store {
|
|||
(int 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) {
|
||||
if (change.newValue != null && change.key != null) {
|
||||
_saveCurrentNode(change.newValue!, change.key!);
|
||||
|
@ -713,6 +724,12 @@ abstract class SettingsStoreBase with Store {
|
|||
@observable
|
||||
int customBitcoinFeeRate;
|
||||
|
||||
@observable
|
||||
bool silentPaymentsCardDisplay;
|
||||
|
||||
@observable
|
||||
bool silentPaymentsAlwaysScan;
|
||||
|
||||
final SecureStorage _secureStorage;
|
||||
final SharedPreferences _sharedPreferences;
|
||||
final BackgroundTasks _backgroundTasks;
|
||||
|
@ -859,6 +876,10 @@ abstract class SettingsStoreBase with Store {
|
|||
final lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true;
|
||||
final lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true;
|
||||
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 (pinLength == null || pinLength == 0) {
|
||||
|
@ -1103,6 +1124,8 @@ abstract class SettingsStoreBase with Store {
|
|||
lookupsOpenAlias: lookupsOpenAlias,
|
||||
lookupsENS: lookupsENS,
|
||||
customBitcoinFeeRate: customBitcoinFeeRate,
|
||||
silentPaymentsCardDisplay: silentPaymentsCardDisplay,
|
||||
silentPaymentsAlwaysScan: silentPaymentsAlwaysScan,
|
||||
initialMoneroTransactionPriority: moneroTransactionPriority,
|
||||
initialBitcoinTransactionPriority: bitcoinTransactionPriority,
|
||||
initialHavenTransactionPriority: havenTransactionPriority,
|
||||
|
@ -1242,6 +1265,10 @@ abstract class SettingsStoreBase with Store {
|
|||
lookupsOpenAlias = sharedPreferences.getBool(PreferencesKey.lookupsOpenAlias) ?? true;
|
||||
lookupsENS = sharedPreferences.getBool(PreferencesKey.lookupsENS) ?? true;
|
||||
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 bitcoinElectrumServerId =
|
||||
sharedPreferences.getInt(PreferencesKey.currentBitcoinElectrumSererIdKey);
|
||||
|
|
|
@ -2,6 +2,7 @@ import 'dart:async';
|
|||
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
|
||||
import 'package:cake_wallet/entities/contact_base.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:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
|
@ -40,16 +41,19 @@ abstract class ContactListViewModelBase with Store {
|
|||
});
|
||||
} else if (info.addresses?.isNotEmpty == true) {
|
||||
info.addresses!.forEach((address, label) {
|
||||
if (label.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final name = _createName(info.name, label);
|
||||
walletContacts.add(WalletContact(
|
||||
address,
|
||||
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(
|
||||
info.address,
|
||||
info.name,
|
||||
|
@ -64,7 +68,9 @@ abstract class ContactListViewModelBase with Store {
|
|||
}
|
||||
|
||||
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;
|
||||
|
|
|
@ -60,6 +60,9 @@ abstract class BalanceViewModelBase with Store {
|
|||
@observable
|
||||
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet;
|
||||
|
||||
@computed
|
||||
bool get hasSilentPayments => wallet.type == WalletType.bitcoin;
|
||||
|
||||
@computed
|
||||
double get price {
|
||||
final price = fiatConvertationStore.prices[appStore.wallet!.currency];
|
||||
|
|
16
lib/view_model/dashboard/cake_features_view_model.dart
Normal file
16
lib/view_model/dashboard/cake_features_view_model.dart
Normal 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();
|
||||
}
|
||||
}
|
|
@ -45,6 +45,7 @@ import 'package:cw_core/wallet_type.dart';
|
|||
import 'package:eth_sig_util/util/utils.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
|
@ -201,6 +202,14 @@ abstract class DashboardViewModelBase with Store {
|
|||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (hasSilentPayments) {
|
||||
silentPaymentsScanningActive = bitcoin!.getScanningActive(wallet);
|
||||
|
||||
reaction((_) => wallet.syncStatus, (SyncStatus syncStatus) {
|
||||
silentPaymentsScanningActive = bitcoin!.getScanningActive(wallet);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@observable
|
||||
|
@ -287,11 +296,36 @@ abstract class DashboardViewModelBase with Store {
|
|||
@observable
|
||||
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 SharedPreferences sharedPreferences;
|
||||
|
||||
@observable
|
||||
bool silentPaymentsScanningActive = false;
|
||||
|
||||
@action
|
||||
void setSilentPaymentsScanning(bool active) {
|
||||
silentPaymentsScanningActive = active;
|
||||
|
||||
if (hasSilentPayments) {
|
||||
bitcoin!.setScanningActive(wallet, active);
|
||||
}
|
||||
}
|
||||
|
||||
BalanceViewModel balanceViewModel;
|
||||
|
||||
AppStore appStore;
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'rescan_view_model.g.dart';
|
||||
|
@ -8,11 +10,12 @@ class RescanViewModel = RescanViewModelBase with _$RescanViewModel;
|
|||
enum RescanWalletState { rescaning, none }
|
||||
|
||||
abstract class RescanViewModelBase with Store {
|
||||
RescanViewModelBase(this._wallet)
|
||||
: state = RescanWalletState.none,
|
||||
isButtonEnabled = false;
|
||||
RescanViewModelBase(this.wallet)
|
||||
: state = RescanWalletState.none,
|
||||
isButtonEnabled = false,
|
||||
doSingleScan = false;
|
||||
|
||||
final WalletBase _wallet;
|
||||
final WalletBase wallet;
|
||||
|
||||
@observable
|
||||
RescanWalletState state;
|
||||
|
@ -20,11 +23,21 @@ abstract class RescanViewModelBase with Store {
|
|||
@observable
|
||||
bool isButtonEnabled;
|
||||
|
||||
@observable
|
||||
bool doSingleScan;
|
||||
|
||||
@computed
|
||||
bool get isSilentPaymentsScan => wallet.type == WalletType.bitcoin;
|
||||
|
||||
@action
|
||||
Future<void> rescanCurrentWallet({required int restoreHeight}) async {
|
||||
state = RescanWalletState.rescaning;
|
||||
await _wallet.rescan(height: restoreHeight);
|
||||
_wallet.transactionHistory.clear();
|
||||
if (wallet.type != WalletType.bitcoin) {
|
||||
wallet.rescan(height: restoreHeight);
|
||||
wallet.transactionHistory.clear();
|
||||
} else {
|
||||
bitcoin!.rescan(wallet, height: restoreHeight, doSingleScan: doSingleScan);
|
||||
}
|
||||
state = RescanWalletState.none;
|
||||
}
|
||||
}
|
|
@ -229,7 +229,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
|||
isFiatDisabled ? '' : pendingTransactionFeeFiatAmount + ' ' + fiat.title;
|
||||
|
||||
@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
|
||||
List<Template> get templates => sendTemplateViewModel.templates
|
||||
|
@ -599,6 +602,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
|||
walletType == WalletType.litecoin ||
|
||||
walletType == WalletType.bitcoinCash) {
|
||||
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());
|
||||
}
|
||||
if (error is TransactionNoInputsException) {
|
||||
|
@ -625,9 +632,15 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
|||
if (error is TransactionCommitFailedVoutNegative) {
|
||||
return S.current.tx_rejected_vout_negative;
|
||||
}
|
||||
if (error is TransactionCommitFailedBIP68Final) {
|
||||
return S.current.tx_rejected_bip68_final;
|
||||
}
|
||||
if (error is TransactionNoDustOnChangeException) {
|
||||
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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -15,7 +15,8 @@ abstract class UnspentCoinsItemBase with Store {
|
|||
required this.isChange,
|
||||
required this.amountRaw,
|
||||
required this.vout,
|
||||
required this.keyImage
|
||||
required this.keyImage,
|
||||
required this.isSilentPayment,
|
||||
});
|
||||
|
||||
@observable
|
||||
|
@ -47,4 +48,7 @@ abstract class UnspentCoinsItemBase with Store {
|
|||
|
||||
@observable
|
||||
String? keyImage;
|
||||
|
||||
@observable
|
||||
bool isSilentPayment;
|
||||
}
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import 'package:cake_wallet/bitcoin/bitcoin.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:cw_core/unspent_coins_info.dart';
|
||||
import 'package:cw_core/unspent_transaction_output.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
|
@ -86,22 +88,33 @@ abstract class UnspentCoinsListViewModelBase with Store {
|
|||
@action
|
||||
void _updateUnspentCoinsInfo() {
|
||||
_items.clear();
|
||||
_items.addAll(_getUnspents().map((elem) {
|
||||
final info =
|
||||
getUnspentCoinInfo(elem.hash, elem.address, elem.value, elem.vout, elem.keyImage);
|
||||
|
||||
return UnspentCoinsItem(
|
||||
address: elem.address,
|
||||
amount: '${formatAmountToString(elem.value)} ${wallet.currency.title}',
|
||||
hash: elem.hash,
|
||||
isFrozen: info.isFrozen,
|
||||
note: info.note,
|
||||
isSending: info.isSending,
|
||||
amountRaw: elem.value,
|
||||
vout: elem.vout,
|
||||
keyImage: elem.keyImage,
|
||||
isChange: elem.isChange,
|
||||
);
|
||||
}));
|
||||
List<UnspentCoinsItem> unspents = [];
|
||||
_getUnspents().forEach((elem) {
|
||||
try {
|
||||
final info =
|
||||
getUnspentCoinInfo(elem.hash, elem.address, elem.value, elem.vout, elem.keyImage);
|
||||
|
||||
unspents.add(UnspentCoinsItem(
|
||||
address: elem.address,
|
||||
amount: '${formatAmountToString(elem.value)} ${wallet.currency.title}',
|
||||
hash: elem.hash,
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
import 'package:cake_wallet/utils/list_item.dart';
|
||||
|
||||
class WalletAddressListHeader extends ListItem {}
|
||||
class WalletAddressListHeader extends ListItem {
|
||||
final String? title;
|
||||
WalletAddressListHeader({this.title});
|
||||
}
|
||||
|
|
|
@ -8,8 +8,10 @@ class WalletAddressListItem extends ListItem {
|
|||
this.name,
|
||||
this.txCount,
|
||||
this.balance,
|
||||
this.isChange = false})
|
||||
: super();
|
||||
this.isChange = false,
|
||||
// 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 bool isPrimary;
|
||||
|
@ -18,6 +20,7 @@ class WalletAddressListItem extends ListItem {
|
|||
final int? txCount;
|
||||
final String? balance;
|
||||
final bool isChange;
|
||||
final bool? isOneTimeReceiveAddress;
|
||||
|
||||
@override
|
||||
String toString() => name ?? address;
|
||||
|
|
|
@ -334,20 +334,55 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
|||
}
|
||||
|
||||
if (isElectrumWallet) {
|
||||
final addressItems = bitcoin!.getSubAddresses(wallet).map((subaddress) {
|
||||
final isPrimary = subaddress.id == 0;
|
||||
if (bitcoin!.hasSelectedSilentPayments(wallet)) {
|
||||
final addressItems = bitcoin!.getSilentPaymentAddresses(wallet).map((address) {
|
||||
final isPrimary = address.id == 0;
|
||||
|
||||
return WalletAddressListItem(
|
||||
id: subaddress.id,
|
||||
return WalletAddressListItem(
|
||||
id: address.id,
|
||||
isPrimary: isPrimary,
|
||||
name: subaddress.name,
|
||||
address: subaddress.address,
|
||||
txCount: subaddress.txCount,
|
||||
name: address.name,
|
||||
address: address.address,
|
||||
txCount: address.txCount,
|
||||
balance: AmountConverter.amountIntToString(
|
||||
walletTypeToCryptoCurrency(type), subaddress.balance),
|
||||
isChange: subaddress.isChange);
|
||||
});
|
||||
addressList.addAll(addressItems);
|
||||
walletTypeToCryptoCurrency(type), address.balance),
|
||||
isChange: address.isChange,
|
||||
);
|
||||
});
|
||||
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) {
|
||||
|
@ -416,9 +451,14 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
|||
wallet.type == WalletType.litecoin ||
|
||||
wallet.type == WalletType.bitcoinCash;
|
||||
|
||||
@computed
|
||||
bool get isSilentPayments =>
|
||||
wallet.type == WalletType.bitcoin && bitcoin!.hasSelectedSilentPayments(wallet);
|
||||
|
||||
@computed
|
||||
bool get isAutoGenerateSubaddressEnabled =>
|
||||
_settingsStore.autoGenerateSubaddressStatus != AutoGenerateSubaddressStatus.disabled;
|
||||
_settingsStore.autoGenerateSubaddressStatus != AutoGenerateSubaddressStatus.disabled &&
|
||||
!isSilentPayments;
|
||||
|
||||
List<ListItem> _baseItems;
|
||||
|
||||
|
@ -479,4 +519,11 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
|||
amount = '';
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
void deleteAddress(ListItem item) {
|
||||
if (wallet.type == WalletType.bitcoin && item is WalletAddressListItem) {
|
||||
bitcoin!.deleteSilentPaymentAddress(wallet, item.address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ class WalletListItem {
|
|||
required this.key,
|
||||
this.isCurrent = false,
|
||||
this.isEnabled = true,
|
||||
this.isTestnet = false,
|
||||
});
|
||||
|
||||
final String name;
|
||||
|
@ -14,4 +15,5 @@ class WalletListItem {
|
|||
final bool isCurrent;
|
||||
final dynamic key;
|
||||
final bool isEnabled;
|
||||
final bool isTestnet;
|
||||
}
|
||||
|
|
|
@ -61,6 +61,7 @@ abstract class WalletListViewModelBase with Store {
|
|||
key: info.key,
|
||||
isCurrent: info.name == _appStore.wallet?.name && info.type == _appStore.wallet?.type,
|
||||
isEnabled: availableWalletTypes.contains(info.type),
|
||||
isTestnet: info.network?.toLowerCase().contains('testnet') ?? false,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -47,6 +47,8 @@ PODS:
|
|||
- shared_preferences_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- sp_scanner (0.0.1):
|
||||
- FlutterMacOS
|
||||
- url_launcher_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
- wakelock_plus (0.0.1):
|
||||
|
@ -67,6 +69,7 @@ DEPENDENCIES:
|
|||
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- share_plus_macos (from `Flutter/ephemeral/.symlinks/plugins/share_plus_macos/macos`)
|
||||
- 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`)
|
||||
- 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
|
||||
shared_preferences_foundation:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin
|
||||
sp_scanner:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/sp_scanner/macos
|
||||
url_launcher_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
||||
wakelock_plus:
|
||||
|
@ -126,7 +131,8 @@ SPEC CHECKSUMS:
|
|||
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
|
||||
share_plus_macos: 853ee48e7dce06b633998ca0735d482dd671ade4
|
||||
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||
url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95
|
||||
sp_scanner: 269d96e0ec3173e69156be7239b95182be3b8303
|
||||
url_launcher_macos: 5f437abeda8c85500ceb03f5c1938a8c5a705399
|
||||
wakelock_plus: 4783562c9a43d209c458cb9b30692134af456269
|
||||
|
||||
PODFILE CHECKSUM: 65ec1541137fb5b35d00490dec1bb48d4d9586bb
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
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_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_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_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_tron && 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_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_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_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_tron; flutter pub get; flutter packages pub run build_runner build --delete-conflicting-outputs; 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
|
||||
|
|
|
@ -105,7 +105,7 @@ dependencies:
|
|||
solana: ^0.30.1
|
||||
bitcoin_base:
|
||||
git:
|
||||
url: https://github.com/cake-tech/bitcoin_base.git
|
||||
url: https://github.com/cake-tech/bitcoin_base
|
||||
ref: cake-mweb
|
||||
ledger_flutter: ^1.0.1
|
||||
|
||||
|
@ -117,7 +117,7 @@ dev_dependencies:
|
|||
mobx_codegen: ^2.1.1
|
||||
build_resolvers: ^2.0.9
|
||||
hive_generator: ^1.1.3
|
||||
flutter_launcher_icons: ^0.11.0
|
||||
# flutter_launcher_icons: ^0.11.0
|
||||
# check flutter_launcher_icons for usage
|
||||
pedantic: ^1.8.0
|
||||
# replace https://github.com/dart-lang/lints#migrating-from-packagepedantic
|
||||
|
|
|
@ -71,6 +71,7 @@
|
|||
"backup": "نسخ الاحتياطي",
|
||||
"backup_file": "ملف النسخ الاحتياطي",
|
||||
"backup_password": "كلمة مرور النسخ الاحتياطي",
|
||||
"balance": "توازن",
|
||||
"balance_page": "صفحة التوازن",
|
||||
"bill_amount": "مبلغ الفاتورة",
|
||||
"billing_address_info": "إذا طُلب منك عنوان إرسال فواتير ، فأدخل عنوان الشحن الخاص بك",
|
||||
|
@ -78,6 +79,7 @@
|
|||
"bitcoin_dark_theme": "موضوع البيتكوين الظلام",
|
||||
"bitcoin_light_theme": "موضوع البيتكوين الخفيفة",
|
||||
"bitcoin_payments_require_1_confirmation": "تتطلب مدفوعات Bitcoin تأكيدًا واحدًا ، والذي قد يستغرق 20 دقيقة أو أكثر. شكرا لصبرك! سيتم إرسال بريد إلكتروني إليك عند تأكيد الدفع.",
|
||||
"block_remaining": "1 كتلة متبقية",
|
||||
"Blocks_remaining": "بلوك متبقي ${status}",
|
||||
"bluetooth": "بلوتوث",
|
||||
"bright_theme": "مشرق",
|
||||
|
@ -139,6 +141,7 @@
|
|||
"confirm_fee_deduction": "تأكيد خصم الرسوم",
|
||||
"confirm_fee_deduction_content": "هل توافق على خصم الرسوم من الإخراج؟",
|
||||
"confirm_sending": "تأكيد الإرسال",
|
||||
"confirm_silent_payments_switch_node": "حاليا مطلوب لتبديل العقد لمسح المدفوعات الصامتة",
|
||||
"confirmations": "التأكيدات",
|
||||
"confirmed": "رصيد مؤكد",
|
||||
"confirmed_tx": "مؤكد",
|
||||
|
@ -221,6 +224,7 @@
|
|||
"electrum_address_disclaimer": "نقوم بإنشاء عناوين جديدة في كل مرة تستخدم فيها عنوانًا ، لكن العناوين السابقة تستمر في العمل",
|
||||
"email_address": "عنوان البريد الالكترونى",
|
||||
"enable_replace_by_fee": "تمكين الاستبدال",
|
||||
"enable_silent_payments_scanning": "تمكين المسح الضوئي للمدفوعات الصامتة",
|
||||
"enabled": "ممكنة",
|
||||
"enter_amount": "أدخل المبلغ",
|
||||
"enter_backup_password": "أدخل كلمة المرور الاحتياطية هنا",
|
||||
|
@ -278,6 +282,7 @@
|
|||
"extracted_address_content": "سوف ترسل الأموال إلى\n${recipient_name}",
|
||||
"failed_authentication": "${state_error} فشل المصادقة.",
|
||||
"faq": "الأسئلة الشائعة",
|
||||
"features": "سمات",
|
||||
"fetching": "جار الجلب",
|
||||
"fiat_api": "Fiat API",
|
||||
"fiat_balance": "الرصيد فيات",
|
||||
|
@ -531,6 +536,7 @@
|
|||
"save_backup_password_alert": "حفظ كلمة المرور الاحتياطية",
|
||||
"save_to_downloads": "ﺕﻼﻳﺰﻨﺘﻟﺍ ﻲﻓ ﻆﻔﺣ",
|
||||
"saved_the_trade_id": "لقد تم حفظ معرف العملية",
|
||||
"scan_one_block": "مسح كتلة واحدة",
|
||||
"scan_qr_code": "امسح رمز QR ضوئيًا",
|
||||
"scan_qr_code_to_get_address": "امسح ال QR للحصول على العنوان",
|
||||
"scan_qr_on_device": " ﺮﺧﺁ ﺯﺎﻬﺟ ﻰﻠﻋ ﺎﻴًﺋﻮﺿ ﺍﺬﻫ ﺔﻌﻳﺮﺴﻟﺍ ﺔﺑﺎﺠﺘﺳﻻﺍ ﺰﻣﺭ ﺢﺴﻤﺑ ﻢﻗ",
|
||||
|
@ -641,11 +647,22 @@
|
|||
"sign_up": "اشتراك",
|
||||
"signTransaction": " ﺔﻠﻣﺎﻌﻤﻟﺍ ﻊﻴﻗﻮﺗ",
|
||||
"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": "قابل للانزلاق",
|
||||
"sort_by": "ترتيب حسب",
|
||||
"spend_key_private": "مفتاح الإنفاق (خاص)",
|
||||
"spend_key_public": "مفتاح الإنفاق (عام)",
|
||||
"status": "الحالة:",
|
||||
"string_default": "تقصير",
|
||||
"subaddress_title": "قائمة العناوين الفرعية",
|
||||
"subaddresses": "العناوين الفرعية",
|
||||
"submit_request": "تقديم طلب",
|
||||
|
@ -670,10 +687,13 @@
|
|||
"sync_status_starting_sync": "بدء المزامنة",
|
||||
"sync_status_syncronized": "متزامن",
|
||||
"sync_status_syncronizing": "يتم المزامنة",
|
||||
"sync_status_timed_out": "نفد وقته",
|
||||
"sync_status_unsupported": "عقدة غير مدعومة",
|
||||
"syncing_wallet_alert_content": "قد لا يكتمل رصيدك وقائمة المعاملات الخاصة بك حتى تظهر عبارة “SYNCHRONIZED“ في الأعلى. انقر / اضغط لمعرفة المزيد.",
|
||||
"syncing_wallet_alert_title": "محفظتك تتم مزامنتها",
|
||||
"template": "قالب",
|
||||
"template_name": "اسم القالب",
|
||||
"testnet_coins_no_value": "عملات TestNet ليس لها قيمة",
|
||||
"third_intro_content": "يعيش Yats خارج Cake Wallet أيضًا. يمكن استبدال أي عنوان محفظة على وجه الأرض بـ Yat!",
|
||||
"third_intro_title": "يتماشي Yat بلطف مع الآخرين",
|
||||
"thorchain_contract_address_not_supported": "لا يدعم Thorchain الإرسال إلى عنوان العقد",
|
||||
|
@ -749,13 +769,16 @@
|
|||
"trusted": "موثوق به",
|
||||
"tx_commit_exception_no_dust_on_change": "يتم رفض المعاملة مع هذا المبلغ. باستخدام هذه العملات المعدنية ، يمكنك إرسال ${min} دون تغيير أو ${max} الذي يعيد التغيير.",
|
||||
"tx_commit_failed": "فشل ارتكاب المعاملة. يرجى الاتصال بالدعم.",
|
||||
"tx_invalid_input": "أنت تستخدم نوع الإدخال الخاطئ لهذا النوع من الدفع",
|
||||
"tx_no_dust_exception": "يتم رفض المعاملة عن طريق إرسال مبلغ صغير جدًا. يرجى محاولة زيادة المبلغ.",
|
||||
"tx_not_enough_inputs_exception": "لا يكفي المدخلات المتاحة. الرجاء تحديد المزيد تحت التحكم في العملة",
|
||||
"tx_rejected_bip68_final": "تحتوي المعاملة على مدخلات غير مؤكدة وفشلت في استبدال الرسوم.",
|
||||
"tx_rejected_dust_change": "المعاملة التي يتم رفضها بموجب قواعد الشبكة ، ومبلغ التغيير المنخفض (الغبار). حاول إرسال كل أو تقليل المبلغ.",
|
||||
"tx_rejected_dust_output": "المعاملة التي يتم رفضها بموجب قواعد الشبكة ، وكمية الإخراج المنخفض (الغبار). يرجى زيادة المبلغ.",
|
||||
"tx_rejected_dust_output_send_all": "المعاملة التي يتم رفضها بموجب قواعد الشبكة ، وكمية الإخراج المنخفض (الغبار). يرجى التحقق من رصيد العملات المعدنية المحددة تحت التحكم في العملة.",
|
||||
"tx_rejected_vout_negative": "لا يوجد ما يكفي من الرصيد لدفع رسوم هذه الصفقة. يرجى التحقق من رصيد العملات المعدنية تحت السيطرة على العملة.",
|
||||
"tx_wrong_balance_exception": "ليس لديك ما يكفي من ${currency} لإرسال هذا المبلغ.",
|
||||
"tx_wrong_balance_with_amount_exception": "ليس لديك ما يكفي ${currency} لإرسال المبلغ الإجمالي ${amount}",
|
||||
"tx_zero_fee_exception": "لا يمكن إرسال معاملة مع 0 رسوم. حاول زيادة المعدل أو التحقق من اتصالك للحصول على أحدث التقديرات.",
|
||||
"unavailable_balance": "ﺮﻓﻮﺘﻣ ﺮﻴﻏ ﺪﻴﺻﺭ",
|
||||
"unavailable_balance_description": ".ﺎﻫﺪﻴﻤﺠﺗ ءﺎﻐﻟﺇ ﺭﺮﻘﺗ ﻰﺘﺣ ﺕﻼﻣﺎﻌﻤﻠﻟ ﻝﻮﺻﻮﻠﻟ ﺔﻠﺑﺎﻗ ﺮﻴﻏ ﺓﺪﻤﺠﻤﻟﺍ ﺓﺪﺻﺭﻷﺍ ﻞﻈﺗ ﺎﻤﻨﻴﺑ ،ﺎﻬﺑ ﺔﺻﺎﺨﻟﺍ ﺕﻼﻣﺎﻌﻤﻟﺍ ﻝﺎﻤﺘﻛﺍ ﺩﺮﺠﻤﺑ ﺔﺣﺎﺘﻣ ﺔﻠﻔﻘﻤﻟﺍ ﺓﺪﺻﺭﻷﺍ ﺢﺒﺼﺘﺳ .ﻚﺑ ﺔﺻﺎﺨﻟﺍ ﺕﻼﻤﻌﻟﺍ ﻲﻓ ﻢﻜﺤﺘﻟﺍ ﺕﺍﺩﺍﺪﻋﺇ ﻲﻓ ﻂﺸﻧ ﻞﻜﺸﺑ ﺎﻫﺪﻴﻤﺠﺘﺑ ﺖﻤﻗ",
|
||||
|
@ -809,6 +832,7 @@
|
|||
"warning": "تحذير",
|
||||
"welcome": "مرحبا بك في",
|
||||
"welcome_to_cakepay": "مرحبا بكم في Cake Pay!",
|
||||
"what_is_silent_payments": "ما هي المدفوعات الصامتة؟",
|
||||
"widgets_address": "عنوان",
|
||||
"widgets_or": "أو",
|
||||
"widgets_restore_from_blockheight": "استعادة من ارتفاع البلوك",
|
||||
|
|
|
@ -71,6 +71,7 @@
|
|||
"backup": "Резервно копие",
|
||||
"backup_file": "Резервно копие",
|
||||
"backup_password": "Парола за възстановяване",
|
||||
"balance": "Баланс",
|
||||
"balance_page": "Страница за баланс",
|
||||
"bill_amount": "Искана сума",
|
||||
"billing_address_info": "Ако Ви попитат за билинг адрес, въведето своя адрес за доставка",
|
||||
|
@ -78,6 +79,7 @@
|
|||
"bitcoin_dark_theme": "Тъмна тема за биткойн",
|
||||
"bitcoin_light_theme": "Лека биткойн тема",
|
||||
"bitcoin_payments_require_1_confirmation": "Плащанията с Bitcoin изискват потвърждение, което може да отнеме 20 минути или повече. Благодарим за търпението! Ще получите имейл, когато плащането е потвърдено.",
|
||||
"block_remaining": "1 блок останал",
|
||||
"Blocks_remaining": "${status} оставащи блока",
|
||||
"bluetooth": "Bluetooth",
|
||||
"bright_theme": "Ярко",
|
||||
|
@ -139,6 +141,7 @@
|
|||
"confirm_fee_deduction": "Потвърдете приспадането на таксите",
|
||||
"confirm_fee_deduction_content": "Съгласни ли сте да приспадате таксата от продукцията?",
|
||||
"confirm_sending": "Потвърждаване на изпращането",
|
||||
"confirm_silent_payments_switch_node": "Понастоящем се изисква да превключвате възлите за сканиране на мълчаливи плащания",
|
||||
"confirmations": "потвърждения",
|
||||
"confirmed": "Потвърден баланс",
|
||||
"confirmed_tx": "Потвърдено",
|
||||
|
@ -221,6 +224,7 @@
|
|||
"electrum_address_disclaimer": "Нови адреси се генерират всеки път, когато използвате този, но и предишните продължават да работят",
|
||||
"email_address": "Имейл адрес",
|
||||
"enable_replace_by_fee": "Активиране на замяна по забрана",
|
||||
"enable_silent_payments_scanning": "Активирайте безшумните плащания за сканиране",
|
||||
"enabled": "Активирано",
|
||||
"enter_amount": "Въведете сума",
|
||||
"enter_backup_password": "Въведете парола за възстановяване",
|
||||
|
@ -278,6 +282,7 @@
|
|||
"extracted_address_content": "Ще изпратите средства на \n${recipient_name}",
|
||||
"failed_authentication": "Неуспешно удостоверяване. ${state_error}",
|
||||
"faq": "FAQ",
|
||||
"features": "Характеристика",
|
||||
"fetching": "Обработване",
|
||||
"fiat_api": "Fiat API",
|
||||
"fiat_balance": "Фиат Баланс",
|
||||
|
@ -531,6 +536,7 @@
|
|||
"save_backup_password_alert": "Запазване на паролата за възстановяване",
|
||||
"save_to_downloads": "Запазване в Изтегляния",
|
||||
"saved_the_trade_id": "Запазих trade ID-то",
|
||||
"scan_one_block": "Сканирайте един блок",
|
||||
"scan_qr_code": "Сканирайте QR кода, за да получите адреса",
|
||||
"scan_qr_code_to_get_address": "Сканирайте QR кода, за да получите адреса",
|
||||
"scan_qr_on_device": "Сканирайте този QR код на друго устройство",
|
||||
|
@ -641,11 +647,22 @@
|
|||
"sign_up": "Регистрация",
|
||||
"signTransaction": "Подпишете транзакция",
|
||||
"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": "Плъзгащ се",
|
||||
"sort_by": "Сортирай по",
|
||||
"spend_key_private": "Spend key (таен)",
|
||||
"spend_key_public": "Spend key (публичен)",
|
||||
"status": "Статус: ",
|
||||
"string_default": "По подразбиране",
|
||||
"subaddress_title": "Лист от подадреси",
|
||||
"subaddresses": "Подадреси",
|
||||
"submit_request": "изпращане на заявка",
|
||||
|
@ -670,10 +687,13 @@
|
|||
"sync_status_starting_sync": "ЗАПОЧВАНЕ НА СИНХРОНИЗАЦИЯ",
|
||||
"sync_status_syncronized": "СИНХРОНИЗИРАНО",
|
||||
"sync_status_syncronizing": "СИНХРОНИЗИРАНЕ",
|
||||
"sync_status_timed_out": "ВРЕМЕТО ИЗТЕЧЕ",
|
||||
"sync_status_unsupported": "Неподдържан възел",
|
||||
"syncing_wallet_alert_content": "Списъкът ви с баланс и транзакции може да не е пълен, докато в горната част не пише „СИНХРОНИЗИРАН“. Кликнете/докоснете, за да научите повече.",
|
||||
"syncing_wallet_alert_title": "Вашият портфейл се синхронизира",
|
||||
"template": "Шаблон",
|
||||
"template_name": "Име на шаблон",
|
||||
"testnet_coins_no_value": "Тестовите монети нямат стойност",
|
||||
"third_intro_content": "Yats също живее извън Cake Wallet. Всеки адрес на портфейл може да бъде заменен с Yat!",
|
||||
"third_intro_title": "Yat добре се сработва с други",
|
||||
"thorchain_contract_address_not_supported": "Thorchain не подкрепя изпращането до адрес на договор",
|
||||
|
@ -749,13 +769,16 @@
|
|||
"trusted": "Надежден",
|
||||
"tx_commit_exception_no_dust_on_change": "Сделката се отхвърля с тази сума. С тези монети можете да изпратите ${min} без промяна или ${max}, която връща промяна.",
|
||||
"tx_commit_failed": "Компетацията на транзакцията не успя. Моля, свържете се с поддръжката.",
|
||||
"tx_invalid_input": "Използвате грешен тип вход за този тип плащане",
|
||||
"tx_no_dust_exception": "Сделката се отхвърля чрез изпращане на сума твърде малка. Моля, опитайте да увеличите сумата.",
|
||||
"tx_not_enough_inputs_exception": "Няма достатъчно налични входове. Моля, изберете повече под контрол на монети",
|
||||
"tx_rejected_bip68_final": "Сделката има непотвърдени входове и не успя да се замени с такса.",
|
||||
"tx_rejected_dust_change": "Транзакция, отхвърлена от мрежови правила, ниска сума на промяна (прах). Опитайте да изпратите всички или да намалите сумата.",
|
||||
"tx_rejected_dust_output": "Транзакция, отхвърлена от мрежови правила, ниска стойност на изхода (прах). Моля, увеличете сумата.",
|
||||
"tx_rejected_dust_output_send_all": "Транзакция, отхвърлена от мрежови правила, ниска стойност на изхода (прах). Моля, проверете баланса на монетите, избрани под контрол на монети.",
|
||||
"tx_rejected_vout_negative": "Няма достатъчно баланс, за да платите за таксите на тази транзакция. Моля, проверете баланса на монетите под контрол на монетите.",
|
||||
"tx_wrong_balance_exception": "Нямате достатъчно ${currency}, за да изпратите тази сума.",
|
||||
"tx_wrong_balance_with_amount_exception": "Нямате достатъчно ${currency} За да изпратите общата сума на ${amount}",
|
||||
"tx_zero_fee_exception": "Не може да изпраща транзакция с 0 такса. Опитайте да увеличите скоростта или да проверите връзката си за най -новите оценки.",
|
||||
"unavailable_balance": "Неналично салдо",
|
||||
"unavailable_balance_description": "Неналично салдо: Тази обща сума включва средства, които са заключени в чакащи транзакции и тези, които сте замразили активно в настройките за контрол на монетите. Заключените баланси ще станат достъпни, след като съответните им транзакции бъдат завършени, докато замразените баланси остават недостъпни за транзакции, докато не решите да ги размразите.",
|
||||
|
@ -809,6 +832,7 @@
|
|||
"warning": "Внимание",
|
||||
"welcome": "Добре дошли в",
|
||||
"welcome_to_cakepay": "Добре дошли в Cake Pay!",
|
||||
"what_is_silent_payments": "Какво са мълчаливи плащания?",
|
||||
"widgets_address": "Адрес",
|
||||
"widgets_or": "или",
|
||||
"widgets_restore_from_blockheight": "Възстановяване от blockheight",
|
||||
|
|
|
@ -71,6 +71,7 @@
|
|||
"backup": "Záloha",
|
||||
"backup_file": "Soubor se zálohou",
|
||||
"backup_password": "Heslo pro zálohy",
|
||||
"balance": "Zůstatek",
|
||||
"balance_page": "Stránka zůstatku",
|
||||
"bill_amount": "Účtovaná částka",
|
||||
"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_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.",
|
||||
"block_remaining": "1 blok zbývající",
|
||||
"Blocks_remaining": "Zbývá ${status} bloků",
|
||||
"bluetooth": "Bluetooth",
|
||||
"bright_theme": "Jasný",
|
||||
|
@ -139,6 +141,7 @@
|
|||
"confirm_fee_deduction": "Potvrďte odpočet poplatků",
|
||||
"confirm_fee_deduction_content": "Souhlasíte s odečtením poplatku z výstupu?",
|
||||
"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í",
|
||||
"confirmed": "Potvrzený zůstatek",
|
||||
"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í",
|
||||
"email_address": "E-mailová adresa",
|
||||
"enable_replace_by_fee": "Povolit výměnu podle poplatku",
|
||||
"enable_silent_payments_scanning": "Povolte skenování tichých plateb",
|
||||
"enabled": "Povoleno",
|
||||
"enter_amount": "Zadejte částku",
|
||||
"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}",
|
||||
"failed_authentication": "Ověřování selhalo. ${state_error}",
|
||||
"faq": "FAQ",
|
||||
"features": "Funkce",
|
||||
"fetching": "Načítá se",
|
||||
"fiat_api": "Fiat API",
|
||||
"fiat_balance": "Fiat Balance",
|
||||
|
@ -531,6 +536,7 @@
|
|||
"save_backup_password_alert": "Uložit heslo pro zálohy",
|
||||
"save_to_downloads": "Uložit do Stažených souborů",
|
||||
"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_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í",
|
||||
|
@ -641,11 +647,22 @@
|
|||
"sign_up": "Registrovat se",
|
||||
"signTransaction": "Podepsat transakci",
|
||||
"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é",
|
||||
"sort_by": "Seřazeno podle",
|
||||
"spend_key_private": "Klíč pro platby (soukromý)",
|
||||
"spend_key_public": "Klíč pro platby (veřejný)",
|
||||
"status": "Status: ",
|
||||
"string_default": "Výchozí",
|
||||
"subaddress_title": "Seznam subadres",
|
||||
"subaddresses": "Subadresy",
|
||||
"submit_request": "odeslat požadavek",
|
||||
|
@ -670,10 +687,13 @@
|
|||
"sync_status_starting_sync": "SPOUŠTĚNÍ SYNCHRONIZACE",
|
||||
"sync_status_syncronized": "SYNCHRONIZOVÁNO",
|
||||
"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_title": "Vaše peněženka se synchronizuje",
|
||||
"template": "Šablona",
|
||||
"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_title": "Yat dobře spolupracuje s ostatními",
|
||||
"thorchain_contract_address_not_supported": "Thorchain nepodporuje odeslání na adresu smlouvy",
|
||||
|
@ -749,13 +769,16 @@
|
|||
"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_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_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_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_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_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.",
|
||||
"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.",
|
||||
|
@ -809,6 +832,7 @@
|
|||
"warning": "Varování",
|
||||
"welcome": "Vítejte v",
|
||||
"welcome_to_cakepay": "Vítejte v Cake Pay!",
|
||||
"what_is_silent_payments": "Co jsou tiché platby?",
|
||||
"widgets_address": "Adresa",
|
||||
"widgets_or": "nebo",
|
||||
"widgets_restore_from_blockheight": "Obnovit z výšky bloku",
|
||||
|
|
|
@ -71,6 +71,7 @@
|
|||
"backup": "Sicherung",
|
||||
"backup_file": "Sicherungsdatei",
|
||||
"backup_password": "Passwort sichern",
|
||||
"balance": "Gleichgewicht",
|
||||
"balance_page": "Balance-Seite",
|
||||
"bill_amount": "Rechnungsbetrag",
|
||||
"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_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.",
|
||||
"block_remaining": "1 Block verbleibend",
|
||||
"Blocks_remaining": "${status} verbleibende Blöcke",
|
||||
"bluetooth": "Bluetooth",
|
||||
"bright_theme": "Strahlend hell",
|
||||
|
@ -139,6 +141,7 @@
|
|||
"confirm_fee_deduction": "Gebührenabzug bestätigen",
|
||||
"confirm_fee_deduction_content": "Stimmen Sie zu, die Gebühr von der Ausgabe abzuziehen?",
|
||||
"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",
|
||||
"confirmed": "Bestätigter Saldo",
|
||||
"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",
|
||||
"email_address": "E-Mail-Adresse",
|
||||
"enable_replace_by_fee": "Aktivieren Sie Ersatz für Fee",
|
||||
"enable_silent_payments_scanning": "Aktivieren Sie stille Zahlungen Scannen",
|
||||
"enabled": "Ermöglicht",
|
||||
"enter_amount": "Betrag eingeben",
|
||||
"enter_backup_password": "Sicherungskennwort hier eingeben",
|
||||
|
@ -278,6 +282,7 @@
|
|||
"extracted_address_content": "Sie senden Geld an\n${recipient_name}",
|
||||
"failed_authentication": "Authentifizierung fehlgeschlagen. ${state_error}",
|
||||
"faq": "Häufig gestellte Fragen",
|
||||
"features": "Merkmale",
|
||||
"fetching": "Frage ab",
|
||||
"fiat_api": "Fiat API",
|
||||
"fiat_balance": "Fiat Balance",
|
||||
|
@ -532,6 +537,7 @@
|
|||
"save_backup_password_alert": "Sicherungskennwort speichern",
|
||||
"save_to_downloads": "Unter „Downloads“ speichern",
|
||||
"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_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",
|
||||
|
@ -642,11 +648,22 @@
|
|||
"sign_up": "Anmelden",
|
||||
"signTransaction": "Transaktion unterzeichnen",
|
||||
"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",
|
||||
"sort_by": "Sortiere nach",
|
||||
"spend_key_private": "Spend Key (geheim)",
|
||||
"spend_key_public": "Spend Key (öffentlich)",
|
||||
"status": "Status: ",
|
||||
"string_default": "Standard",
|
||||
"subaddress_title": "Unteradressenliste",
|
||||
"subaddresses": "Unteradressen",
|
||||
"submit_request": "Eine Anfrage stellen",
|
||||
|
@ -671,10 +688,13 @@
|
|||
"sync_status_starting_sync": "STARTE SYNCHRONISIERUNG",
|
||||
"sync_status_syncronized": "SYNCHRONISIERT",
|
||||
"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_title": "Ihr Wallet wird synchronisiert",
|
||||
"template": "Vorlage",
|
||||
"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_title": "Yat spielt gut mit anderen",
|
||||
"thorchain_contract_address_not_supported": "Thorchain unterstützt das Senden an eine Vertragsadresse nicht",
|
||||
|
@ -750,13 +770,16 @@
|
|||
"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_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_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_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_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_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.",
|
||||
"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.",
|
||||
|
@ -812,6 +835,7 @@
|
|||
"warning": "Warnung",
|
||||
"welcome": "Willkommen bei",
|
||||
"welcome_to_cakepay": "Willkommen bei Cake Pay!",
|
||||
"what_is_silent_payments": "Was sind stille Zahlungen?",
|
||||
"widgets_address": "Adresse",
|
||||
"widgets_or": "oder",
|
||||
"widgets_restore_from_blockheight": "Ab Blockhöhe wiederherstellen",
|
||||
|
|
|
@ -71,6 +71,7 @@
|
|||
"backup": "Backup",
|
||||
"backup_file": "Backup file",
|
||||
"backup_password": "Backup password",
|
||||
"balance": "Balance",
|
||||
"balance_page": "Balance Page",
|
||||
"bill_amount": "Bill Amount",
|
||||
"billing_address_info": "If asked for a billing address, provide your shipping address",
|
||||
|
@ -78,6 +79,7 @@
|
|||
"bitcoin_dark_theme": "Bitcoin Dark 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.",
|
||||
"block_remaining": "1 Block Remaining",
|
||||
"Blocks_remaining": "${status} Blocks Remaining",
|
||||
"bluetooth": "Bluetooth",
|
||||
"bright_theme": "Bright",
|
||||
|
@ -139,6 +141,7 @@
|
|||
"confirm_fee_deduction": "Confirm Fee Deduction",
|
||||
"confirm_fee_deduction_content": "Do you agree to deduct the fee from the output?",
|
||||
"confirm_sending": "Confirm sending",
|
||||
"confirm_silent_payments_switch_node": "Currently it is required to switch nodes to scan silent payments",
|
||||
"confirmations": "Confirmations",
|
||||
"confirmed": "Confirmed Balance",
|
||||
"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",
|
||||
"email_address": "Email Address",
|
||||
"enable_replace_by_fee": "Enable Replace-By-Fee",
|
||||
"enable_silent_payments_scanning": "Enable silent payments scanning",
|
||||
"enabled": "Enabled",
|
||||
"enter_amount": "Enter Amount",
|
||||
"enter_backup_password": "Enter backup password here",
|
||||
|
@ -278,6 +282,7 @@
|
|||
"extracted_address_content": "You will be sending funds to\n${recipient_name}",
|
||||
"failed_authentication": "Failed authentication. ${state_error}",
|
||||
"faq": "FAQ",
|
||||
"features": "Features",
|
||||
"fetching": "Fetching",
|
||||
"fiat_api": "Fiat API",
|
||||
"fiat_balance": "Fiat Balance",
|
||||
|
@ -531,6 +536,7 @@
|
|||
"save_backup_password_alert": "Save backup password",
|
||||
"save_to_downloads": "Save to Downloads",
|
||||
"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_to_get_address": "Scan the QR code to get the address",
|
||||
"scan_qr_on_device": "Scan this QR code on another device",
|
||||
|
@ -641,11 +647,22 @@
|
|||
"sign_up": "Sign Up",
|
||||
"signTransaction": "Sign Transaction",
|
||||
"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",
|
||||
"sort_by": "Sort by",
|
||||
"spend_key_private": "Spend key (private)",
|
||||
"spend_key_public": "Spend key (public)",
|
||||
"status": "Status: ",
|
||||
"string_default": "Default",
|
||||
"subaddress_title": "Subaddress list",
|
||||
"subaddresses": "Subaddresses",
|
||||
"submit_request": "submit a request",
|
||||
|
@ -670,10 +687,13 @@
|
|||
"sync_status_starting_sync": "STARTING SYNC",
|
||||
"sync_status_syncronized": "SYNCHRONIZED",
|
||||
"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_title": "Your wallet is syncing",
|
||||
"template": "Template",
|
||||
"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_title": "Yat plays nicely with others",
|
||||
"thorchain_contract_address_not_supported": "THORChain does not support sending to a contract address",
|
||||
|
@ -749,13 +769,16 @@
|
|||
"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_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_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_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_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_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.",
|
||||
"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.",
|
||||
|
@ -809,6 +832,7 @@
|
|||
"warning": "Warning",
|
||||
"welcome": "Welcome to",
|
||||
"welcome_to_cakepay": "Welcome to Cake Pay!",
|
||||
"what_is_silent_payments": "What is silent payments?",
|
||||
"widgets_address": "Address",
|
||||
"widgets_or": "or",
|
||||
"widgets_restore_from_blockheight": "Restore from blockheight",
|
||||
|
|
|
@ -71,6 +71,7 @@
|
|||
"backup": "Apoyo",
|
||||
"backup_file": "Archivo de respaldo",
|
||||
"backup_password": "Contraseña de respaldo",
|
||||
"balance": "Balance",
|
||||
"balance_page": "Página de saldo",
|
||||
"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",
|
||||
|
@ -78,6 +79,7 @@
|
|||
"bitcoin_dark_theme": "Tema oscuro 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.",
|
||||
"block_remaining": "1 bloqueo restante",
|
||||
"Blocks_remaining": "${status} Bloques restantes",
|
||||
"bluetooth": "Bluetooth",
|
||||
"bright_theme": "Brillante",
|
||||
|
@ -139,6 +141,7 @@
|
|||
"confirm_fee_deduction": "Confirmar la deducción de la tarifa",
|
||||
"confirm_fee_deduction_content": "¿Acepta deducir la tarifa de la producción?",
|
||||
"confirm_sending": "Confirmar envío",
|
||||
"confirm_silent_payments_switch_node": "Actualmente se requiere cambiar los nodos para escanear pagos silenciosos",
|
||||
"confirmations": "Confirmaciones",
|
||||
"confirmed": "Saldo 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",
|
||||
"email_address": "Dirección de correo electrónico",
|
||||
"enable_replace_by_fee": "Habilitar reemplazar por tarea",
|
||||
"enable_silent_payments_scanning": "Habilitar escaneo de pagos silenciosos",
|
||||
"enabled": "Activado",
|
||||
"enter_amount": "Ingrese la cantidad",
|
||||
"enter_backup_password": "Ingrese la contraseña de respaldo aquí",
|
||||
|
@ -278,6 +282,7 @@
|
|||
"extracted_address_content": "Enviará fondos a\n${recipient_name}",
|
||||
"failed_authentication": "Autenticación fallida. ${state_error}",
|
||||
"faq": "FAQ",
|
||||
"features": "Características",
|
||||
"fetching": "Cargando",
|
||||
"fiat_api": "Fiat API",
|
||||
"fiat_balance": "Equilibrio Fiat",
|
||||
|
@ -532,6 +537,7 @@
|
|||
"save_backup_password_alert": "Guardar contraseña de respaldo",
|
||||
"save_to_downloads": "Guardar en Descargas",
|
||||
"saved_the_trade_id": "He salvado comercial ID",
|
||||
"scan_one_block": "Escanear un bloque",
|
||||
"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_on_device": "Escanea este código QR en otro dispositivo",
|
||||
|
@ -642,11 +648,22 @@
|
|||
"sign_up": "Registrarse",
|
||||
"signTransaction": "Firmar transacción",
|
||||
"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",
|
||||
"sort_by": "Ordenar por",
|
||||
"spend_key_private": "Spend clave (privado)",
|
||||
"spend_key_public": "Spend clave (público)",
|
||||
"status": "Estado: ",
|
||||
"string_default": "Por defecto",
|
||||
"subaddress_title": "Lista de subdirecciones",
|
||||
"subaddresses": "Subdirecciones",
|
||||
"submit_request": "presentar una solicitud",
|
||||
|
@ -671,10 +688,13 @@
|
|||
"sync_status_starting_sync": "EMPEZANDO A SINCRONIZAR",
|
||||
"sync_status_syncronized": "SINCRONIZADO",
|
||||
"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_title": "Tu billetera se está sincronizando",
|
||||
"template": "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_title": "Yat juega muy bien con otras",
|
||||
"thorchain_contract_address_not_supported": "Thorchain no admite enviar a una dirección de contrato",
|
||||
|
@ -750,13 +770,16 @@
|
|||
"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_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_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_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_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_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.",
|
||||
"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.",
|
||||
|
@ -810,6 +833,7 @@
|
|||
"warning": "Advertencia",
|
||||
"welcome": "Bienvenido",
|
||||
"welcome_to_cakepay": "¡Bienvenido a Cake Pay!",
|
||||
"what_is_silent_payments": "¿Qué son los pagos silenciosos?",
|
||||
"widgets_address": "Dirección",
|
||||
"widgets_or": "o",
|
||||
"widgets_restore_from_blockheight": "Restaurar desde blockheight",
|
||||
|
|
|
@ -71,6 +71,7 @@
|
|||
"backup": "Sauvegarde",
|
||||
"backup_file": "Fichier de sauvegarde",
|
||||
"backup_password": "Mot de passe de sauvegarde",
|
||||
"balance": "Équilibre",
|
||||
"balance_page": "Page Solde",
|
||||
"bill_amount": "Montant de la facture",
|
||||
"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_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é.",
|
||||
"block_remaining": "1 bloc restant",
|
||||
"Blocks_remaining": "Blocs Restants : ${status}",
|
||||
"bluetooth": "Bluetooth",
|
||||
"bright_theme": "Vif",
|
||||
|
@ -139,6 +141,7 @@
|
|||
"confirm_fee_deduction": "Confirmer la déduction des frais",
|
||||
"confirm_fee_deduction_content": "Acceptez-vous de déduire les frais de la production?",
|
||||
"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",
|
||||
"confirmed": "Solde 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",
|
||||
"email_address": "Adresse e-mail",
|
||||
"enable_replace_by_fee": "Activer Remplace-by-Fee",
|
||||
"enable_silent_payments_scanning": "Activer la numérisation des paiements silencieux",
|
||||
"enabled": "Activé",
|
||||
"enter_amount": "Entrez le montant",
|
||||
"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}",
|
||||
"failed_authentication": "Échec d'authentification. ${state_error}",
|
||||
"faq": "FAQ",
|
||||
"features": "Caractéristiques",
|
||||
"fetching": "Récupération",
|
||||
"fiat_api": "Fiat API",
|
||||
"fiat_balance": "Solde fiat",
|
||||
|
@ -531,6 +536,7 @@
|
|||
"save_backup_password_alert": "Enregistrer le mot de passe de sauvegarde",
|
||||
"save_to_downloads": "Enregistrer dans les téléchargements",
|
||||
"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_to_get_address": "Scannez le QR code pour obtenir l'adresse",
|
||||
"scan_qr_on_device": "Scannez ce code QR sur un autre appareil",
|
||||
|
@ -641,11 +647,22 @@
|
|||
"sign_up": "S'inscrire",
|
||||
"signTransaction": "Signer une transaction",
|
||||
"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",
|
||||
"sort_by": "Trier par",
|
||||
"spend_key_private": "Clef de dépense (spend key) (privée)",
|
||||
"spend_key_public": "Clef de dépense (spend key) (publique)",
|
||||
"status": "Statut : ",
|
||||
"string_default": "Défaut",
|
||||
"subaddress_title": "Liste des sous-adresses",
|
||||
"subaddresses": "Sous-adresses",
|
||||
"submit_request": "soumettre une requête",
|
||||
|
@ -670,10 +687,13 @@
|
|||
"sync_status_starting_sync": "DÉBUT DE SYNCHRO",
|
||||
"sync_status_syncronized": "SYNCHRONISÉ",
|
||||
"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_title": "Votre portefeuille (wallet) est en cours de synchronisation",
|
||||
"template": "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_title": "Yat est universel",
|
||||
"thorchain_contract_address_not_supported": "Thorchain ne prend pas en charge l'envoi à une adresse de contrat",
|
||||
|
@ -749,13 +769,16 @@
|
|||
"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_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_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_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_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_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.",
|
||||
"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.",
|
||||
|
@ -809,6 +832,7 @@
|
|||
"warning": "Avertissement",
|
||||
"welcome": "Bienvenue sur",
|
||||
"welcome_to_cakepay": "Bienvenue sur Cake Pay !",
|
||||
"what_is_silent_payments": "Qu'est-ce que les paiements silencieux?",
|
||||
"widgets_address": "Adresse",
|
||||
"widgets_or": "ou",
|
||||
"widgets_restore_from_blockheight": "Restaurer depuis une hauteur de bloc",
|
||||
|
|
|
@ -71,6 +71,7 @@
|
|||
"backup": "Ajiyayyen",
|
||||
"backup_file": "Ajiyayyen fayil",
|
||||
"backup_password": "Ajiyayyen kalmar sirri",
|
||||
"balance": "Ma'auni",
|
||||
"balance_page": "Ma'auni Page",
|
||||
"bill_amount": "Adadin Bill",
|
||||
"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_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.",
|
||||
"block_remaining": "1 toshe ragowar",
|
||||
"Blocks_remaining": "${status} Katanga ya rage",
|
||||
"bluetooth": "Bluetooth",
|
||||
"bright_theme": "Mai haske",
|
||||
|
@ -139,6 +141,7 @@
|
|||
"confirm_fee_deduction": "Tabbatar da cire kudade",
|
||||
"confirm_fee_deduction_content": "Shin kun yarda ku cire kuɗin daga fitarwa?",
|
||||
"confirm_sending": "Tabbatar da aikawa",
|
||||
"confirm_silent_payments_switch_node": "A halin yanzu ana buƙatar sauya nodes don bincika biyan siliki",
|
||||
"confirmations": "Tabbatar",
|
||||
"confirmed": "An 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",
|
||||
"email_address": "Adireshin i-mel",
|
||||
"enable_replace_by_fee": "Ba da damar maye gurbin-by-kudin",
|
||||
"enable_silent_payments_scanning": "Kunna biya biya",
|
||||
"enabled": "An kunna",
|
||||
"enter_amount": "Shigar da Adadi",
|
||||
"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}",
|
||||
"failed_authentication": "Binne wajen shiga. ${state_error}",
|
||||
"faq": "FAQ",
|
||||
"features": "Fasas",
|
||||
"fetching": "Daukewa",
|
||||
"fiat_api": "API ɗin Fiat",
|
||||
"fiat_balance": "Fiat Balance",
|
||||
|
@ -533,6 +538,7 @@
|
|||
"save_backup_password_alert": "Ajiye kalmar sirri ta ajiya",
|
||||
"save_to_downloads": "Ajiye zuwa Zazzagewa",
|
||||
"saved_the_trade_id": "Na ajiye ID na ciniki",
|
||||
"scan_one_block": "Duba toshe daya",
|
||||
"scan_qr_code": "Gani QR kodin",
|
||||
"scan_qr_code_to_get_address": "Duba lambar QR don samun adireshin",
|
||||
"scan_qr_on_device": "Duba wannan lambar QR akan wata na'ura",
|
||||
|
@ -643,11 +649,22 @@
|
|||
"sign_up": "Shiga",
|
||||
"signTransaction": "Sa hannu Ma'amala",
|
||||
"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",
|
||||
"sort_by": "Kasa",
|
||||
"spend_key_private": "makullin biya (maɓallin kalmar sirri)",
|
||||
"spend_key_public": "makullin biya (maɓallin jama'a)",
|
||||
"status": "Matsayi:",
|
||||
"string_default": "Ƙin cika alƙawari",
|
||||
"subaddress_title": "Jagorar subaddress",
|
||||
"subaddresses": "Subaddresses",
|
||||
"submit_request": "gabatar da bukata",
|
||||
|
@ -672,10 +689,13 @@
|
|||
"sync_status_starting_sync": "KWAFI",
|
||||
"sync_status_syncronized": "KYAU",
|
||||
"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_title": "Walat ɗin ku yana aiki tare",
|
||||
"template": "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_title": "Yat yana wasa da kyau tare da wasu",
|
||||
"thorchain_contract_address_not_supported": "Thorchain baya goyon bayan aika zuwa adireshin kwangila",
|
||||
|
@ -751,13 +771,16 @@
|
|||
"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_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_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_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_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_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.",
|
||||
"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.",
|
||||
|
@ -811,6 +834,7 @@
|
|||
"warning": "Gargadi",
|
||||
"welcome": "Barka da zuwa",
|
||||
"welcome_to_cakepay": "Barka da zuwa Cake Pay!",
|
||||
"what_is_silent_payments": "Menene biyan shiru?",
|
||||
"widgets_address": "Adireshin",
|
||||
"widgets_or": "ko",
|
||||
"widgets_restore_from_blockheight": "Sake dawo da daga blockheight",
|
||||
|
|
|
@ -71,6 +71,7 @@
|
|||
"backup": "बैकअप",
|
||||
"backup_file": "बैकअपफ़ाइल",
|
||||
"backup_password": "बैकअप पासवर्ड",
|
||||
"balance": "संतुलन",
|
||||
"balance_page": "बैलेंस पेज",
|
||||
"bill_amount": "बिल राशि",
|
||||
"billing_address_info": "यदि बिलिंग पता मांगा जाए, तो अपना शिपिंग पता प्रदान करें",
|
||||
|
@ -78,6 +79,7 @@
|
|||
"bitcoin_dark_theme": "बिटकॉइन डार्क थीम",
|
||||
"bitcoin_light_theme": "बिटकॉइन लाइट थीम",
|
||||
"bitcoin_payments_require_1_confirmation": "बिटकॉइन भुगतान के लिए 1 पुष्टिकरण की आवश्यकता होती है, जिसमें 20 मिनट या अधिक समय लग सकता है। आपके धैर्य के लिए धन्यवाद! भुगतान की पुष्टि होने पर आपको ईमेल किया जाएगा।",
|
||||
"block_remaining": "1 ब्लॉक शेष",
|
||||
"Blocks_remaining": "${status} शेष रहते हैं",
|
||||
"bluetooth": "ब्लूटूथ",
|
||||
"bright_theme": "उज्ज्वल",
|
||||
|
@ -139,6 +141,7 @@
|
|||
"confirm_fee_deduction": "शुल्क कटौती की पुष्टि करें",
|
||||
"confirm_fee_deduction_content": "क्या आप आउटपुट से शुल्क में कटौती करने के लिए सहमत हैं?",
|
||||
"confirm_sending": "भेजने की पुष्टि करें",
|
||||
"confirm_silent_payments_switch_node": "वर्तमान में मूक भुगतान को स्कैन करने के लिए नोड्स को स्विच करना आवश्यक है",
|
||||
"confirmations": "पुष्टिकरण",
|
||||
"confirmed": "पुष्टि की गई शेष राशिी",
|
||||
"confirmed_tx": "की पुष्टि",
|
||||
|
@ -221,6 +224,7 @@
|
|||
"electrum_address_disclaimer": "हर बार जब आप एक का उपयोग करते हैं तो हम नए पते उत्पन्न करते हैं, लेकिन पिछले पते काम करना जारी रखते हैं",
|
||||
"email_address": "ईमेल पता",
|
||||
"enable_replace_by_fee": "प्रतिस्थापित-दर-शुल्क सक्षम करें",
|
||||
"enable_silent_payments_scanning": "मूक भुगतान स्कैनिंग सक्षम करें",
|
||||
"enabled": "सक्रिय",
|
||||
"enter_amount": "राशि दर्ज करें",
|
||||
"enter_backup_password": "यहां बैकअप पासवर्ड डालें",
|
||||
|
@ -278,6 +282,7 @@
|
|||
"extracted_address_content": "आपको धनराशि भेजी जाएगी\n${recipient_name}",
|
||||
"failed_authentication": "प्रमाणीकरण विफल. ${state_error}",
|
||||
"faq": "FAQ",
|
||||
"features": "विशेषताएँ",
|
||||
"fetching": "ला रहा है",
|
||||
"fiat_api": "फिएट पैसे API",
|
||||
"fiat_balance": "फिएट बैलेंस",
|
||||
|
@ -533,6 +538,7 @@
|
|||
"save_backup_password_alert": "बैकअप पासवर्ड सेव करें",
|
||||
"save_to_downloads": "डाउनलोड में सहेजें",
|
||||
"saved_the_trade_id": "मैंने व्यापार बचा लिया है ID",
|
||||
"scan_one_block": "एक ब्लॉक को स्कैन करना",
|
||||
"scan_qr_code": "स्कैन क्यू आर कोड",
|
||||
"scan_qr_code_to_get_address": "पता प्राप्त करने के लिए QR कोड स्कैन करें",
|
||||
"scan_qr_on_device": "इस QR कोड को किसी अन्य डिवाइस पर स्कैन करें",
|
||||
|
@ -643,11 +649,22 @@
|
|||
"sign_up": "साइन अप करें",
|
||||
"signTransaction": "लेन-देन पर हस्ताक्षर करें",
|
||||
"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": "फिसलने लायक",
|
||||
"sort_by": "इसके अनुसार क्रमबद्ध करें",
|
||||
"spend_key_private": "खर्च करना (निजी)",
|
||||
"spend_key_public": "खर्च करना (जनता)",
|
||||
"status": "स्थिति: ",
|
||||
"string_default": "गलती करना",
|
||||
"subaddress_title": "उपखंड सूची",
|
||||
"subaddresses": "उप पते",
|
||||
"submit_request": "एक अनुरोध सबमिट करें",
|
||||
|
@ -672,10 +689,13 @@
|
|||
"sync_status_starting_sync": "सिताज़ा करना",
|
||||
"sync_status_syncronized": "सिंक्रनाइज़",
|
||||
"sync_status_syncronizing": "सिंक्रनाइज़ करने",
|
||||
"sync_status_timed_out": "समय समााप्त",
|
||||
"sync_status_unsupported": "असमर्थित नोड",
|
||||
"syncing_wallet_alert_content": "आपकी शेष राशि और लेनदेन सूची तब तक पूरी नहीं हो सकती जब तक कि शीर्ष पर \"सिंक्रनाइज़्ड\" न लिखा हो। अधिक जानने के लिए क्लिक/टैप करें।",
|
||||
"syncing_wallet_alert_title": "आपका वॉलेट सिंक हो रहा है",
|
||||
"template": "खाका",
|
||||
"template_name": "टेम्पलेट नाम",
|
||||
"testnet_coins_no_value": "टेस्टनेट सिक्कों का कोई मूल्य नहीं है",
|
||||
"third_intro_content": "Yats Cake Wallet के बाहर भी रहता है। धरती पर किसी भी वॉलेट पते को Yat से बदला जा सकता है!",
|
||||
"third_intro_title": "Yat दूसरों के साथ अच्छा खेलता है",
|
||||
"thorchain_contract_address_not_supported": "थोरचेन एक अनुबंध पते पर भेजने का समर्थन नहीं करता है",
|
||||
|
@ -751,13 +771,16 @@
|
|||
"trusted": "भरोसा",
|
||||
"tx_commit_exception_no_dust_on_change": "लेनदेन को इस राशि से खारिज कर दिया जाता है। इन सिक्कों के साथ आप चेंज या ${min} के बिना ${max} को भेज सकते हैं जो परिवर्तन लौटाता है।",
|
||||
"tx_commit_failed": "लेन -देन प्रतिबद्ध विफल। कृपया संपर्क समर्थन करें।",
|
||||
"tx_invalid_input": "आप इस प्रकार के भुगतान के लिए गलत इनपुट प्रकार का उपयोग कर रहे हैं",
|
||||
"tx_no_dust_exception": "लेनदेन को बहुत छोटी राशि भेजकर अस्वीकार कर दिया जाता है। कृपया राशि बढ़ाने का प्रयास करें।",
|
||||
"tx_not_enough_inputs_exception": "पर्याप्त इनपुट उपलब्ध नहीं है। कृपया सिक्का नियंत्रण के तहत अधिक चुनें",
|
||||
"tx_rejected_bip68_final": "लेन -देन में अपुष्ट इनपुट हैं और शुल्क द्वारा प्रतिस्थापित करने में विफल रहे हैं।",
|
||||
"tx_rejected_dust_change": "नेटवर्क नियमों, कम परिवर्तन राशि (धूल) द्वारा खारिज किए गए लेनदेन। सभी भेजने या राशि को कम करने का प्रयास करें।",
|
||||
"tx_rejected_dust_output": "नेटवर्क नियमों, कम आउटपुट राशि (धूल) द्वारा खारिज किए गए लेनदेन। कृपया राशि बढ़ाएं।",
|
||||
"tx_rejected_dust_output_send_all": "नेटवर्क नियमों, कम आउटपुट राशि (धूल) द्वारा खारिज किए गए लेनदेन। कृपया सिक्का नियंत्रण के तहत चुने गए सिक्कों के संतुलन की जाँच करें।",
|
||||
"tx_rejected_vout_negative": "इस लेनदेन की फीस के लिए भुगतान करने के लिए पर्याप्त शेष राशि नहीं है। कृपया सिक्के नियंत्रण के तहत सिक्कों के संतुलन की जाँच करें।",
|
||||
"tx_wrong_balance_exception": "इस राशि को भेजने के लिए आपके पास पर्याप्त ${currency} नहीं है।",
|
||||
"tx_wrong_balance_with_amount_exception": "आपके पास पर्याप्त नहीं है${currency} ${amount} की कुल राशि भेजने के लिए",
|
||||
"tx_zero_fee_exception": "0 शुल्क के साथ लेनदेन नहीं भेज सकते। नवीनतम अनुमानों के लिए दर बढ़ाने या अपने कनेक्शन की जांच करने का प्रयास करें।",
|
||||
"unavailable_balance": "अनुपलब्ध शेष",
|
||||
"unavailable_balance_description": "अनुपलब्ध शेष राशि: इस कुल में वे धनराशि शामिल हैं जो लंबित लेनदेन में बंद हैं और जिन्हें आपने अपनी सिक्का नियंत्रण सेटिंग्स में सक्रिय रूप से जमा कर रखा है। लॉक किए गए शेष उनके संबंधित लेन-देन पूरे होने के बाद उपलब्ध हो जाएंगे, जबकि जमे हुए शेष लेन-देन के लिए अप्राप्य रहेंगे जब तक कि आप उन्हें अनफ्रीज करने का निर्णय नहीं लेते।",
|
||||
|
@ -811,6 +834,7 @@
|
|||
"warning": "चेतावनी",
|
||||
"welcome": "स्वागत हे सेवा मेरे",
|
||||
"welcome_to_cakepay": "केकपे में आपका स्वागत है!",
|
||||
"what_is_silent_payments": "मूक भुगतान क्या है?",
|
||||
"widgets_address": "पता",
|
||||
"widgets_or": "या",
|
||||
"widgets_restore_from_blockheight": "ब्लॉकचेन से पुनर्स्थापित करें",
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue