Mweb enhancements 4 ()

* [skip-ci] show mweb confirmations, show last mweb balance while syncing

* potential send-all fix

* [skip-ci] undo fix that didn't work

* [skip-ci] undo unnecessary changes

* [skip ci] add export mweb logs screen

* [skip ci] cleanup

* confirmation fixes

* catch electrum call errors

* [skip ci] undo some changes

* potential electrum fixes + mweb logs display only last 10000 characters

* Add question mark and link to MWEB card

* updates

* show negative unconfirmed mweb balanaces + other fixes [skip ci]

* error handling

* [skip ci] [wip] check if node supports mweb

* check fee before building tx

* [skip ci] minor

* [skip ci] minor

* mweb node setting [wip] [skip ci]

* prioritize mweb coins when selecting inputs from the pool

* potential connection edgecase fix

* translations + mweb node fixes

* don't use mweb for exchange refund address

* add peg in / out labels and make 6 confs only show up for peg in / out

* bump bitcoin_base version to v9

* [skip ci] fix logs page

* don't fetch txinfo for non-mweb addresses [skip ci]

* fix non-mweb confirmations

* rename always scan to enable mweb

* Update litecoin_wallet_addresses.dart

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

* Update cw_mweb.dart

Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>

* [skip ci] review updates pt.1

* [skip ci] minor code cleanup

* [skip ci] use exception handler

* exception handling [skip ci]

* [skip ci] exception handling

* trigger build

* pegout label fixes

* fix showing change transactions on peg-out

* minor code cleanup and minor peg-out fix

* final balance fixes

* non-mweb confirmations potential fix

* [skip ci] wip

* trigger build

---------

Co-authored-by: tuxpizza <tuxsudo@tux.pizza>
Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
Matthew Fosse 2024-11-06 18:57:36 -08:00 committed by GitHub
parent 109d9b458e
commit c8cfc2cff1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
55 changed files with 821 additions and 207 deletions

View file

@ -124,6 +124,7 @@ class ElectrumClient {
final errorMsg = error.toString(); final errorMsg = error.toString();
print(errorMsg); print(errorMsg);
unterminatedString = ''; unterminatedString = '';
socket = null;
}, },
onDone: () { onDone: () {
print("SOCKET CLOSED!!!!!"); print("SOCKET CLOSED!!!!!");
@ -132,6 +133,7 @@ class ElectrumClient {
if (host == socket?.address.host || socket == null) { if (host == socket?.address.host || socket == null) {
_setConnectionStatus(ConnectionStatus.disconnected); _setConnectionStatus(ConnectionStatus.disconnected);
socket?.destroy(); socket?.destroy();
socket = null;
} }
} catch (e) { } catch (e) {
print("onDone: $e"); print("onDone: $e");

View file

@ -24,9 +24,12 @@ class ElectrumBalance extends Balance {
final decoded = json.decode(jsonSource) as Map; final decoded = json.decode(jsonSource) as Map;
return ElectrumBalance( return ElectrumBalance(
confirmed: decoded['confirmed'] as int? ?? 0, confirmed: decoded['confirmed'] as int? ?? 0,
unconfirmed: decoded['unconfirmed'] as int? ?? 0, unconfirmed: decoded['unconfirmed'] as int? ?? 0,
frozen: decoded['frozen'] as int? ?? 0); frozen: decoded['frozen'] as int? ?? 0,
secondConfirmed: decoded['secondConfirmed'] as int? ?? 0,
secondUnconfirmed: decoded['secondUnconfirmed'] as int? ?? 0,
);
} }
int confirmed; int confirmed;
@ -36,8 +39,7 @@ class ElectrumBalance extends Balance {
int secondUnconfirmed = 0; int secondUnconfirmed = 0;
@override @override
String get formattedAvailableBalance => String get formattedAvailableBalance => bitcoinAmountToString(amount: confirmed - frozen);
bitcoinAmountToString(amount: confirmed - frozen);
@override @override
String get formattedAdditionalBalance => bitcoinAmountToString(amount: unconfirmed); String get formattedAdditionalBalance => bitcoinAmountToString(amount: unconfirmed);

View file

@ -41,6 +41,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
String? to, String? to,
this.unspents, this.unspents,
this.isReceivedSilentPayment = false, this.isReceivedSilentPayment = false,
Map<String, dynamic>? additionalInfo,
}) { }) {
this.id = id; this.id = id;
this.height = height; this.height = height;
@ -54,6 +55,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
this.isReplaced = isReplaced; this.isReplaced = isReplaced;
this.confirmations = confirmations; this.confirmations = confirmations;
this.to = to; this.to = to;
this.additionalInfo = additionalInfo ?? {};
} }
factory ElectrumTransactionInfo.fromElectrumVerbose(Map<String, Object> obj, WalletType type, factory ElectrumTransactionInfo.fromElectrumVerbose(Map<String, Object> obj, WalletType type,
@ -212,6 +214,7 @@ class ElectrumTransactionInfo extends TransactionInfo {
BitcoinSilentPaymentsUnspent.fromJSON(null, unspent as Map<String, dynamic>)) BitcoinSilentPaymentsUnspent.fromJSON(null, unspent as Map<String, dynamic>))
.toList(), .toList(),
isReceivedSilentPayment: data['isReceivedSilentPayment'] as bool? ?? false, isReceivedSilentPayment: data['isReceivedSilentPayment'] as bool? ?? false,
additionalInfo: data['additionalInfo'] as Map<String, dynamic>?,
); );
} }
@ -246,7 +249,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
isReplaced: isReplaced ?? false, isReplaced: isReplaced ?? false,
inputAddresses: inputAddresses, inputAddresses: inputAddresses,
outputAddresses: outputAddresses, outputAddresses: outputAddresses,
confirmations: info.confirmations); confirmations: info.confirmations,
additionalInfo: additionalInfo);
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
@ -265,10 +269,11 @@ class ElectrumTransactionInfo extends TransactionInfo {
m['inputAddresses'] = inputAddresses; m['inputAddresses'] = inputAddresses;
m['outputAddresses'] = outputAddresses; m['outputAddresses'] = outputAddresses;
m['isReceivedSilentPayment'] = isReceivedSilentPayment; m['isReceivedSilentPayment'] = isReceivedSilentPayment;
m['additionalInfo'] = additionalInfo;
return m; return m;
} }
String toString() { String toString() {
return 'ElectrumTransactionInfo(id: $id, height: $height, amount: $amount, fee: $fee, direction: $direction, date: $date, isPending: $isPending, isReplaced: $isReplaced, confirmations: $confirmations, to: $to, unspent: $unspents, inputAddresses: $inputAddresses, outputAddresses: $outputAddresses)'; return 'ElectrumTransactionInfo(id: $id, height: $height, amount: $amount, fee: $fee, direction: $direction, date: $date, isPending: $isPending, isReplaced: $isReplaced, confirmations: $confirmations, to: $to, unspent: $unspents, inputAddresses: $inputAddresses, outputAddresses: $outputAddresses, additionalInfo: $additionalInfo)';
} }
} }

View file

@ -5,6 +5,7 @@ import 'dart:isolate';
import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/bitcoin_wallet.dart'; import 'package:cw_bitcoin/bitcoin_wallet.dart';
import 'package:cw_bitcoin/litecoin_wallet.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:blockchain_utils/blockchain_utils.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
@ -52,10 +53,9 @@ part 'electrum_wallet.g.dart';
class ElectrumWallet = ElectrumWalletBase with _$ElectrumWallet; class ElectrumWallet = ElectrumWalletBase with _$ElectrumWallet;
abstract class ElectrumWalletBase extends WalletBase< abstract class ElectrumWalletBase
ElectrumBalance, extends WalletBase<ElectrumBalance, ElectrumTransactionHistory, ElectrumTransactionInfo>
ElectrumTransactionHistory, with Store, WalletKeysFile {
ElectrumTransactionInfo> with Store, WalletKeysFile {
ElectrumWalletBase({ ElectrumWalletBase({
required String password, required String password,
required WalletInfo walletInfo, required WalletInfo walletInfo,
@ -71,8 +71,8 @@ abstract class ElectrumWalletBase extends WalletBase<
ElectrumBalance? initialBalance, ElectrumBalance? initialBalance,
CryptoCurrency? currency, CryptoCurrency? currency,
this.alwaysScan, this.alwaysScan,
}) : accountHD = getAccountHDWallet( }) : accountHD =
currency, network, seedBytes, xpub, walletInfo.derivationInfo), getAccountHDWallet(currency, network, seedBytes, xpub, walletInfo.derivationInfo),
syncStatus = NotConnectedSyncStatus(), syncStatus = NotConnectedSyncStatus(),
_password = password, _password = password,
_feeRates = <int>[], _feeRates = <int>[],
@ -107,12 +107,8 @@ abstract class ElectrumWalletBase extends WalletBase<
sharedPrefs.complete(SharedPreferences.getInstance()); sharedPrefs.complete(SharedPreferences.getInstance());
} }
static Bip32Slip10Secp256k1 getAccountHDWallet( static Bip32Slip10Secp256k1 getAccountHDWallet(CryptoCurrency? currency, BasedUtxoNetwork network,
CryptoCurrency? currency, Uint8List? seedBytes, String? xpub, DerivationInfo? derivationInfo) {
BasedUtxoNetwork network,
Uint8List? seedBytes,
String? xpub,
DerivationInfo? derivationInfo) {
if (seedBytes == null && xpub == null) { if (seedBytes == null && xpub == null) {
throw Exception( throw Exception(
"To create a Wallet you need either a seed or an xpub. This should not happen"); "To create a Wallet you need either a seed or an xpub. This should not happen");
@ -123,9 +119,8 @@ abstract class ElectrumWalletBase extends WalletBase<
case CryptoCurrency.btc: case CryptoCurrency.btc:
case CryptoCurrency.ltc: case CryptoCurrency.ltc:
case CryptoCurrency.tbtc: case CryptoCurrency.tbtc:
return Bip32Slip10Secp256k1.fromSeed(seedBytes, getKeyNetVersion(network)) return Bip32Slip10Secp256k1.fromSeed(seedBytes, getKeyNetVersion(network)).derivePath(
.derivePath(_hardenedDerivationPath( _hardenedDerivationPath(derivationInfo?.derivationPath ?? electrum_path))
derivationInfo?.derivationPath ?? electrum_path))
as Bip32Slip10Secp256k1; as Bip32Slip10Secp256k1;
case CryptoCurrency.bch: case CryptoCurrency.bch:
return bitcoinCashHDWallet(seedBytes); return bitcoinCashHDWallet(seedBytes);
@ -134,13 +129,11 @@ abstract class ElectrumWalletBase extends WalletBase<
} }
} }
return Bip32Slip10Secp256k1.fromExtendedKey( return Bip32Slip10Secp256k1.fromExtendedKey(xpub!, getKeyNetVersion(network));
xpub!, getKeyNetVersion(network));
} }
static Bip32Slip10Secp256k1 bitcoinCashHDWallet(Uint8List seedBytes) => static Bip32Slip10Secp256k1 bitcoinCashHDWallet(Uint8List seedBytes) =>
Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/44'/145'/0'") Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/44'/145'/0'") as Bip32Slip10Secp256k1;
as Bip32Slip10Secp256k1;
static int estimatedTransactionSize(int inputsCount, int outputsCounts) => static int estimatedTransactionSize(int inputsCount, int outputsCounts) =>
inputsCount * 68 + outputsCounts * 34 + 10; inputsCount * 68 + outputsCounts * 34 + 10;
@ -250,7 +243,7 @@ abstract class ElectrumWalletBase extends WalletBase<
} }
if (tip > walletInfo.restoreHeight) { if (tip > walletInfo.restoreHeight) {
_setListeners(walletInfo.restoreHeight, chainTipParam: _currentChainTip); _setListeners(walletInfo.restoreHeight, chainTipParam: currentChainTip);
} }
} else { } else {
alwaysScan = false; alwaysScan = false;
@ -265,23 +258,23 @@ abstract class ElectrumWalletBase extends WalletBase<
} }
} }
int? _currentChainTip; int? currentChainTip;
Future<int> getCurrentChainTip() async { Future<int> getCurrentChainTip() async {
if ((_currentChainTip ?? 0) > 0) { if ((currentChainTip ?? 0) > 0) {
return _currentChainTip!; return currentChainTip!;
} }
_currentChainTip = await electrumClient.getCurrentBlockChainTip() ?? 0; currentChainTip = await electrumClient.getCurrentBlockChainTip() ?? 0;
return _currentChainTip!; return currentChainTip!;
} }
Future<int> getUpdatedChainTip() async { Future<int> getUpdatedChainTip() async {
final newTip = await electrumClient.getCurrentBlockChainTip(); final newTip = await electrumClient.getCurrentBlockChainTip();
if (newTip != null && newTip > (_currentChainTip ?? 0)) { if (newTip != null && newTip > (currentChainTip ?? 0)) {
_currentChainTip = newTip; currentChainTip = newTip;
} }
return _currentChainTip ?? 0; return currentChainTip ?? 0;
} }
@override @override
@ -357,7 +350,7 @@ abstract class ElectrumWalletBase extends WalletBase<
isSingleScan: doSingleScan ?? false, isSingleScan: doSingleScan ?? false,
)); ));
_receiveStream?.cancel(); await _receiveStream?.cancel();
_receiveStream = receivePort.listen((var message) async { _receiveStream = receivePort.listen((var message) async {
if (message is Map<String, ElectrumTransactionInfo>) { if (message is Map<String, ElectrumTransactionInfo>) {
for (final map in message.entries) { for (final map in message.entries) {
@ -618,7 +611,7 @@ abstract class ElectrumWalletBase extends WalletBase<
bool spendsUnconfirmedTX = false; bool spendsUnconfirmedTX = false;
int leftAmount = credentialsAmount; int leftAmount = credentialsAmount;
final availableInputs = unspentCoins.where((utx) { var availableInputs = unspentCoins.where((utx) {
if (!utx.isSending || utx.isFrozen) { if (!utx.isSending || utx.isFrozen) {
return false; return false;
} }
@ -634,6 +627,9 @@ abstract class ElectrumWalletBase extends WalletBase<
}).toList(); }).toList();
final unconfirmedCoins = availableInputs.where((utx) => utx.confirmations == 0).toList(); final unconfirmedCoins = availableInputs.where((utx) => utx.confirmations == 0).toList();
// sort the unconfirmed coins so that mweb coins are first:
availableInputs.sort((a, b) => a.bitcoinAddressRecord.type == SegwitAddresType.mweb ? -1 : 1);
for (int i = 0; i < availableInputs.length; i++) { for (int i = 0; i < availableInputs.length; i++) {
final utx = availableInputs[i]; final utx = availableInputs[i];
if (!spendsUnconfirmedTX) spendsUnconfirmedTX = utx.confirmations == 0; if (!spendsUnconfirmedTX) spendsUnconfirmedTX = utx.confirmations == 0;
@ -652,9 +648,8 @@ abstract class ElectrumWalletBase extends WalletBase<
ECPrivate? privkey; ECPrivate? privkey;
bool? isSilentPayment = false; bool? isSilentPayment = false;
final hd = utx.bitcoinAddressRecord.isHidden final hd =
? walletAddresses.sideHd utx.bitcoinAddressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd;
: walletAddresses.mainHd;
if (utx.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) { if (utx.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) {
final unspentAddress = utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord; final unspentAddress = utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord;
@ -1233,8 +1228,7 @@ abstract class ElectrumWalletBase extends WalletBase<
} }
} }
void setLedgerConnection(ledger.LedgerConnection connection) => void setLedgerConnection(ledger.LedgerConnection connection) => throw UnimplementedError();
throw UnimplementedError();
Future<BtcTransaction> buildHardwareWalletTransaction({ Future<BtcTransaction> buildHardwareWalletTransaction({
required List<BitcoinBaseOutput> outputs, required List<BitcoinBaseOutput> outputs,
@ -1593,9 +1587,7 @@ abstract class ElectrumWalletBase extends WalletBase<
final btcAddress = RegexUtils.addressTypeFromStr(addressRecord.address, network); final btcAddress = RegexUtils.addressTypeFromStr(addressRecord.address, network);
final privkey = generateECPrivate( final privkey = generateECPrivate(
hd: addressRecord.isHidden hd: addressRecord.isHidden ? walletAddresses.sideHd : walletAddresses.mainHd,
? walletAddresses.sideHd
: walletAddresses.mainHd,
index: addressRecord.index, index: addressRecord.index,
network: network); network: network);
@ -1777,8 +1769,7 @@ abstract class ElectrumWalletBase extends WalletBase<
if (height != null) { if (height != null) {
if (time == null && height > 0) { if (time == null && height > 0) {
time = (getDateByBitcoinHeight(height).millisecondsSinceEpoch / 1000) time = (getDateByBitcoinHeight(height).millisecondsSinceEpoch / 1000).round();
.round();
} }
if (confirmations == null) { if (confirmations == null) {
@ -1847,6 +1838,7 @@ abstract class ElectrumWalletBase extends WalletBase<
.map((type) => fetchTransactionsForAddressType(historiesWithDetails, type))); .map((type) => fetchTransactionsForAddressType(historiesWithDetails, type)));
} else if (type == WalletType.litecoin) { } else if (type == WalletType.litecoin) {
await Future.wait(LITECOIN_ADDRESS_TYPES await Future.wait(LITECOIN_ADDRESS_TYPES
.where((type) => type != SegwitAddresType.mweb)
.map((type) => fetchTransactionsForAddressType(historiesWithDetails, type))); .map((type) => fetchTransactionsForAddressType(historiesWithDetails, type)));
} }
@ -1958,6 +1950,20 @@ abstract class ElectrumWalletBase extends WalletBase<
// Got a new transaction fetched, add it to the transaction history // Got a new transaction fetched, add it to the transaction history
// instead of waiting all to finish, and next time it will be faster // instead of waiting all to finish, and next time it will be faster
if (this is LitecoinWallet) {
// if we have a peg out transaction with the same value
// that matches this received transaction, mark it as being from a peg out:
for (final tx2 in transactionHistory.transactions.values) {
final heightDiff = ((tx2.height ?? 0) - (tx.height ?? 0)).abs();
// this isn't a perfect matching algorithm since we don't have the right input/output information from these transaction models (the addresses are in different formats), but this should be more than good enough for now as it's extremely unlikely a user receives the EXACT same amount from 2 different sources and one of them is a peg out and the other isn't WITHIN 5 blocks of each other
if (tx2.additionalInfo["isPegOut"] == true &&
tx2.amount == tx.amount &&
heightDiff <= 5) {
tx.additionalInfo["fromPegOut"] = true;
}
}
}
transactionHistory.addOne(tx); transactionHistory.addOne(tx);
await transactionHistory.save(); await transactionHistory.save();
} }
@ -1984,18 +1990,28 @@ abstract class ElectrumWalletBase extends WalletBase<
if (_isTransactionUpdating) { if (_isTransactionUpdating) {
return; return;
} }
await getCurrentChainTip(); currentChainTip = await getUpdatedChainTip();
bool updated = false;
transactionHistory.transactions.values.forEach((tx) { transactionHistory.transactions.values.forEach((tx) {
if (tx.unspents != null && if ((tx.height ?? 0) > 0 && (currentChainTip ?? 0) > 0) {
tx.unspents!.isNotEmpty && var confirmations = currentChainTip! - tx.height! + 1;
tx.height != null && if (confirmations < 0) {
tx.height! > 0 && // if our chain tip is outdated then it could lead to negative confirmations so this is just a failsafe:
(_currentChainTip ?? 0) > 0) { confirmations = 0;
tx.confirmations = _currentChainTip! - tx.height! + 1; }
if (confirmations != tx.confirmations) {
updated = true;
tx.confirmations = confirmations;
transactionHistory.addOne(tx);
}
} }
}); });
if (updated) {
await transactionHistory.save();
}
_isTransactionUpdating = true; _isTransactionUpdating = true;
await fetchTransactions(); await fetchTransactions();
walletAddresses.updateReceiveAddresses(); walletAddresses.updateReceiveAddresses();
@ -2043,6 +2059,8 @@ abstract class ElectrumWalletBase extends WalletBase<
library: this.runtimeType.toString(), library: this.runtimeType.toString(),
)); ));
} }
}, onError: (e, s) {
print("sub_listen error: $e $s");
}); });
})); }));
} }
@ -2092,6 +2110,13 @@ abstract class ElectrumWalletBase extends WalletBase<
final balances = await Future.wait(balanceFutures); final balances = await Future.wait(balanceFutures);
if (balances.isNotEmpty && balances.first['confirmed'] == null) {
// if we got null balance responses from the server, set our connection status to lost and return our last known balance:
print("got null balance responses from the server, setting connection status to lost");
syncStatus = LostConnectionSyncStatus();
return balance[currency] ?? ElectrumBalance(confirmed: 0, unconfirmed: 0, frozen: 0);
}
for (var i = 0; i < balances.length; i++) { for (var i = 0; i < balances.length; i++) {
final addressRecord = addresses[i]; final addressRecord = addresses[i];
final balance = balances[i]; final balance = balances[i];
@ -2197,10 +2222,10 @@ abstract class ElectrumWalletBase extends WalletBase<
Future<void> _setInitialHeight() async { Future<void> _setInitialHeight() async {
if (_chainTipUpdateSubject != null) return; if (_chainTipUpdateSubject != null) return;
_currentChainTip = await getUpdatedChainTip(); currentChainTip = await getUpdatedChainTip();
if ((_currentChainTip == null || _currentChainTip! == 0) && walletInfo.restoreHeight == 0) { if ((currentChainTip == null || currentChainTip! == 0) && walletInfo.restoreHeight == 0) {
await walletInfo.updateRestoreHeight(_currentChainTip!); await walletInfo.updateRestoreHeight(currentChainTip!);
} }
_chainTipUpdateSubject = electrumClient.chainTipSubscribe(); _chainTipUpdateSubject = electrumClient.chainTipSubscribe();
@ -2209,7 +2234,7 @@ abstract class ElectrumWalletBase extends WalletBase<
final height = int.tryParse(event['height'].toString()); final height = int.tryParse(event['height'].toString());
if (height != null) { if (height != null) {
_currentChainTip = height; currentChainTip = height;
if (alwaysScan == true && syncStatus is SyncedSyncStatus) { if (alwaysScan == true && syncStatus is SyncedSyncStatus) {
_setListeners(walletInfo.restoreHeight); _setListeners(walletInfo.restoreHeight);
@ -2223,7 +2248,6 @@ abstract class ElectrumWalletBase extends WalletBase<
@action @action
void _onConnectionStatusChange(ConnectionStatus status) { void _onConnectionStatusChange(ConnectionStatus status) {
switch (status) { switch (status) {
case ConnectionStatus.connected: case ConnectionStatus.connected:
if (syncStatus is NotConnectedSyncStatus || if (syncStatus is NotConnectedSyncStatus ||
@ -2270,8 +2294,6 @@ abstract class ElectrumWalletBase extends WalletBase<
Timer(Duration(seconds: 5), () { Timer(Duration(seconds: 5), () {
if (this.syncStatus is NotConnectedSyncStatus || if (this.syncStatus is NotConnectedSyncStatus ||
this.syncStatus is LostConnectionSyncStatus) { this.syncStatus is LostConnectionSyncStatus) {
if (node == null) return;
this.electrumClient.connectToUri( this.electrumClient.connectToUri(
node!.uri, node!.uri,
useSSL: node!.useSSL ?? false, useSSL: node!.useSSL ?? false,

View file

@ -9,6 +9,7 @@ import 'package:crypto/crypto.dart';
import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart'; import 'package:cw_bitcoin/bitcoin_transaction_credentials.dart';
import 'package:cw_core/cake_hive.dart'; import 'package:cw_core/cake_hive.dart';
import 'package:cw_core/mweb_utxo.dart'; import 'package:cw_core/mweb_utxo.dart';
import 'package:cw_core/node.dart';
import 'package:cw_mweb/mwebd.pbgrpc.dart'; import 'package:cw_mweb/mwebd.pbgrpc.dart';
import 'package:fixnum/fixnum.dart'; import 'package:fixnum/fixnum.dart';
import 'package:bip39/bip39.dart' as bip39; import 'package:bip39/bip39.dart' as bip39;
@ -47,6 +48,7 @@ import 'package:cw_mweb/cw_mweb.dart';
import 'package:bitcoin_base/src/crypto/keypair/sign_utils.dart'; import 'package:bitcoin_base/src/crypto/keypair/sign_utils.dart';
import 'package:pointycastle/ecc/api.dart'; import 'package:pointycastle/ecc/api.dart';
import 'package:pointycastle/ecc/curves/secp256k1.dart'; import 'package:pointycastle/ecc/curves/secp256k1.dart';
import 'package:shared_preferences/shared_preferences.dart';
part 'litecoin_wallet.g.dart'; part 'litecoin_wallet.g.dart';
@ -85,8 +87,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
alwaysScan: alwaysScan, alwaysScan: alwaysScan,
) { ) {
if (seedBytes != null) { if (seedBytes != null) {
mwebHd = Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath( mwebHd =
"m/1000'") as Bip32Slip10Secp256k1; Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/1000'") as Bip32Slip10Secp256k1;
mwebEnabled = alwaysScan ?? false; mwebEnabled = alwaysScan ?? false;
} else { } else {
mwebHd = null; mwebHd = null;
@ -287,6 +289,16 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
await (walletAddresses as LitecoinWalletAddresses).ensureMwebAddressUpToIndexExists(1020); await (walletAddresses as LitecoinWalletAddresses).ensureMwebAddressUpToIndexExists(1020);
} }
@action
@override
Future<void> connectToNode({required Node node}) async {
await super.connectToNode(node: node);
final prefs = await SharedPreferences.getInstance();
final mwebNodeUri = prefs.getString("mwebNodeUri") ?? "ltc-electrum.cakewallet.com:9333";
await CwMweb.setNodeUriOverride(mwebNodeUri);
}
@action @action
@override @override
Future<void> startSync() async { Future<void> startSync() async {
@ -349,6 +361,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
return; return;
} }
// update the current chain tip so that confirmation calculations are accurate:
currentChainTip = nodeHeight;
final resp = await CwMweb.status(StatusRequest()); final resp = await CwMweb.status(StatusRequest());
try { try {
@ -361,22 +376,46 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
} else if (resp.mwebUtxosHeight < nodeHeight) { } else if (resp.mwebUtxosHeight < nodeHeight) {
mwebSyncStatus = SyncingSyncStatus(1, 0.999); mwebSyncStatus = SyncingSyncStatus(1, 0.999);
} else { } else {
bool confirmationsUpdated = false;
if (resp.mwebUtxosHeight > walletInfo.restoreHeight) { if (resp.mwebUtxosHeight > walletInfo.restoreHeight) {
await walletInfo.updateRestoreHeight(resp.mwebUtxosHeight); await walletInfo.updateRestoreHeight(resp.mwebUtxosHeight);
await checkMwebUtxosSpent(); await checkMwebUtxosSpent();
// update the confirmations for each transaction: // update the confirmations for each transaction:
for (final transaction in transactionHistory.transactions.values) { for (final tx in transactionHistory.transactions.values) {
if (transaction.isPending) continue; if (tx.height == null || tx.height == 0) {
int txHeight = transaction.height ?? resp.mwebUtxosHeight; // update with first confirmation on next block since it hasn't been confirmed yet:
final confirmations = (resp.mwebUtxosHeight - txHeight) + 1; tx.height = resp.mwebUtxosHeight;
if (transaction.confirmations == confirmations) continue; continue;
if (transaction.confirmations == 0) {
updateBalance();
} }
transaction.confirmations = confirmations;
transactionHistory.addOne(transaction); final confirmations = (resp.mwebUtxosHeight - tx.height!) + 1;
// if the confirmations haven't changed, skip updating:
if (tx.confirmations == confirmations) continue;
// if an outgoing tx is now confirmed, delete the utxo from the box (delete the unspent coin):
if (confirmations >= 2 &&
tx.direction == TransactionDirection.outgoing &&
tx.unspents != null) {
for (var coin in tx.unspents!) {
final utxo = mwebUtxosBox.get(coin.address);
if (utxo != null) {
print("deleting utxo ${coin.address} @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
await mwebUtxosBox.delete(coin.address);
}
}
}
tx.confirmations = confirmations;
tx.isPending = false;
transactionHistory.addOne(tx);
confirmationsUpdated = true;
}
if (confirmationsUpdated) {
await transactionHistory.save();
await updateTransactions();
} }
await transactionHistory.save();
} }
// prevent unnecessary reaction triggers: // prevent unnecessary reaction triggers:
@ -501,13 +540,12 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
outputAddresses: [utxo.outputId], outputAddresses: [utxo.outputId],
isReplaced: false, isReplaced: false,
); );
} } else {
if (tx.confirmations != confirmations || tx.height != utxo.height) {
// don't update the confirmations if the tx is updated by electrum: tx.height = utxo.height;
if (tx.confirmations == 0 || utxo.height != 0) { tx.confirmations = confirmations;
tx.height = utxo.height; tx.isPending = utxo.height == 0;
tx.isPending = utxo.height == 0; }
tx.confirmations = confirmations;
} }
bool isNew = transactionHistory.transactions[tx.id] == null; bool isNew = transactionHistory.transactions[tx.id] == null;
@ -557,56 +595,88 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
if (responseStream == null) { if (responseStream == null) {
throw Exception("failed to get utxos stream!"); throw Exception("failed to get utxos stream!");
} }
_utxoStream = responseStream.listen((Utxo sUtxo) async { _utxoStream = responseStream.listen(
// we're processing utxos, so our balance could still be innacurate: (Utxo sUtxo) async {
if (mwebSyncStatus is! SyncronizingSyncStatus && mwebSyncStatus is! SyncingSyncStatus) { // we're processing utxos, so our balance could still be innacurate:
mwebSyncStatus = SyncronizingSyncStatus(); if (mwebSyncStatus is! SyncronizingSyncStatus && mwebSyncStatus is! SyncingSyncStatus) {
processingUtxos = true; mwebSyncStatus = SyncronizingSyncStatus();
_processingTimer?.cancel(); processingUtxos = true;
_processingTimer = Timer.periodic(const Duration(seconds: 2), (timer) async { _processingTimer?.cancel();
processingUtxos = false; _processingTimer = Timer.periodic(const Duration(seconds: 2), (timer) async {
timer.cancel(); processingUtxos = false;
}); timer.cancel();
} });
final utxo = MwebUtxo(
address: sUtxo.address,
blockTime: sUtxo.blockTime,
height: sUtxo.height,
outputId: sUtxo.outputId,
value: sUtxo.value.toInt(),
);
if (mwebUtxosBox.containsKey(utxo.outputId)) {
// we've already stored this utxo, skip it:
// but do update the utxo height if it's somehow different:
final existingUtxo = mwebUtxosBox.get(utxo.outputId);
if (existingUtxo!.height != utxo.height) {
print(
"updating utxo height for $utxo.outputId: ${existingUtxo.height} -> ${utxo.height}");
existingUtxo.height = utxo.height;
await mwebUtxosBox.put(utxo.outputId, existingUtxo);
} }
return;
}
await updateUnspent(); final utxo = MwebUtxo(
await updateBalance(); address: sUtxo.address,
blockTime: sUtxo.blockTime,
height: sUtxo.height,
outputId: sUtxo.outputId,
value: sUtxo.value.toInt(),
);
final mwebAddrs = (walletAddresses as LitecoinWalletAddresses).mwebAddrs; if (mwebUtxosBox.containsKey(utxo.outputId)) {
// we've already stored this utxo, skip it:
// but do update the utxo height if it's somehow different:
final existingUtxo = mwebUtxosBox.get(utxo.outputId);
if (existingUtxo!.height != utxo.height) {
print(
"updating utxo height for $utxo.outputId: ${existingUtxo.height} -> ${utxo.height}");
existingUtxo.height = utxo.height;
await mwebUtxosBox.put(utxo.outputId, existingUtxo);
}
return;
}
// don't process utxos with addresses that are not in the mwebAddrs list: await updateUnspent();
if (utxo.address.isNotEmpty && !mwebAddrs.contains(utxo.address)) { await updateBalance();
return;
}
await mwebUtxosBox.put(utxo.outputId, utxo); final mwebAddrs = (walletAddresses as LitecoinWalletAddresses).mwebAddrs;
await handleIncoming(utxo); // don't process utxos with addresses that are not in the mwebAddrs list:
}); if (utxo.address.isNotEmpty && !mwebAddrs.contains(utxo.address)) {
return;
}
await mwebUtxosBox.put(utxo.outputId, utxo);
await handleIncoming(utxo);
},
onError: (error) {
print("error in utxo stream: $error");
mwebSyncStatus = FailedSyncStatus(error: error.toString());
},
cancelOnError: true,
);
}
Future<void> deleteSpentUtxos() async {
print("deleteSpentUtxos() called!");
final chainHeight = await electrumClient.getCurrentBlockChainTip();
final status = await CwMweb.status(StatusRequest());
if (chainHeight == null || status.blockHeaderHeight != chainHeight) return;
if (status.mwebUtxosHeight != chainHeight) return; // we aren't synced
// delete any spent utxos with >= 2 confirmations:
final spentOutputIds = mwebUtxosBox.values
.where((utxo) => utxo.spent && (chainHeight - utxo.height) >= 2)
.map((utxo) => utxo.outputId)
.toList();
if (spentOutputIds.isEmpty) return;
final resp = await CwMweb.spent(SpentRequest(outputId: spentOutputIds));
final spent = resp.outputId;
if (spent.isEmpty) return;
for (final outputId in spent) {
await mwebUtxosBox.delete(outputId);
}
} }
Future<void> checkMwebUtxosSpent() async { Future<void> checkMwebUtxosSpent() async {
print("checkMwebUtxosSpent() called!");
if (!mwebEnabled) { if (!mwebEnabled) {
return; return;
} }
@ -620,15 +690,17 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
updatedAny = await isConfirmed(tx) || updatedAny; updatedAny = await isConfirmed(tx) || updatedAny;
} }
await deleteSpentUtxos();
// get output ids of all the mweb utxos that have > 0 height: // get output ids of all the mweb utxos that have > 0 height:
final outputIds = final outputIds = mwebUtxosBox.values
mwebUtxosBox.values.where((utxo) => utxo.height > 0).map((utxo) => utxo.outputId).toList(); .where((utxo) => utxo.height > 0 && !utxo.spent)
.map((utxo) => utxo.outputId)
.toList();
final resp = await CwMweb.spent(SpentRequest(outputId: outputIds)); final resp = await CwMweb.spent(SpentRequest(outputId: outputIds));
final spent = resp.outputId; final spent = resp.outputId;
if (spent.isEmpty) { if (spent.isEmpty) return;
return;
}
final status = await CwMweb.status(StatusRequest()); final status = await CwMweb.status(StatusRequest());
final height = await electrumClient.getCurrentBlockChainTip(); final height = await electrumClient.getCurrentBlockChainTip();
@ -739,7 +811,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
mwebUtxosBox.keys.forEach((dynamic oId) { mwebUtxosBox.keys.forEach((dynamic oId) {
final String outputId = oId as String; final String outputId = oId as String;
final utxo = mwebUtxosBox.get(outputId); final utxo = mwebUtxosBox.get(outputId);
if (utxo == null) { if (utxo == null || utxo.spent) {
return; return;
} }
if (utxo.address.isEmpty) { if (utxo.address.isEmpty) {
@ -789,15 +861,23 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
int unconfirmedMweb = 0; int unconfirmedMweb = 0;
try { try {
mwebUtxosBox.values.forEach((utxo) { mwebUtxosBox.values.forEach((utxo) {
if (utxo.height > 0) { bool isConfirmed = utxo.height > 0;
print(
"utxo: ${isConfirmed ? "confirmed" : "unconfirmed"} ${utxo.spent ? "spent" : "unspent"} ${utxo.outputId} ${utxo.height} ${utxo.value}");
if (isConfirmed) {
confirmedMweb += utxo.value.toInt(); confirmedMweb += utxo.value.toInt();
} else { }
if (isConfirmed && utxo.spent) {
unconfirmedMweb -= utxo.value.toInt();
}
if (!isConfirmed && !utxo.spent) {
unconfirmedMweb += utxo.value.toInt(); unconfirmedMweb += utxo.value.toInt();
} }
}); });
if (unconfirmedMweb > 0) {
unconfirmedMweb = -1 * (confirmedMweb - unconfirmedMweb);
}
} catch (_) {} } catch (_) {}
for (var addressRecord in walletAddresses.allAddresses) { for (var addressRecord in walletAddresses.allAddresses) {
@ -829,7 +909,6 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
// update the txCount for each address using the tx history, since we can't rely on mwebd // update the txCount for each address using the tx history, since we can't rely on mwebd
// to have an accurate count, we should just keep it in sync with what we know from the tx history: // to have an accurate count, we should just keep it in sync with what we know from the tx history:
for (final tx in transactionHistory.transactions.values) { for (final tx in transactionHistory.transactions.values) {
// if (tx.isPending) continue;
if (tx.inputAddresses == null || tx.outputAddresses == null) { if (tx.inputAddresses == null || tx.outputAddresses == null) {
continue; continue;
} }
@ -908,7 +987,26 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
// https://github.com/ltcmweb/mwebd?tab=readme-ov-file#fee-estimation // https://github.com/ltcmweb/mwebd?tab=readme-ov-file#fee-estimation
final preOutputSum = final preOutputSum =
outputs.fold<BigInt>(BigInt.zero, (acc, output) => acc + output.toOutput.amount); outputs.fold<BigInt>(BigInt.zero, (acc, output) => acc + output.toOutput.amount);
final fee = utxos.sumOfUtxosValue() - preOutputSum; var fee = utxos.sumOfUtxosValue() - preOutputSum;
// determines if the fee is correct:
BigInt _sumOutputAmounts(List<TxOutput> outputs) {
BigInt sum = BigInt.zero;
for (final e in outputs) {
sum += e.amount;
}
return sum;
}
final sum1 = _sumOutputAmounts(outputs.map((e) => e.toOutput).toList()) + fee;
final sum2 = utxos.sumOfUtxosValue();
if (sum1 != sum2) {
print("@@@@@ WE HAD TO ADJUST THE FEE! @@@@@@@@");
final diff = sum2 - sum1;
// add the difference to the fee (abs value):
fee += diff.abs();
}
final txb = final txb =
BitcoinTransactionBuilder(utxos: utxos, outputs: outputs, fee: fee, network: network); BitcoinTransactionBuilder(utxos: utxos, outputs: outputs, fee: fee, network: network);
final resp = await CwMweb.create(CreateRequest( final resp = await CwMweb.create(CreateRequest(
@ -949,8 +1047,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
if (!mwebEnabled) { if (!mwebEnabled) {
tx.changeAddressOverride = tx.changeAddressOverride =
(await (walletAddresses as LitecoinWalletAddresses) (await (walletAddresses as LitecoinWalletAddresses).getChangeAddress(isPegIn: false))
.getChangeAddress(isPegIn: false))
.address; .address;
return tx; return tx;
} }
@ -969,15 +1066,25 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
bool hasMwebInput = false; bool hasMwebInput = false;
bool hasMwebOutput = false; bool hasMwebOutput = false;
bool hasRegularOutput = false;
for (final output in transactionCredentials.outputs) { for (final output in transactionCredentials.outputs) {
if (output.extractedAddress?.toLowerCase().contains("mweb") ?? false) { final address = output.address.toLowerCase();
final extractedAddress = output.extractedAddress?.toLowerCase();
if (address.contains("mweb")) {
hasMwebOutput = true; hasMwebOutput = true;
break;
} }
if (output.address.toLowerCase().contains("mweb")) { if (!address.contains("mweb")) {
hasMwebOutput = true; hasRegularOutput = true;
break; }
if (extractedAddress != null && extractedAddress.isNotEmpty) {
if (extractedAddress.contains("mweb")) {
hasMwebOutput = true;
}
if (!extractedAddress.contains("mweb")) {
hasRegularOutput = true;
}
} }
} }
@ -989,11 +1096,11 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
} }
bool isPegIn = !hasMwebInput && hasMwebOutput; bool isPegIn = !hasMwebInput && hasMwebOutput;
bool isPegOut = hasMwebInput && hasRegularOutput;
bool isRegular = !hasMwebInput && !hasMwebOutput; bool isRegular = !hasMwebInput && !hasMwebOutput;
tx.changeAddressOverride = tx.changeAddressOverride = (await (walletAddresses as LitecoinWalletAddresses)
(await (walletAddresses as LitecoinWalletAddresses) .getChangeAddress(isPegIn: isPegIn || isRegular))
.getChangeAddress(isPegIn: isPegIn || isRegular)) .address;
.address;
if (!hasMwebInput && !hasMwebOutput) { if (!hasMwebInput && !hasMwebOutput) {
tx.isMweb = false; tx.isMweb = false;
return tx; return tx;
@ -1046,8 +1153,11 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
final addresses = <String>{}; final addresses = <String>{};
transaction.inputAddresses?.forEach((id) async { transaction.inputAddresses?.forEach((id) async {
final utxo = mwebUtxosBox.get(id); final utxo = mwebUtxosBox.get(id);
await mwebUtxosBox.delete(id); // gets deleted in checkMwebUtxosSpent // await mwebUtxosBox.delete(id); // gets deleted in checkMwebUtxosSpent
if (utxo == null) return; if (utxo == null) return;
// mark utxo as spent so we add it to the unconfirmed balance (as negative):
utxo.spent = true;
await mwebUtxosBox.put(id, utxo);
final addressRecord = walletAddresses.allAddresses final addressRecord = walletAddresses.allAddresses
.firstWhere((addressRecord) => addressRecord.address == utxo.address); .firstWhere((addressRecord) => addressRecord.address == utxo.address);
if (!addresses.contains(utxo.address)) { if (!addresses.contains(utxo.address)) {
@ -1056,7 +1166,9 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
addressRecord.balance -= utxo.value.toInt(); addressRecord.balance -= utxo.value.toInt();
}); });
transaction.inputAddresses?.addAll(addresses); transaction.inputAddresses?.addAll(addresses);
print("isPegIn: $isPegIn, isPegOut: $isPegOut");
transaction.additionalInfo["isPegIn"] = isPegIn;
transaction.additionalInfo["isPegOut"] = isPegOut;
transactionHistory.addOne(transaction); transactionHistory.addOne(transaction);
await updateUnspent(); await updateUnspent();
await updateBalance(); await updateBalance();
@ -1240,8 +1352,8 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
@override @override
void setLedgerConnection(LedgerConnection connection) { void setLedgerConnection(LedgerConnection connection) {
_ledgerConnection = connection; _ledgerConnection = connection;
_litecoinLedgerApp = _litecoinLedgerApp = LitecoinLedgerApp(_ledgerConnection!,
LitecoinLedgerApp(_ledgerConnection!, derivationPath: walletInfo.derivationInfo!.derivationPath!); derivationPath: walletInfo.derivationInfo!.derivationPath!);
} }
@override @override
@ -1277,19 +1389,17 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
if (maybeChangePath != null) changePath ??= maybeChangePath.derivationPath; if (maybeChangePath != null) changePath ??= maybeChangePath.derivationPath;
} }
final rawHex = await _litecoinLedgerApp!.createTransaction( final rawHex = await _litecoinLedgerApp!.createTransaction(
inputs: readyInputs, inputs: readyInputs,
outputs: outputs outputs: outputs
.map((e) => TransactionOutput.fromBigInt( .map((e) => TransactionOutput.fromBigInt((e as BitcoinOutput).value,
(e as BitcoinOutput).value, Uint8List.fromList(e.address.toScriptPubKey().toBytes()))) Uint8List.fromList(e.address.toScriptPubKey().toBytes())))
.toList(), .toList(),
changePath: changePath, changePath: changePath,
sigHashType: 0x01, sigHashType: 0x01,
additionals: ["bech32"], additionals: ["bech32"],
isSegWit: true, isSegWit: true,
useTrustedInputForSegwit: true useTrustedInputForSegwit: true);
);
return BtcTransaction.fromRaw(rawHex); return BtcTransaction.fromRaw(rawHex);
} }

View file

@ -16,11 +16,9 @@ import 'package:mobx/mobx.dart';
part 'litecoin_wallet_addresses.g.dart'; part 'litecoin_wallet_addresses.g.dart';
class LitecoinWalletAddresses = LitecoinWalletAddressesBase class LitecoinWalletAddresses = LitecoinWalletAddressesBase with _$LitecoinWalletAddresses;
with _$LitecoinWalletAddresses;
abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses with Store {
with Store {
LitecoinWalletAddressesBase( LitecoinWalletAddressesBase(
WalletInfo walletInfo, { WalletInfo walletInfo, {
required super.mainHd, required super.mainHd,
@ -46,8 +44,7 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses
List<String> mwebAddrs = []; List<String> mwebAddrs = [];
bool generating = false; bool generating = false;
List<int> get scanSecret => List<int> get scanSecret => mwebHd!.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw;
mwebHd!.childKey(Bip32KeyIndex(0x80000000)).privateKey.privKey.raw;
List<int> get spendPubkey => List<int> get spendPubkey =>
mwebHd!.childKey(Bip32KeyIndex(0x80000001)).publicKey.pubKey.compressed; mwebHd!.childKey(Bip32KeyIndex(0x80000001)).publicKey.pubKey.compressed;
@ -203,4 +200,12 @@ abstract class LitecoinWalletAddressesBase extends ElectrumWalletAddresses
return super.getChangeAddress(); return super.getChangeAddress();
} }
@override
String get addressForExchange {
// don't use mweb addresses for exchange refund address:
final addresses = receiveAddresses
.where((element) => element.type == SegwitAddresType.p2wpkh && !element.isUsed);
return addresses.first.address;
}
} }

View file

@ -112,6 +112,7 @@ class LitecoinWalletService extends WalletService<
File neturinoDb = File('$appDirPath/neutrino.db'); File neturinoDb = File('$appDirPath/neutrino.db');
File blockHeaders = File('$appDirPath/block_headers.bin'); File blockHeaders = File('$appDirPath/block_headers.bin');
File regFilterHeaders = File('$appDirPath/reg_filter_headers.bin'); File regFilterHeaders = File('$appDirPath/reg_filter_headers.bin');
File mwebdLogs = File('$appDirPath/logs/debug.log');
if (neturinoDb.existsSync()) { if (neturinoDb.existsSync()) {
neturinoDb.deleteSync(); neturinoDb.deleteSync();
} }
@ -121,6 +122,9 @@ class LitecoinWalletService extends WalletService<
if (regFilterHeaders.existsSync()) { if (regFilterHeaders.existsSync()) {
regFilterHeaders.deleteSync(); regFilterHeaders.deleteSync();
} }
if (mwebdLogs.existsSync()) {
mwebdLogs.deleteSync();
}
} }
} }

View file

@ -118,8 +118,7 @@ class PendingBitcoinTransaction with PendingTransaction {
Future<void> _ltcCommit() async { Future<void> _ltcCommit() async {
try { try {
final stub = await CwMweb.stub(); final resp = await CwMweb.broadcast(BroadcastRequest(rawTx: BytesUtils.fromHexString(hex)));
final resp = await stub.broadcast(BroadcastRequest(rawTx: BytesUtils.fromHexString(hex)));
idOverride = resp.txid; idOverride = resp.txid;
} on GrpcError catch (e) { } on GrpcError catch (e) {
throw BitcoinTransactionCommitFailed(errorMessage: e.message); throw BitcoinTransactionCommitFailed(errorMessage: e.message);

View file

@ -64,7 +64,7 @@ dependency_overrides:
bitcoin_base: bitcoin_base:
git: git:
url: https://github.com/cake-tech/bitcoin_base url: https://github.com/cake-tech/bitcoin_base
ref: cake-update-v8 ref: cake-update-v9
pointycastle: 3.7.4 pointycastle: 3.7.4
ffi: 2.1.0 ffi: 2.1.0

View file

@ -42,7 +42,7 @@ dependency_overrides:
bitcoin_base: bitcoin_base:
git: git:
url: https://github.com/cake-tech/bitcoin_base url: https://github.com/cake-tech/bitcoin_base
ref: cake-update-v8 ref: cake-update-v9
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec

View file

@ -11,6 +11,7 @@ class MwebUtxo extends HiveObject {
required this.address, required this.address,
required this.outputId, required this.outputId,
required this.blockTime, required this.blockTime,
this.spent = false,
}); });
static const typeId = MWEB_UTXO_TYPE_ID; static const typeId = MWEB_UTXO_TYPE_ID;
@ -30,4 +31,7 @@ class MwebUtxo extends HiveObject {
@HiveField(4) @HiveField(4)
int blockTime; int blockTime;
@HiveField(5, defaultValue: false)
bool spent;
} }

View file

@ -79,6 +79,9 @@ class Node extends HiveObject with Keyable {
@HiveField(9) @HiveField(9)
bool? supportsSilentPayments; bool? supportsSilentPayments;
@HiveField(10)
bool? supportsMweb;
bool get isSSL => useSSL ?? false; bool get isSSL => useSSL ?? false;
bool get useSocksProxy => socksProxyAddress == null ? false : socksProxyAddress!.isNotEmpty; bool get useSocksProxy => socksProxyAddress == null ? false : socksProxyAddress!.isNotEmpty;

View file

@ -25,6 +25,5 @@ abstract class TransactionInfo extends Object with Keyable {
@override @override
dynamic get keyIndex => id; dynamic get keyIndex => id;
late Map<String, dynamic> additionalInfo; Map<String, dynamic> additionalInfo = {};
} }

View file

@ -13,8 +13,18 @@ class CwMweb {
static RpcClient? _rpcClient; static RpcClient? _rpcClient;
static ClientChannel? _clientChannel; static ClientChannel? _clientChannel;
static int? _port; static int? _port;
static const TIMEOUT_DURATION = Duration(seconds: 5); static const TIMEOUT_DURATION = Duration(seconds: 15);
static Timer? logTimer; static Timer? logTimer;
static String? nodeUriOverride;
static Future<void> setNodeUriOverride(String uri) async {
nodeUriOverride = uri;
if (_rpcClient != null) {
await stop();
// will be re-started automatically when the next rpc call is made
}
}
static void readFileWithTimer(String filePath) { static void readFileWithTimer(String filePath) {
final file = File(filePath); final file = File(filePath);
@ -47,7 +57,7 @@ class CwMweb {
String debugLogPath = "${appDir.path}/logs/debug.log"; String debugLogPath = "${appDir.path}/logs/debug.log";
readFileWithTimer(debugLogPath); readFileWithTimer(debugLogPath);
_port = await CwMwebPlatform.instance.start(appDir.path, ltcNodeUri); _port = await CwMwebPlatform.instance.start(appDir.path, nodeUriOverride ?? ltcNodeUri);
if (_port == null || _port == 0) { if (_port == null || _port == 0) {
throw Exception("Failed to start server"); throw Exception("Failed to start server");
} }
@ -197,4 +207,18 @@ class CwMweb {
} }
return null; return null;
} }
static Future<BroadcastResponse> broadcast(BroadcastRequest request) async {
log("mweb.broadcast() called");
try {
_rpcClient = await stub();
return await _rpcClient!.broadcast(request, options: CallOptions(timeout: TIMEOUT_DURATION));
} on GrpcError catch (e) {
log('Caught grpc error: ${e.message}');
throw "error from broadcast mweb: $e";
} catch (e) {
log("Error getting create: $e");
rethrow;
}
}
} }

View file

@ -35,6 +35,8 @@ import 'package:cake_wallet/entities/parse_address_from_domain.dart';
import 'package:cake_wallet/entities/wallet_edit_page_arguments.dart'; import 'package:cake_wallet/entities/wallet_edit_page_arguments.dart';
import 'package:cake_wallet/entities/wallet_manager.dart'; import 'package:cake_wallet/entities/wallet_manager.dart';
import 'package:cake_wallet/src/screens/receive/address_list_page.dart'; import 'package:cake_wallet/src/screens/receive/address_list_page.dart';
import 'package:cake_wallet/src/screens/settings/mweb_logs_page.dart';
import 'package:cake_wallet/src/screens/settings/mweb_node_page.dart';
import 'package:cake_wallet/view_model/link_view_model.dart'; import 'package:cake_wallet/view_model/link_view_model.dart';
import 'package:cake_wallet/tron/tron.dart'; import 'package:cake_wallet/tron/tron.dart';
import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart'; import 'package:cake_wallet/src/screens/transaction_details/rbf_details_page.dart';
@ -945,6 +947,10 @@ Future<void> setup({
getIt.registerFactory(() => MwebSettingsPage(getIt.get<MwebSettingsViewModel>())); getIt.registerFactory(() => MwebSettingsPage(getIt.get<MwebSettingsViewModel>()));
getIt.registerFactory(() => MwebLogsPage(getIt.get<MwebSettingsViewModel>()));
getIt.registerFactory(() => MwebNodePage(getIt.get<MwebSettingsViewModel>()));
getIt.registerFactory(() => OtherSettingsPage(getIt.get<OtherSettingsViewModel>())); getIt.registerFactory(() => OtherSettingsPage(getIt.get<OtherSettingsViewModel>()));
getIt.registerFactory(() => NanoChangeRepPage( getIt.registerFactory(() => NanoChangeRepPage(

View file

@ -54,6 +54,7 @@ class PreferencesKey {
static const mwebEnabled = 'mwebEnabled'; static const mwebEnabled = 'mwebEnabled';
static const hasEnabledMwebBefore = 'hasEnabledMwebBefore'; static const hasEnabledMwebBefore = 'hasEnabledMwebBefore';
static const mwebAlwaysScan = 'mwebAlwaysScan'; static const mwebAlwaysScan = 'mwebAlwaysScan';
static const mwebNodeUri = 'mwebNodeUri';
static const shouldShowReceiveWarning = 'should_show_receive_warning'; static const shouldShowReceiveWarning = 'should_show_receive_warning';
static const shouldShowYatPopup = 'should_show_yat_popup'; static const shouldShowYatPopup = 'should_show_yat_popup';
static const shouldShowRepWarning = 'should_show_rep_warning'; static const shouldShowRepWarning = 'should_show_rep_warning';

View file

@ -72,6 +72,8 @@ import 'package:cake_wallet/src/screens/settings/desktop_settings/desktop_settin
import 'package:cake_wallet/src/screens/settings/display_settings_page.dart'; import 'package:cake_wallet/src/screens/settings/display_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/domain_lookups_page.dart'; import 'package:cake_wallet/src/screens/settings/domain_lookups_page.dart';
import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart'; import 'package:cake_wallet/src/screens/settings/manage_nodes_page.dart';
import 'package:cake_wallet/src/screens/settings/mweb_logs_page.dart';
import 'package:cake_wallet/src/screens/settings/mweb_node_page.dart';
import 'package:cake_wallet/src/screens/settings/mweb_settings.dart'; import 'package:cake_wallet/src/screens/settings/mweb_settings.dart';
import 'package:cake_wallet/src/screens/settings/other_settings_page.dart'; import 'package:cake_wallet/src/screens/settings/other_settings_page.dart';
import 'package:cake_wallet/src/screens/settings/privacy_page.dart'; import 'package:cake_wallet/src/screens/settings/privacy_page.dart';
@ -461,6 +463,14 @@ Route<dynamic> createRoute(RouteSettings settings) {
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
fullscreenDialog: true, builder: (_) => getIt.get<MwebSettingsPage>()); fullscreenDialog: true, builder: (_) => getIt.get<MwebSettingsPage>());
case Routes.mwebLogs:
return CupertinoPageRoute<void>(
fullscreenDialog: true, builder: (_) => getIt.get<MwebLogsPage>());
case Routes.mwebNode:
return CupertinoPageRoute<void>(
fullscreenDialog: true, builder: (_) => getIt.get<MwebNodePage>());
case Routes.connectionSync: case Routes.connectionSync:
return CupertinoPageRoute<void>( return CupertinoPageRoute<void>(
fullscreenDialog: true, builder: (_) => getIt.get<ConnectionSyncPage>()); fullscreenDialog: true, builder: (_) => getIt.get<ConnectionSyncPage>());

View file

@ -74,6 +74,8 @@ class Routes {
static const webViewPage = '/web_view_page'; static const webViewPage = '/web_view_page';
static const silentPaymentsSettings = '/silent_payments_settings'; static const silentPaymentsSettings = '/silent_payments_settings';
static const mwebSettings = '/mweb_settings'; static const mwebSettings = '/mweb_settings';
static const mwebLogs = '/mweb_logs';
static const mwebNode = '/mweb_node';
static const connectionSync = '/connection_sync_page'; static const connectionSync = '/connection_sync_page';
static const securityBackupPage = '/security_and_backup_page'; static const securityBackupPage = '/security_and_backup_page';
static const privacyPage = '/privacy_page'; static const privacyPage = '/privacy_page';

View file

@ -886,17 +886,37 @@ class BalanceRowWidget extends StatelessWidget {
Column( Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( GestureDetector(
'${secondAvailableBalanceLabel}', behavior: HitTestBehavior.opaque,
textAlign: TextAlign.center, onTap: () => launchUrl(
style: TextStyle( Uri.parse(
fontSize: 12, "https://guides.cakewallet.com/docs/cryptos/litecoin/#mweb"),
fontFamily: 'Lato', mode: LaunchMode.externalApplication,
fontWeight: FontWeight.w400, ),
color: Theme.of(context) child: Row(
.extension<BalancePageTheme>()! children: [
.labelTextColor, Text(
height: 1, '${secondAvailableBalanceLabel}',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontFamily: 'Lato',
fontWeight: FontWeight.w400,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.labelTextColor,
height: 1,
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: Icon(Icons.help_outline,
size: 16,
color: Theme.of(context)
.extension<BalancePageTheme>()!
.labelTextColor),
)
],
), ),
), ),
SizedBox(height: 8), SizedBox(height: 8),

View file

@ -0,0 +1,127 @@
import 'dart:io';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/utils/exception_handler.dart';
import 'package:cake_wallet/utils/share_util.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/settings/mweb_settings_view_model.dart';
import 'package:cw_core/root_dir.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
class MwebLogsPage extends BasePage {
MwebLogsPage(this.mwebSettingsViewModelBase);
final MwebSettingsViewModelBase mwebSettingsViewModelBase;
@override
String get title => S.current.litecoin_mweb_logs;
@override
Widget body(BuildContext context) {
return Stack(
fit: StackFit.expand,
children: [
FutureBuilder<String>(
future: mwebSettingsViewModelBase.getAbbreviatedLogs(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
} else if (snapshot.hasError || !snapshot.hasData || snapshot.data!.isEmpty) {
return Center(child: Text('No logs found'));
} else {
return SingleChildScrollView(
child: Padding(
padding: EdgeInsets.all(16.0),
child: Text(
snapshot.data!,
style: TextStyle(fontFamily: 'Monospace'),
),
),
);
}
},
),
Positioned(
child: LoadingPrimaryButton(
onPressed: () => onExportLogs(context),
text: S.of(context).export_logs,
color: Theme.of(context).primaryColor,
textColor: Colors.white,
),
bottom: 24,
left: 24,
right: 24,
)
],
);
}
void onExportLogs(BuildContext context) {
if (Platform.isAndroid) {
onExportAndroid(context);
} else if (Platform.isIOS) {
share(context);
} else {
_saveFile();
}
}
void onExportAndroid(BuildContext context) {
showPopUp<void>(
context: context,
builder: (dialogContext) {
return AlertWithTwoActions(
alertTitle: S.of(context).export_backup,
alertContent: S.of(context).select_destination,
rightButtonText: S.of(context).save_to_downloads,
leftButtonText: S.of(context).share,
actionRightButton: () async {
const downloadDirPath = "/storage/emulated/0/Download";
final filePath = downloadDirPath + "/debug.log";
await mwebSettingsViewModelBase.saveLogsLocally(filePath);
Navigator.of(dialogContext).pop();
},
actionLeftButton: () async {
Navigator.of(dialogContext).pop();
try {
await share(context);
} catch (e, s) {
ExceptionHandler.onError(FlutterErrorDetails(
exception: e,
stack: s,
library: "Export Logs",
));
}
});
});
}
Future<void> share(BuildContext context) async {
final filePath = (await getAppDir()).path + "/debug.log";
bool success = await mwebSettingsViewModelBase.saveLogsLocally(filePath);
if (!success) return;
await ShareUtil.shareFile(filePath: filePath, fileName: "debug.log", context: context);
await mwebSettingsViewModelBase.removeLogsLocally(filePath);
}
Future<void> _saveFile() async {
String? outputFile = await FilePicker.platform
.saveFile(dialogTitle: 'Save Your File to desired location', fileName: "debug.log");
try {
final filePath = (await getApplicationSupportDirectory()).path + "/debug.log";
File debugLogFile = File(filePath);
await debugLogFile.copy(outputFile!);
} catch (exception, stackTrace) {
ExceptionHandler.onError(FlutterErrorDetails(
exception: exception,
stack: stackTrace,
library: "Export Logs",
));
}
}
}

View file

@ -0,0 +1,56 @@
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/view_model/settings/mweb_settings_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
class MwebNodePage extends BasePage {
MwebNodePage(this.mwebSettingsViewModelBase)
: _nodeUriController = TextEditingController(text: mwebSettingsViewModelBase.mwebNodeUri),
super();
final MwebSettingsViewModelBase mwebSettingsViewModelBase;
final TextEditingController _nodeUriController;
@override
String get title => S.current.litecoin_mweb_node;
@override
Widget body(BuildContext context) {
return Stack(
fit: StackFit.expand,
children: [
Container(
padding: EdgeInsets.symmetric(horizontal: 24),
child: Row(
children: <Widget>[
Expanded(
child: BaseTextFormField(controller: _nodeUriController),
)
],
),
),
Positioned(
child: Observer(
builder: (_) => LoadingPrimaryButton(
onPressed: () => save(context),
text: S.of(context).save,
color: Theme.of(context).primaryColor,
textColor: Colors.white,
),
),
bottom: 24,
left: 24,
right: 24,
)
],
);
}
void save(BuildContext context) {
mwebSettingsViewModelBase.setMwebNodeUri(_nodeUriController.text);
Navigator.pop(context);
}
}

View file

@ -31,7 +31,7 @@ class MwebSettingsPage extends BasePage {
}, },
), ),
SettingsSwitcherCell( SettingsSwitcherCell(
title: S.current.litecoin_mweb_always_scan, title: S.current.litecoin_mweb_enable,
value: _mwebSettingsViewModel.mwebEnabled, value: _mwebSettingsViewModel.mwebEnabled,
onValueChange: (_, bool value) { onValueChange: (_, bool value) {
_mwebSettingsViewModel.setMwebEnabled(value); _mwebSettingsViewModel.setMwebEnabled(value);
@ -41,6 +41,14 @@ class MwebSettingsPage extends BasePage {
title: S.current.litecoin_mweb_scanning, title: S.current.litecoin_mweb_scanning,
handler: (BuildContext context) => Navigator.of(context).pushNamed(Routes.rescan), handler: (BuildContext context) => Navigator.of(context).pushNamed(Routes.rescan),
), ),
SettingsCellWithArrow(
title: S.current.litecoin_mweb_logs,
handler: (BuildContext context) => Navigator.of(context).pushNamed(Routes.mwebLogs),
),
SettingsCellWithArrow(
title: S.current.litecoin_mweb_node,
handler: (BuildContext context) => Navigator.of(context).pushNamed(Routes.mwebNode),
),
], ],
), ),
); );

View file

@ -120,6 +120,7 @@ abstract class SettingsStoreBase with Store {
required this.mwebCardDisplay, required this.mwebCardDisplay,
required this.mwebEnabled, required this.mwebEnabled,
required this.hasEnabledMwebBefore, required this.hasEnabledMwebBefore,
required this.mwebNodeUri,
TransactionPriority? initialBitcoinTransactionPriority, TransactionPriority? initialBitcoinTransactionPriority,
TransactionPriority? initialMoneroTransactionPriority, TransactionPriority? initialMoneroTransactionPriority,
TransactionPriority? initialWowneroTransactionPriority, TransactionPriority? initialWowneroTransactionPriority,
@ -358,8 +359,8 @@ abstract class SettingsStoreBase with Store {
reaction( reaction(
(_) => bitcoinSeedType, (_) => bitcoinSeedType,
(BitcoinSeedType bitcoinSeedType) => sharedPreferences.setInt( (BitcoinSeedType bitcoinSeedType) =>
PreferencesKey.bitcoinSeedType, bitcoinSeedType.raw)); sharedPreferences.setInt(PreferencesKey.bitcoinSeedType, bitcoinSeedType.raw));
reaction( reaction(
(_) => nanoSeedType, (_) => nanoSeedType,
@ -442,8 +443,10 @@ abstract class SettingsStoreBase with Store {
reaction((_) => useTronGrid, reaction((_) => useTronGrid,
(bool useTronGrid) => _sharedPreferences.setBool(PreferencesKey.useTronGrid, useTronGrid)); (bool useTronGrid) => _sharedPreferences.setBool(PreferencesKey.useTronGrid, useTronGrid));
reaction((_) => useMempoolFeeAPI, reaction(
(bool useMempoolFeeAPI) => _sharedPreferences.setBool(PreferencesKey.useMempoolFeeAPI, useMempoolFeeAPI)); (_) => useMempoolFeeAPI,
(bool useMempoolFeeAPI) =>
_sharedPreferences.setBool(PreferencesKey.useMempoolFeeAPI, useMempoolFeeAPI));
reaction((_) => defaultNanoRep, reaction((_) => defaultNanoRep,
(String nanoRep) => _sharedPreferences.setString(PreferencesKey.defaultNanoRep, nanoRep)); (String nanoRep) => _sharedPreferences.setString(PreferencesKey.defaultNanoRep, nanoRep));
@ -591,6 +594,11 @@ abstract class SettingsStoreBase with Store {
(bool hasEnabledMwebBefore) => (bool hasEnabledMwebBefore) =>
_sharedPreferences.setBool(PreferencesKey.hasEnabledMwebBefore, hasEnabledMwebBefore)); _sharedPreferences.setBool(PreferencesKey.hasEnabledMwebBefore, hasEnabledMwebBefore));
reaction(
(_) => mwebNodeUri,
(String mwebNodeUri) =>
_sharedPreferences.setString(PreferencesKey.mwebNodeUri, mwebNodeUri));
this.nodes.observe((change) { this.nodes.observe((change) {
if (change.newValue != null && change.key != null) { if (change.newValue != null && change.key != null) {
_saveCurrentNode(change.newValue!, change.key!); _saveCurrentNode(change.newValue!, change.key!);
@ -822,6 +830,9 @@ abstract class SettingsStoreBase with Store {
@observable @observable
bool hasEnabledMwebBefore; bool hasEnabledMwebBefore;
@observable
String mwebNodeUri;
final SecureStorage _secureStorage; final SecureStorage _secureStorage;
final SharedPreferences _sharedPreferences; final SharedPreferences _sharedPreferences;
final BackgroundTasks _backgroundTasks; final BackgroundTasks _backgroundTasks;
@ -988,6 +999,8 @@ abstract class SettingsStoreBase with Store {
final mwebEnabled = sharedPreferences.getBool(PreferencesKey.mwebEnabled) ?? false; final mwebEnabled = sharedPreferences.getBool(PreferencesKey.mwebEnabled) ?? false;
final hasEnabledMwebBefore = final hasEnabledMwebBefore =
sharedPreferences.getBool(PreferencesKey.hasEnabledMwebBefore) ?? false; sharedPreferences.getBool(PreferencesKey.hasEnabledMwebBefore) ?? false;
final mwebNodeUri = sharedPreferences.getString(PreferencesKey.mwebNodeUri) ??
"ltc-electrum.cakewallet.com:9333";
// If no value // If no value
if (pinLength == null || pinLength == 0) { if (pinLength == null || pinLength == 0) {
@ -1259,6 +1272,7 @@ abstract class SettingsStoreBase with Store {
mwebAlwaysScan: mwebAlwaysScan, mwebAlwaysScan: mwebAlwaysScan,
mwebCardDisplay: mwebCardDisplay, mwebCardDisplay: mwebCardDisplay,
mwebEnabled: mwebEnabled, mwebEnabled: mwebEnabled,
mwebNodeUri: mwebNodeUri,
hasEnabledMwebBefore: hasEnabledMwebBefore, hasEnabledMwebBefore: hasEnabledMwebBefore,
initialMoneroTransactionPriority: moneroTransactionPriority, initialMoneroTransactionPriority: moneroTransactionPriority,
initialWowneroTransactionPriority: wowneroTransactionPriority, initialWowneroTransactionPriority: wowneroTransactionPriority,
@ -1686,7 +1700,8 @@ abstract class SettingsStoreBase with Store {
deviceName = windowsInfo.productName; deviceName = windowsInfo.productName;
} catch (e) { } catch (e) {
print(e); print(e);
print('likely digitalProductId is null wait till https://github.com/fluttercommunity/plus_plugins/pull/3188 is merged'); print(
'likely digitalProductId is null wait till https://github.com/fluttercommunity/plus_plugins/pull/3188 is merged');
deviceName = "Windows Device"; deviceName = "Windows Device";
} }
} }

View file

@ -381,7 +381,7 @@ abstract class BalanceViewModelBase with Store {
bool _hasSecondAdditionalBalanceForWalletType(WalletType type) { bool _hasSecondAdditionalBalanceForWalletType(WalletType type) {
if (wallet.type == WalletType.litecoin) { if (wallet.type == WalletType.litecoin) {
if ((wallet.balance[CryptoCurrency.ltc]?.secondAdditional ?? 0) > 0) { if ((wallet.balance[CryptoCurrency.ltc]?.secondAdditional ?? 0) != 0) {
return true; return true;
} }
} }

View file

@ -56,25 +56,53 @@ class TransactionListItem extends ActionListItem with Keyable {
} }
String get formattedPendingStatus { String get formattedPendingStatus {
if (balanceViewModel.wallet.type == WalletType.monero || switch (balanceViewModel.wallet.type) {
balanceViewModel.wallet.type == WalletType.haven) { case WalletType.monero:
if (transaction.confirmations >= 0 && transaction.confirmations < 10) { case WalletType.haven:
return ' (${transaction.confirmations}/10)'; if (transaction.confirmations >= 0 && transaction.confirmations < 10) {
} return ' (${transaction.confirmations}/10)';
} else if (balanceViewModel.wallet.type == WalletType.wownero) { }
if (transaction.confirmations >= 0 && transaction.confirmations < 3) { break;
return ' (${transaction.confirmations}/3)'; case WalletType.wownero:
} if (transaction.confirmations >= 0 && transaction.confirmations < 3) {
return ' (${transaction.confirmations}/3)';
}
break;
case WalletType.litecoin:
bool isPegIn = (transaction.additionalInfo["isPegIn"] as bool?) ?? false;
bool isPegOut = (transaction.additionalInfo["isPegOut"] as bool?) ?? false;
bool fromPegOut = (transaction.additionalInfo["fromPegOut"] as bool?) ?? false;
String str = '';
if (transaction.confirmations <= 0) {
str = S.current.pending;
}
if ((isPegOut || fromPegOut) && transaction.confirmations >= 0 && transaction.confirmations < 6) {
str = " (${transaction.confirmations}/6)";
}
if (isPegIn) {
str += " (Peg In)";
}
if (isPegOut) {
str += " (Peg Out)";
}
return str;
default:
return '';
} }
return ''; return '';
} }
String get formattedStatus { String get formattedStatus {
if (balanceViewModel.wallet.type == WalletType.monero || if ([
balanceViewModel.wallet.type == WalletType.wownero || WalletType.monero,
balanceViewModel.wallet.type == WalletType.haven) { WalletType.haven,
WalletType.wownero,
WalletType.litecoin,
].contains(balanceViewModel.wallet.type)) {
return formattedPendingStatus; return formattedPendingStatus;
} }
return transaction.isPending ? S.current.pending : ''; return transaction.isPending ? S.current.pending : '';
} }

View file

@ -1,7 +1,12 @@
import 'dart:io';
import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/utils/exception_handler.dart';
import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_base.dart';
import 'package:flutter/widgets.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:path_provider/path_provider.dart';
part 'mweb_settings_view_model.g.dart'; part 'mweb_settings_view_model.g.dart';
@ -22,15 +27,60 @@ abstract class MwebSettingsViewModelBase with Store {
@observable @observable
late bool mwebEnabled; late bool mwebEnabled;
@computed
String get mwebNodeUri => _settingsStore.mwebNodeUri;
@action @action
void setMwebCardDisplay(bool value) { void setMwebCardDisplay(bool value) {
_settingsStore.mwebCardDisplay = value; _settingsStore.mwebCardDisplay = value;
} }
@action
void setMwebNodeUri(String value) {
_settingsStore.mwebNodeUri = value;
}
@action @action
void setMwebEnabled(bool value) { void setMwebEnabled(bool value) {
mwebEnabled = value; mwebEnabled = value;
bitcoin!.setMwebEnabled(_wallet, value); bitcoin!.setMwebEnabled(_wallet, value);
_settingsStore.mwebAlwaysScan = value; _settingsStore.mwebAlwaysScan = value;
} }
Future<bool> saveLogsLocally(String filePath) async {
try {
final appSupportPath = (await getApplicationSupportDirectory()).path;
final logsFile = File("$appSupportPath/logs/debug.log");
if (!logsFile.existsSync()) {
throw Exception('Logs file does not exist');
}
await logsFile.copy(filePath);
return true;
} catch (e, s) {
ExceptionHandler.onError(FlutterErrorDetails(
exception: e,
stack: s,
library: "Export Logs",
));
return false;
}
}
Future<String> getAbbreviatedLogs() async {
final appSupportPath = (await getApplicationSupportDirectory()).path;
final logsFile = File("$appSupportPath/logs/debug.log");
if (!logsFile.existsSync()) {
return "";
}
final logs = logsFile.readAsStringSync();
// return last 10000 characters:
return logs.substring(logs.length > 10000 ? logs.length - 10000 : 0);
}
Future<void> removeLogsLocally(String filePath) async {
final logsFile = File(filePath);
if (logsFile.existsSync()) {
await logsFile.delete();
}
}
} }

View file

@ -134,7 +134,7 @@ dependency_overrides:
bitcoin_base: bitcoin_base:
git: git:
url: https://github.com/cake-tech/bitcoin_base url: https://github.com/cake-tech/bitcoin_base
ref: cake-update-v8 ref: cake-update-v9
ffi: 2.1.0 ffi: 2.1.0
flutter_icons: flutter_icons:

View file

@ -295,6 +295,7 @@
"expiresOn": "ﻲﻓ ﻪﺘﻴﺣﻼﺻ ﻲﻬﺘﻨﺗ", "expiresOn": "ﻲﻓ ﻪﺘﻴﺣﻼﺻ ﻲﻬﺘﻨﺗ",
"expiry_and_validity": "انتهاء الصلاحية والصلاحية", "expiry_and_validity": "انتهاء الصلاحية والصلاحية",
"export_backup": "تصدير نسخة احتياطية", "export_backup": "تصدير نسخة احتياطية",
"export_logs": "سجلات التصدير",
"extra_id": "معرف إضافي:", "extra_id": "معرف إضافي:",
"extracted_address_content": "سوف ترسل الأموال إلى\n${recipient_name}", "extracted_address_content": "سوف ترسل الأموال إلى\n${recipient_name}",
"failed_authentication": "${state_error} فشل المصادقة.", "failed_authentication": "${state_error} فشل المصادقة.",
@ -371,7 +372,10 @@
"litecoin_mweb_description": "MWEB هو بروتوكول جديد يجلب معاملات أسرع وأرخص وأكثر خصوصية إلى Litecoin", "litecoin_mweb_description": "MWEB هو بروتوكول جديد يجلب معاملات أسرع وأرخص وأكثر خصوصية إلى Litecoin",
"litecoin_mweb_dismiss": "رفض", "litecoin_mweb_dismiss": "رفض",
"litecoin_mweb_display_card": "عرض بطاقة mweb", "litecoin_mweb_display_card": "عرض بطاقة mweb",
"litecoin_mweb_enable": "تمكين MWEB",
"litecoin_mweb_enable_later": "يمكنك اختيار تمكين MWEB مرة أخرى ضمن إعدادات العرض.", "litecoin_mweb_enable_later": "يمكنك اختيار تمكين MWEB مرة أخرى ضمن إعدادات العرض.",
"litecoin_mweb_logs": "سجلات MWEB",
"litecoin_mweb_node": "عقدة MWEB",
"litecoin_mweb_pegin": "ربط في", "litecoin_mweb_pegin": "ربط في",
"litecoin_mweb_pegout": "ربط", "litecoin_mweb_pegout": "ربط",
"litecoin_mweb_scanning": "MWEB المسح الضوئي", "litecoin_mweb_scanning": "MWEB المسح الضوئي",

View file

@ -295,6 +295,7 @@
"expiresOn": "Изтича на", "expiresOn": "Изтича на",
"expiry_and_validity": "Изтичане и валидност", "expiry_and_validity": "Изтичане и валидност",
"export_backup": "Експортиране на резервно копие", "export_backup": "Експортиране на резервно копие",
"export_logs": "Експортни дневници",
"extra_id": "Допълнително ID:", "extra_id": "Допълнително ID:",
"extracted_address_content": "Ще изпратите средства на \n${recipient_name}", "extracted_address_content": "Ще изпратите средства на \n${recipient_name}",
"failed_authentication": "Неуспешно удостоверяване. ${state_error}", "failed_authentication": "Неуспешно удостоверяване. ${state_error}",
@ -371,7 +372,10 @@
"litecoin_mweb_description": "MWeb е нов протокол, който носи по -бърз, по -евтин и повече частни транзакции на Litecoin", "litecoin_mweb_description": "MWeb е нов протокол, който носи по -бърз, по -евтин и повече частни транзакции на Litecoin",
"litecoin_mweb_dismiss": "Уволнение", "litecoin_mweb_dismiss": "Уволнение",
"litecoin_mweb_display_card": "Показване на MWEB карта", "litecoin_mweb_display_card": "Показване на MWEB карта",
"litecoin_mweb_enable": "Активирайте MWeb",
"litecoin_mweb_enable_later": "Можете да изберете да активирате MWEB отново под настройките на дисплея.", "litecoin_mweb_enable_later": "Можете да изберете да активирате MWEB отново под настройките на дисплея.",
"litecoin_mweb_logs": "MWeb logs",
"litecoin_mweb_node": "MWEB възел",
"litecoin_mweb_pegin": "PEG в", "litecoin_mweb_pegin": "PEG в",
"litecoin_mweb_pegout": "PEG OUT", "litecoin_mweb_pegout": "PEG OUT",
"litecoin_mweb_scanning": "Сканиране на MWEB", "litecoin_mweb_scanning": "Сканиране на MWEB",

View file

@ -295,6 +295,7 @@
"expiresOn": "Vyprší dne", "expiresOn": "Vyprší dne",
"expiry_and_validity": "Vypršení a platnost", "expiry_and_validity": "Vypršení a platnost",
"export_backup": "Exportovat zálohu", "export_backup": "Exportovat zálohu",
"export_logs": "Vývozní protokoly",
"extra_id": "Extra ID:", "extra_id": "Extra ID:",
"extracted_address_content": "Prostředky budete posílat na\n${recipient_name}", "extracted_address_content": "Prostředky budete posílat na\n${recipient_name}",
"failed_authentication": "Ověřování selhalo. ${state_error}", "failed_authentication": "Ověřování selhalo. ${state_error}",
@ -371,7 +372,10 @@
"litecoin_mweb_description": "MWEB je nový protokol, který do Litecoin přináší rychlejší, levnější a více soukromých transakcí", "litecoin_mweb_description": "MWEB je nový protokol, který do Litecoin přináší rychlejší, levnější a více soukromých transakcí",
"litecoin_mweb_dismiss": "Propustit", "litecoin_mweb_dismiss": "Propustit",
"litecoin_mweb_display_card": "Zobrazit kartu MWeb", "litecoin_mweb_display_card": "Zobrazit kartu MWeb",
"litecoin_mweb_enable": "Povolit mWeb",
"litecoin_mweb_enable_later": "V nastavení zobrazení můžete vybrat znovu povolit MWeb.", "litecoin_mweb_enable_later": "V nastavení zobrazení můžete vybrat znovu povolit MWeb.",
"litecoin_mweb_logs": "Protokoly mWeb",
"litecoin_mweb_node": "Uzel mWeb",
"litecoin_mweb_pegin": "Peg in", "litecoin_mweb_pegin": "Peg in",
"litecoin_mweb_pegout": "Zkrachovat", "litecoin_mweb_pegout": "Zkrachovat",
"litecoin_mweb_scanning": "Skenování mWeb", "litecoin_mweb_scanning": "Skenování mWeb",

View file

@ -295,6 +295,7 @@
"expiresOn": "Läuft aus am", "expiresOn": "Läuft aus am",
"expiry_and_validity": "Ablauf und Gültigkeit", "expiry_and_validity": "Ablauf und Gültigkeit",
"export_backup": "Sicherung exportieren", "export_backup": "Sicherung exportieren",
"export_logs": "Exportprotokolle",
"extra_id": "Extra ID:", "extra_id": "Extra ID:",
"extracted_address_content": "Sie senden Geld an\n${recipient_name}", "extracted_address_content": "Sie senden Geld an\n${recipient_name}",
"failed_authentication": "Authentifizierung fehlgeschlagen. ${state_error}", "failed_authentication": "Authentifizierung fehlgeschlagen. ${state_error}",
@ -371,7 +372,10 @@
"litecoin_mweb_description": "MWWB ist ein neues Protokoll, das schnellere, billigere und privatere Transaktionen zu Litecoin bringt", "litecoin_mweb_description": "MWWB ist ein neues Protokoll, das schnellere, billigere und privatere Transaktionen zu Litecoin bringt",
"litecoin_mweb_dismiss": "Zurückweisen", "litecoin_mweb_dismiss": "Zurückweisen",
"litecoin_mweb_display_card": "MWEB-Karte anzeigen", "litecoin_mweb_display_card": "MWEB-Karte anzeigen",
"litecoin_mweb_enable": "Aktivieren Sie MWeb",
"litecoin_mweb_enable_later": "Sie können MWEB unter Anzeigeeinstellungen erneut aktivieren.", "litecoin_mweb_enable_later": "Sie können MWEB unter Anzeigeeinstellungen erneut aktivieren.",
"litecoin_mweb_logs": "MWEB -Protokolle",
"litecoin_mweb_node": "MWEB -Knoten",
"litecoin_mweb_pegin": "Peg in", "litecoin_mweb_pegin": "Peg in",
"litecoin_mweb_pegout": "Abstecken", "litecoin_mweb_pegout": "Abstecken",
"litecoin_mweb_scanning": "MWEB Scanning", "litecoin_mweb_scanning": "MWEB Scanning",
@ -941,4 +945,4 @@
"you_will_get": "Konvertieren zu", "you_will_get": "Konvertieren zu",
"you_will_send": "Konvertieren von", "you_will_send": "Konvertieren von",
"yy": "YY" "yy": "YY"
} }

View file

@ -295,6 +295,7 @@
"expiresOn": "Expires on", "expiresOn": "Expires on",
"expiry_and_validity": "Expiry and Validity", "expiry_and_validity": "Expiry and Validity",
"export_backup": "Export backup", "export_backup": "Export backup",
"export_logs": "Export logs",
"extra_id": "Extra ID:", "extra_id": "Extra ID:",
"extracted_address_content": "You will be sending funds to\n${recipient_name}", "extracted_address_content": "You will be sending funds to\n${recipient_name}",
"failed_authentication": "Failed authentication. ${state_error}", "failed_authentication": "Failed authentication. ${state_error}",
@ -373,7 +374,10 @@
"litecoin_mweb_description": "MWEB is a new protocol that brings faster, cheaper, and more private transactions to Litecoin", "litecoin_mweb_description": "MWEB is a new protocol that brings faster, cheaper, and more private transactions to Litecoin",
"litecoin_mweb_dismiss": "Dismiss", "litecoin_mweb_dismiss": "Dismiss",
"litecoin_mweb_display_card": "Show MWEB card", "litecoin_mweb_display_card": "Show MWEB card",
"litecoin_mweb_enable": "Enable MWEB",
"litecoin_mweb_enable_later": "You can choose to enable MWEB again under Display Settings.", "litecoin_mweb_enable_later": "You can choose to enable MWEB again under Display Settings.",
"litecoin_mweb_logs": "MWEB Logs",
"litecoin_mweb_node": "MWEB Node",
"litecoin_mweb_pegin": "Peg In", "litecoin_mweb_pegin": "Peg In",
"litecoin_mweb_pegout": "Peg Out", "litecoin_mweb_pegout": "Peg Out",
"litecoin_mweb_scanning": "MWEB Scanning", "litecoin_mweb_scanning": "MWEB Scanning",

View file

@ -295,6 +295,7 @@
"expiresOn": "Expira el", "expiresOn": "Expira el",
"expiry_and_validity": "Vencimiento y validez", "expiry_and_validity": "Vencimiento y validez",
"export_backup": "Exportar copia de seguridad", "export_backup": "Exportar copia de seguridad",
"export_logs": "Registros de exportación",
"extra_id": "ID adicional:", "extra_id": "ID adicional:",
"extracted_address_content": "Enviará fondos a\n${recipient_name}", "extracted_address_content": "Enviará fondos a\n${recipient_name}",
"failed_authentication": "Autenticación fallida. ${state_error}", "failed_authentication": "Autenticación fallida. ${state_error}",
@ -371,7 +372,10 @@
"litecoin_mweb_description": "Mweb es un nuevo protocolo que trae transacciones más rápidas, más baratas y más privadas a Litecoin", "litecoin_mweb_description": "Mweb es un nuevo protocolo que trae transacciones más rápidas, más baratas y más privadas a Litecoin",
"litecoin_mweb_dismiss": "Despedir", "litecoin_mweb_dismiss": "Despedir",
"litecoin_mweb_display_card": "Mostrar tarjeta MWEB", "litecoin_mweb_display_card": "Mostrar tarjeta MWEB",
"litecoin_mweb_enable": "Habilitar mweb",
"litecoin_mweb_enable_later": "Puede elegir habilitar MWEB nuevamente en la configuración de visualización.", "litecoin_mweb_enable_later": "Puede elegir habilitar MWEB nuevamente en la configuración de visualización.",
"litecoin_mweb_logs": "Registros de mweb",
"litecoin_mweb_node": "Nodo mweb",
"litecoin_mweb_pegin": "Convertir", "litecoin_mweb_pegin": "Convertir",
"litecoin_mweb_pegout": "Recuperar", "litecoin_mweb_pegout": "Recuperar",
"litecoin_mweb_scanning": "Escaneo mweb", "litecoin_mweb_scanning": "Escaneo mweb",

View file

@ -295,6 +295,7 @@
"expiresOn": "Expire le", "expiresOn": "Expire le",
"expiry_and_validity": "Expiration et validité", "expiry_and_validity": "Expiration et validité",
"export_backup": "Exporter la sauvegarde", "export_backup": "Exporter la sauvegarde",
"export_logs": "Journaux d'exportation",
"extra_id": "ID supplémentaire :", "extra_id": "ID supplémentaire :",
"extracted_address_content": "Vous allez envoyer des fonds à\n${recipient_name}", "extracted_address_content": "Vous allez envoyer des fonds à\n${recipient_name}",
"failed_authentication": "Échec d'authentification. ${state_error}", "failed_authentication": "Échec d'authentification. ${state_error}",
@ -371,7 +372,10 @@
"litecoin_mweb_description": "MWEB est un nouveau protocole qui apporte des transactions plus rapides, moins chères et plus privées à Litecoin", "litecoin_mweb_description": "MWEB est un nouveau protocole qui apporte des transactions plus rapides, moins chères et plus privées à Litecoin",
"litecoin_mweb_dismiss": "Rejeter", "litecoin_mweb_dismiss": "Rejeter",
"litecoin_mweb_display_card": "Afficher la carte MWeb", "litecoin_mweb_display_card": "Afficher la carte MWeb",
"litecoin_mweb_enable": "Activer Mweb",
"litecoin_mweb_enable_later": "Vous pouvez choisir d'activer à nouveau MWEB sous Paramètres d'affichage.", "litecoin_mweb_enable_later": "Vous pouvez choisir d'activer à nouveau MWEB sous Paramètres d'affichage.",
"litecoin_mweb_logs": "Journaux MWEB",
"litecoin_mweb_node": "Node MWEB",
"litecoin_mweb_pegin": "Entraver", "litecoin_mweb_pegin": "Entraver",
"litecoin_mweb_pegout": "Crever", "litecoin_mweb_pegout": "Crever",
"litecoin_mweb_scanning": "Scann mweb", "litecoin_mweb_scanning": "Scann mweb",

View file

@ -295,6 +295,7 @@
"expiresOn": "Yana ƙarewa", "expiresOn": "Yana ƙarewa",
"expiry_and_validity": "Karewa da inganci", "expiry_and_validity": "Karewa da inganci",
"export_backup": "Ajiyayyen fitarwa", "export_backup": "Ajiyayyen fitarwa",
"export_logs": "Injin fitarwa",
"extra_id": "Karin ID:", "extra_id": "Karin ID:",
"extracted_address_content": "Za ku aika da kudade zuwa\n${recipient_name}", "extracted_address_content": "Za ku aika da kudade zuwa\n${recipient_name}",
"failed_authentication": "Binne wajen shiga. ${state_error}", "failed_authentication": "Binne wajen shiga. ${state_error}",
@ -371,7 +372,10 @@
"litecoin_mweb_description": "Mweb shine sabon tsarin yarjejeniya da ya kawo da sauri, mai rahusa, da kuma ma'amaloli masu zaman kansu zuwa Litecoin", "litecoin_mweb_description": "Mweb shine sabon tsarin yarjejeniya da ya kawo da sauri, mai rahusa, da kuma ma'amaloli masu zaman kansu zuwa Litecoin",
"litecoin_mweb_dismiss": "Tuɓe \\ sallama", "litecoin_mweb_dismiss": "Tuɓe \\ sallama",
"litecoin_mweb_display_card": "Nuna katin Mweb", "litecoin_mweb_display_card": "Nuna katin Mweb",
"litecoin_mweb_enable": "Kunna Mweb",
"litecoin_mweb_enable_later": "Kuna iya zaɓar kunna Mweb kuma a ƙarƙashin saitunan nuni.", "litecoin_mweb_enable_later": "Kuna iya zaɓar kunna Mweb kuma a ƙarƙashin saitunan nuni.",
"litecoin_mweb_logs": "Jagoran Mweb",
"litecoin_mweb_node": "Mweb Node",
"litecoin_mweb_pegin": "Peg in", "litecoin_mweb_pegin": "Peg in",
"litecoin_mweb_pegout": "Peg fita", "litecoin_mweb_pegout": "Peg fita",
"litecoin_mweb_scanning": "Mweb scanning", "litecoin_mweb_scanning": "Mweb scanning",

View file

@ -295,6 +295,7 @@
"expiresOn": "पर समय सीमा समाप्त", "expiresOn": "पर समय सीमा समाप्त",
"expiry_and_validity": "समाप्ति और वैधता", "expiry_and_validity": "समाप्ति और वैधता",
"export_backup": "निर्यात बैकअप", "export_backup": "निर्यात बैकअप",
"export_logs": "निर्यात लॉग",
"extra_id": "अतिरिक्त आईडी:", "extra_id": "अतिरिक्त आईडी:",
"extracted_address_content": "आपको धनराशि भेजी जाएगी\n${recipient_name}", "extracted_address_content": "आपको धनराशि भेजी जाएगी\n${recipient_name}",
"failed_authentication": "प्रमाणीकरण विफल. ${state_error}", "failed_authentication": "प्रमाणीकरण विफल. ${state_error}",
@ -371,7 +372,10 @@
"litecoin_mweb_description": "MWEB एक नया प्रोटोकॉल है जो लिटकोइन के लिए तेजी से, सस्ता और अधिक निजी लेनदेन लाता है", "litecoin_mweb_description": "MWEB एक नया प्रोटोकॉल है जो लिटकोइन के लिए तेजी से, सस्ता और अधिक निजी लेनदेन लाता है",
"litecoin_mweb_dismiss": "नकार देना", "litecoin_mweb_dismiss": "नकार देना",
"litecoin_mweb_display_card": "MWEB कार्ड दिखाएं", "litecoin_mweb_display_card": "MWEB कार्ड दिखाएं",
"litecoin_mweb_enable": "MWEB सक्षम करें",
"litecoin_mweb_enable_later": "आप प्रदर्शन सेटिंग्स के तहत फिर से MWEB को सक्षम करने के लिए चुन सकते हैं।", "litecoin_mweb_enable_later": "आप प्रदर्शन सेटिंग्स के तहत फिर से MWEB को सक्षम करने के लिए चुन सकते हैं।",
"litecoin_mweb_logs": "MWEB लॉग",
"litecoin_mweb_node": "MWEB नोड",
"litecoin_mweb_pegin": "खूंटी", "litecoin_mweb_pegin": "खूंटी",
"litecoin_mweb_pegout": "मरना", "litecoin_mweb_pegout": "मरना",
"litecoin_mweb_scanning": "MWEB स्कैनिंग", "litecoin_mweb_scanning": "MWEB स्कैनिंग",

View file

@ -295,6 +295,7 @@
"expiresOn": "Istječe", "expiresOn": "Istječe",
"expiry_and_validity": "Istek i valjanost", "expiry_and_validity": "Istek i valjanost",
"export_backup": "Izvezi sigurnosnu kopiju", "export_backup": "Izvezi sigurnosnu kopiju",
"export_logs": "Izvozni trupci",
"extra_id": "Dodatni ID:", "extra_id": "Dodatni ID:",
"extracted_address_content": "Poslat ćete sredstva primatelju\n${recipient_name}", "extracted_address_content": "Poslat ćete sredstva primatelju\n${recipient_name}",
"failed_authentication": "Autentifikacija neuspješna. ${state_error}", "failed_authentication": "Autentifikacija neuspješna. ${state_error}",
@ -371,7 +372,10 @@
"litecoin_mweb_description": "MWEB je novi protokol koji u Litecoin donosi brže, jeftinije i privatnije transakcije", "litecoin_mweb_description": "MWEB je novi protokol koji u Litecoin donosi brže, jeftinije i privatnije transakcije",
"litecoin_mweb_dismiss": "Odbaciti", "litecoin_mweb_dismiss": "Odbaciti",
"litecoin_mweb_display_card": "Prikaži MWeb karticu", "litecoin_mweb_display_card": "Prikaži MWeb karticu",
"litecoin_mweb_enable": "Omogući MWeb",
"litecoin_mweb_enable_later": "Možete odabrati da MWEB ponovo omogućite pod postavkama zaslona.", "litecoin_mweb_enable_later": "Možete odabrati da MWEB ponovo omogućite pod postavkama zaslona.",
"litecoin_mweb_logs": "MWEB trupci",
"litecoin_mweb_node": "MWEB čvor",
"litecoin_mweb_pegin": "Uvući se", "litecoin_mweb_pegin": "Uvući se",
"litecoin_mweb_pegout": "Odapeti", "litecoin_mweb_pegout": "Odapeti",
"litecoin_mweb_scanning": "MWEB skeniranje", "litecoin_mweb_scanning": "MWEB skeniranje",

View file

@ -295,6 +295,7 @@
"expiresOn": "Վավերականությունը լրանում է", "expiresOn": "Վավերականությունը լրանում է",
"expiry_and_validity": "Վավերականություն և լրացում", "expiry_and_validity": "Վավերականություն և լրացում",
"export_backup": "Արտահանել կրկնօրինակը", "export_backup": "Արտահանել կրկնօրինակը",
"export_logs": "Արտահանման տեղեկամատյաններ",
"extra_id": "Լրացուցիչ ID", "extra_id": "Լրացուցիչ ID",
"extracted_address_content": "Դուք կուղարկեք գումար ${recipient_name}", "extracted_address_content": "Դուք կուղարկեք գումար ${recipient_name}",
"failed_authentication": "Վավերացումը ձախողվեց. ${state_error}", "failed_authentication": "Վավերացումը ձախողվեց. ${state_error}",
@ -367,7 +368,10 @@
"light_theme": "Լուսավոր", "light_theme": "Լուսավոր",
"litecoin_mweb_description": "Mweb- ը նոր արձանագրություն է, որը բերում է ավելի արագ, ավելի էժան եւ ավելի մասնավոր գործարքներ դեպի LITECOIN", "litecoin_mweb_description": "Mweb- ը նոր արձանագրություն է, որը բերում է ավելի արագ, ավելի էժան եւ ավելի մասնավոր գործարքներ դեպի LITECOIN",
"litecoin_mweb_dismiss": "Հեռացնել", "litecoin_mweb_dismiss": "Հեռացնել",
"litecoin_mweb_enable": "Միացնել Mweb- ը",
"litecoin_mweb_enable_later": "Կարող եք ընտրել Mweb- ը կրկին միացնել ցուցադրման պարամետրերը:", "litecoin_mweb_enable_later": "Կարող եք ընտրել Mweb- ը կրկին միացնել ցուցադրման պարամետրերը:",
"litecoin_mweb_logs": "Mweb տեղեկամատյաններ",
"litecoin_mweb_node": "Mweb հանգույց",
"litecoin_mweb_pegin": "Peg in", "litecoin_mweb_pegin": "Peg in",
"litecoin_mweb_pegout": "Հափշտակել", "litecoin_mweb_pegout": "Հափշտակել",
"live_fee_rates": "Ապակի վարձավճարներ API- ի միջոցով", "live_fee_rates": "Ապակի վարձավճարներ API- ի միջոցով",

View file

@ -295,6 +295,7 @@
"expiresOn": "Kadaluarsa pada", "expiresOn": "Kadaluarsa pada",
"expiry_and_validity": "Kedaluwarsa dan validitas", "expiry_and_validity": "Kedaluwarsa dan validitas",
"export_backup": "Ekspor cadangan", "export_backup": "Ekspor cadangan",
"export_logs": "Log ekspor",
"extra_id": "ID tambahan:", "extra_id": "ID tambahan:",
"extracted_address_content": "Anda akan mengirim dana ke\n${recipient_name}", "extracted_address_content": "Anda akan mengirim dana ke\n${recipient_name}",
"failed_authentication": "Otentikasi gagal. ${state_error}", "failed_authentication": "Otentikasi gagal. ${state_error}",
@ -371,7 +372,10 @@
"litecoin_mweb_description": "MWEB adalah protokol baru yang membawa transaksi yang lebih cepat, lebih murah, dan lebih pribadi ke Litecoin", "litecoin_mweb_description": "MWEB adalah protokol baru yang membawa transaksi yang lebih cepat, lebih murah, dan lebih pribadi ke Litecoin",
"litecoin_mweb_dismiss": "Membubarkan", "litecoin_mweb_dismiss": "Membubarkan",
"litecoin_mweb_display_card": "Tunjukkan kartu mWeb", "litecoin_mweb_display_card": "Tunjukkan kartu mWeb",
"litecoin_mweb_enable": "Aktifkan MWEB",
"litecoin_mweb_enable_later": "Anda dapat memilih untuk mengaktifkan MWEB lagi di bawah pengaturan tampilan.", "litecoin_mweb_enable_later": "Anda dapat memilih untuk mengaktifkan MWEB lagi di bawah pengaturan tampilan.",
"litecoin_mweb_logs": "Log MWeb",
"litecoin_mweb_node": "Node MWEB",
"litecoin_mweb_pegin": "Pasak masuk", "litecoin_mweb_pegin": "Pasak masuk",
"litecoin_mweb_pegout": "Mati", "litecoin_mweb_pegout": "Mati",
"litecoin_mweb_scanning": "Pemindaian MWEB", "litecoin_mweb_scanning": "Pemindaian MWEB",

View file

@ -296,6 +296,7 @@
"expiresOn": "Scade il", "expiresOn": "Scade il",
"expiry_and_validity": "Scadenza e validità", "expiry_and_validity": "Scadenza e validità",
"export_backup": "Esporta backup", "export_backup": "Esporta backup",
"export_logs": "Registri di esportazione",
"extra_id": "Extra ID:", "extra_id": "Extra ID:",
"extracted_address_content": "Invierai i tuoi fondi a\n${recipient_name}", "extracted_address_content": "Invierai i tuoi fondi a\n${recipient_name}",
"failed_authentication": "Autenticazione fallita. ${state_error}", "failed_authentication": "Autenticazione fallita. ${state_error}",
@ -372,7 +373,10 @@
"litecoin_mweb_description": "MWeb è un nuovo protocollo che porta transazioni più veloci, più economiche e più private a Litecoin", "litecoin_mweb_description": "MWeb è un nuovo protocollo che porta transazioni più veloci, più economiche e più private a Litecoin",
"litecoin_mweb_dismiss": "Congedare", "litecoin_mweb_dismiss": "Congedare",
"litecoin_mweb_display_card": "Mostra la scheda MWeb", "litecoin_mweb_display_card": "Mostra la scheda MWeb",
"litecoin_mweb_enable": "Abilita mWeb",
"litecoin_mweb_enable_later": "È possibile scegliere di abilitare nuovamente MWeb nelle impostazioni di visualizzazione.", "litecoin_mweb_enable_later": "È possibile scegliere di abilitare nuovamente MWeb nelle impostazioni di visualizzazione.",
"litecoin_mweb_logs": "Registri mWeb",
"litecoin_mweb_node": "Nodo MWeb",
"litecoin_mweb_pegin": "Piolo in", "litecoin_mweb_pegin": "Piolo in",
"litecoin_mweb_pegout": "PEG OUT", "litecoin_mweb_pegout": "PEG OUT",
"litecoin_mweb_scanning": "Scansione MWeb", "litecoin_mweb_scanning": "Scansione MWeb",

View file

@ -295,6 +295,7 @@
"expiresOn": "有効期限は次のとおりです", "expiresOn": "有効期限は次のとおりです",
"expiry_and_validity": "有効期限と有効性", "expiry_and_validity": "有効期限と有効性",
"export_backup": "バックアップのエクスポート", "export_backup": "バックアップのエクスポート",
"export_logs": "ログをエクスポートします",
"extra_id": "追加ID:", "extra_id": "追加ID:",
"extracted_address_content": "に送金します\n${recipient_name}", "extracted_address_content": "に送金します\n${recipient_name}",
"failed_authentication": "認証失敗. ${state_error}", "failed_authentication": "認証失敗. ${state_error}",
@ -372,7 +373,10 @@
"litecoin_mweb_description": "MWEBは、Litecoinにより速く、より安価で、よりプライベートなトランザクションをもたらす新しいプロトコルです", "litecoin_mweb_description": "MWEBは、Litecoinにより速く、より安価で、よりプライベートなトランザクションをもたらす新しいプロトコルです",
"litecoin_mweb_dismiss": "却下する", "litecoin_mweb_dismiss": "却下する",
"litecoin_mweb_display_card": "MWEBカードを表示します", "litecoin_mweb_display_card": "MWEBカードを表示します",
"litecoin_mweb_enable": "MWEBを有効にします",
"litecoin_mweb_enable_later": "表示設定の下で、MWEBを再度有効にすることを選択できます。", "litecoin_mweb_enable_later": "表示設定の下で、MWEBを再度有効にすることを選択できます。",
"litecoin_mweb_logs": "MWEBログ",
"litecoin_mweb_node": "MWEBード",
"litecoin_mweb_pegin": "ペグイン", "litecoin_mweb_pegin": "ペグイン",
"litecoin_mweb_pegout": "ペグアウト", "litecoin_mweb_pegout": "ペグアウト",
"litecoin_mweb_scanning": "MWEBスキャン", "litecoin_mweb_scanning": "MWEBスキャン",

View file

@ -295,6 +295,7 @@
"expiresOn": "만료 날짜", "expiresOn": "만료 날짜",
"expiry_and_validity": "만료와 타당성", "expiry_and_validity": "만료와 타당성",
"export_backup": "백업 내보내기", "export_backup": "백업 내보내기",
"export_logs": "내보내기 로그",
"extra_id": "추가 ID:", "extra_id": "추가 ID:",
"extracted_address_content": "당신은에 자금을 보낼 것입니다\n${recipient_name}", "extracted_address_content": "당신은에 자금을 보낼 것입니다\n${recipient_name}",
"failed_authentication": "인증 실패. ${state_error}", "failed_authentication": "인증 실패. ${state_error}",
@ -371,7 +372,10 @@
"litecoin_mweb_description": "MWEB는 Litecoin에 더 빠르고 저렴하며 개인 거래를 제공하는 새로운 프로토콜입니다.", "litecoin_mweb_description": "MWEB는 Litecoin에 더 빠르고 저렴하며 개인 거래를 제공하는 새로운 프로토콜입니다.",
"litecoin_mweb_dismiss": "해고하다", "litecoin_mweb_dismiss": "해고하다",
"litecoin_mweb_display_card": "mweb 카드를 보여주십시오", "litecoin_mweb_display_card": "mweb 카드를 보여주십시오",
"litecoin_mweb_enable": "mweb 활성화",
"litecoin_mweb_enable_later": "디스플레이 설정에서 MWEB를 다시 활성화하도록 선택할 수 있습니다.", "litecoin_mweb_enable_later": "디스플레이 설정에서 MWEB를 다시 활성화하도록 선택할 수 있습니다.",
"litecoin_mweb_logs": "mweb 로그",
"litecoin_mweb_node": "mweb 노드",
"litecoin_mweb_pegin": "페그를 입력하십시오", "litecoin_mweb_pegin": "페그를 입력하십시오",
"litecoin_mweb_pegout": "죽다", "litecoin_mweb_pegout": "죽다",
"litecoin_mweb_scanning": "mweb 스캔", "litecoin_mweb_scanning": "mweb 스캔",

View file

@ -295,6 +295,7 @@
"expiresOn": "သက်တမ်းကုန်သည်။", "expiresOn": "သက်တမ်းကုန်သည်။",
"expiry_and_validity": "သက်တမ်းကုန်ဆုံးခြင်းနှင့်တရားဝင်မှု", "expiry_and_validity": "သက်တမ်းကုန်ဆုံးခြင်းနှင့်တရားဝင်မှု",
"export_backup": "အရန်ကူးထုတ်ရန်", "export_backup": "အရန်ကူးထုတ်ရန်",
"export_logs": "ပို့ကုန်မှတ်တမ်းများ",
"extra_id": "အပို ID-", "extra_id": "အပို ID-",
"extracted_address_content": "သင်သည် \n${recipient_name} သို့ ရန်ပုံငွေများ ပေးပို့ပါမည်", "extracted_address_content": "သင်သည် \n${recipient_name} သို့ ရန်ပုံငွေများ ပေးပို့ပါမည်",
"failed_authentication": "အထောက်အထားစိစစ်ခြင်း မအောင်မြင်ပါ။. ${state_error}", "failed_authentication": "အထောက်အထားစိစစ်ခြင်း မအောင်မြင်ပါ။. ${state_error}",
@ -371,7 +372,10 @@
"litecoin_mweb_description": "Mweb သည် Protocol အသစ်ဖြစ်ပြီး LitCoin သို့ပိုမိုဈေးချိုသာသော, စျေးသက်သက်သာသာသုံးခြင်းနှင့်ပိုမိုများပြားသောပုဂ္ဂလိကငွေပို့ဆောင်မှုများကိုဖြစ်ပေါ်စေသည်", "litecoin_mweb_description": "Mweb သည် Protocol အသစ်ဖြစ်ပြီး LitCoin သို့ပိုမိုဈေးချိုသာသော, စျေးသက်သက်သာသာသုံးခြင်းနှင့်ပိုမိုများပြားသောပုဂ္ဂလိကငွေပို့ဆောင်မှုများကိုဖြစ်ပေါ်စေသည်",
"litecoin_mweb_dismiss": "ထုတ်ပစ်", "litecoin_mweb_dismiss": "ထုတ်ပစ်",
"litecoin_mweb_display_card": "MweB ကဒ်ကိုပြပါ", "litecoin_mweb_display_card": "MweB ကဒ်ကိုပြပါ",
"litecoin_mweb_enable": "mweb enable",
"litecoin_mweb_enable_later": "သင် MweB ကို display settings အောက်ရှိ ထပ်မံ. ခွင့်ပြုရန်ရွေးချယ်နိုင်သည်။", "litecoin_mweb_enable_later": "သင် MweB ကို display settings အောက်ရှိ ထပ်မံ. ခွင့်ပြုရန်ရွေးချယ်နိုင်သည်။",
"litecoin_mweb_logs": "Mweb မှတ်တမ်းများ",
"litecoin_mweb_node": "mweb node ကို",
"litecoin_mweb_pegin": "တံစို့", "litecoin_mweb_pegin": "တံစို့",
"litecoin_mweb_pegout": "တံစို့", "litecoin_mweb_pegout": "တံစို့",
"litecoin_mweb_scanning": "mweb scanning", "litecoin_mweb_scanning": "mweb scanning",

View file

@ -295,6 +295,7 @@
"expiresOn": "Verloopt op", "expiresOn": "Verloopt op",
"expiry_and_validity": "Vervallen en geldigheid", "expiry_and_validity": "Vervallen en geldigheid",
"export_backup": "Back-up exporteren", "export_backup": "Back-up exporteren",
"export_logs": "Exporteer logboeken",
"extra_id": "Extra ID:", "extra_id": "Extra ID:",
"extracted_address_content": "U stuurt geld naar\n${recipient_name}", "extracted_address_content": "U stuurt geld naar\n${recipient_name}",
"failed_authentication": "Mislukte authenticatie. ${state_error}", "failed_authentication": "Mislukte authenticatie. ${state_error}",
@ -371,7 +372,10 @@
"litecoin_mweb_description": "MWEB is een nieuw protocol dat snellere, goedkopere en meer privé -transacties naar Litecoin brengt", "litecoin_mweb_description": "MWEB is een nieuw protocol dat snellere, goedkopere en meer privé -transacties naar Litecoin brengt",
"litecoin_mweb_dismiss": "Afwijzen", "litecoin_mweb_dismiss": "Afwijzen",
"litecoin_mweb_display_card": "Toon MWEB -kaart", "litecoin_mweb_display_card": "Toon MWEB -kaart",
"litecoin_mweb_enable": "MWEB inschakelen",
"litecoin_mweb_enable_later": "U kunt ervoor kiezen om MWeb opnieuw in te schakelen onder weergave -instellingen.", "litecoin_mweb_enable_later": "U kunt ervoor kiezen om MWeb opnieuw in te schakelen onder weergave -instellingen.",
"litecoin_mweb_logs": "MWEB -logboeken",
"litecoin_mweb_node": "MWEB -knooppunt",
"litecoin_mweb_pegin": "Vastmaken", "litecoin_mweb_pegin": "Vastmaken",
"litecoin_mweb_pegout": "Uithakken", "litecoin_mweb_pegout": "Uithakken",
"litecoin_mweb_scanning": "MWEB -scanning", "litecoin_mweb_scanning": "MWEB -scanning",

View file

@ -295,6 +295,7 @@
"expiresOn": "Upływa w dniu", "expiresOn": "Upływa w dniu",
"expiry_and_validity": "Wygaśnięcie i ważność", "expiry_and_validity": "Wygaśnięcie i ważność",
"export_backup": "Eksportuj kopię zapasową", "export_backup": "Eksportuj kopię zapasową",
"export_logs": "Dzienniki eksportu",
"extra_id": "Dodatkowy ID:", "extra_id": "Dodatkowy ID:",
"extracted_address_content": "Wysyłasz środki na\n${recipient_name}", "extracted_address_content": "Wysyłasz środki na\n${recipient_name}",
"failed_authentication": "Nieudane uwierzytelnienie. ${state_error}", "failed_authentication": "Nieudane uwierzytelnienie. ${state_error}",
@ -371,7 +372,10 @@
"litecoin_mweb_description": "MWEB to nowy protokół, który przynosi szybciej, tańsze i bardziej prywatne transakcje do Litecoin", "litecoin_mweb_description": "MWEB to nowy protokół, który przynosi szybciej, tańsze i bardziej prywatne transakcje do Litecoin",
"litecoin_mweb_dismiss": "Odrzucać", "litecoin_mweb_dismiss": "Odrzucać",
"litecoin_mweb_display_card": "Pokaż kartę MWEB", "litecoin_mweb_display_card": "Pokaż kartę MWEB",
"litecoin_mweb_enable": "Włącz MWEB",
"litecoin_mweb_enable_later": "Możesz ponownie włączyć MWEB w ustawieniach wyświetlania.", "litecoin_mweb_enable_later": "Możesz ponownie włączyć MWEB w ustawieniach wyświetlania.",
"litecoin_mweb_logs": "Dzienniki MWEB",
"litecoin_mweb_node": "Węzeł MWEB",
"litecoin_mweb_pegin": "Kołek", "litecoin_mweb_pegin": "Kołek",
"litecoin_mweb_pegout": "Palikować", "litecoin_mweb_pegout": "Palikować",
"litecoin_mweb_scanning": "Skanowanie MWEB", "litecoin_mweb_scanning": "Skanowanie MWEB",

View file

@ -295,6 +295,7 @@
"expiresOn": "Expira em", "expiresOn": "Expira em",
"expiry_and_validity": "Expiração e validade", "expiry_and_validity": "Expiração e validade",
"export_backup": "Backup de exportação", "export_backup": "Backup de exportação",
"export_logs": "Exportar logs",
"extra_id": "ID extra:", "extra_id": "ID extra:",
"extracted_address_content": "Você enviará fundos para\n${recipient_name}", "extracted_address_content": "Você enviará fundos para\n${recipient_name}",
"failed_authentication": "Falha na autenticação. ${state_error}", "failed_authentication": "Falha na autenticação. ${state_error}",
@ -371,7 +372,10 @@
"litecoin_mweb_description": "MWEB é um novo protocolo que traz transações mais rápidas, baratas e mais privadas para o Litecoin", "litecoin_mweb_description": "MWEB é um novo protocolo que traz transações mais rápidas, baratas e mais privadas para o Litecoin",
"litecoin_mweb_dismiss": "Liberar", "litecoin_mweb_dismiss": "Liberar",
"litecoin_mweb_display_card": "Mostre o cartão MWEB", "litecoin_mweb_display_card": "Mostre o cartão MWEB",
"litecoin_mweb_enable": "Ativar Mweb",
"litecoin_mweb_enable_later": "Você pode optar por ativar o MWEB novamente em Configurações de exibição.", "litecoin_mweb_enable_later": "Você pode optar por ativar o MWEB novamente em Configurações de exibição.",
"litecoin_mweb_logs": "Logs MWeb",
"litecoin_mweb_node": "Nó MWeb",
"litecoin_mweb_pegin": "Peg in", "litecoin_mweb_pegin": "Peg in",
"litecoin_mweb_pegout": "Peg fora", "litecoin_mweb_pegout": "Peg fora",
"litecoin_mweb_scanning": "MWEB Scanning", "litecoin_mweb_scanning": "MWEB Scanning",

View file

@ -295,6 +295,7 @@
"expiresOn": "Годен до", "expiresOn": "Годен до",
"expiry_and_validity": "Истечение и достоверность", "expiry_and_validity": "Истечение и достоверность",
"export_backup": "Экспорт резервной копии", "export_backup": "Экспорт резервной копии",
"export_logs": "Экспортные журналы",
"extra_id": "Дополнительный ID:", "extra_id": "Дополнительный ID:",
"extracted_address_content": "Вы будете отправлять средства\n${recipient_name}", "extracted_address_content": "Вы будете отправлять средства\n${recipient_name}",
"failed_authentication": "Ошибка аутентификации. ${state_error}", "failed_authentication": "Ошибка аутентификации. ${state_error}",
@ -371,7 +372,10 @@
"litecoin_mweb_description": "MWEB - это новый протокол, который приносит быстрее, дешевле и более частные транзакции в Litecoin", "litecoin_mweb_description": "MWEB - это новый протокол, который приносит быстрее, дешевле и более частные транзакции в Litecoin",
"litecoin_mweb_dismiss": "Увольнять", "litecoin_mweb_dismiss": "Увольнять",
"litecoin_mweb_display_card": "Показать карту MWEB", "litecoin_mweb_display_card": "Показать карту MWEB",
"litecoin_mweb_enable": "Включить MWEB",
"litecoin_mweb_enable_later": "Вы можете снова включить MWEB в настройках отображения.", "litecoin_mweb_enable_later": "Вы можете снова включить MWEB в настройках отображения.",
"litecoin_mweb_logs": "MWEB журналы",
"litecoin_mweb_node": "Узел MWEB",
"litecoin_mweb_pegin": "Внедрять", "litecoin_mweb_pegin": "Внедрять",
"litecoin_mweb_pegout": "Выкрикивать", "litecoin_mweb_pegout": "Выкрикивать",
"litecoin_mweb_scanning": "MWEB сканирование", "litecoin_mweb_scanning": "MWEB сканирование",

View file

@ -295,6 +295,7 @@
"expiresOn": "หมดอายุวันที่", "expiresOn": "หมดอายุวันที่",
"expiry_and_validity": "หมดอายุและถูกต้อง", "expiry_and_validity": "หมดอายุและถูกต้อง",
"export_backup": "ส่งออกข้อมูลสำรอง", "export_backup": "ส่งออกข้อมูลสำรอง",
"export_logs": "บันทึกการส่งออก",
"extra_id": "ไอดีเพิ่มเติม:", "extra_id": "ไอดีเพิ่มเติม:",
"extracted_address_content": "คุณกำลังจะส่งเงินไปยัง\n${recipient_name}", "extracted_address_content": "คุณกำลังจะส่งเงินไปยัง\n${recipient_name}",
"failed_authentication": "การยืนยันสิทธิ์ล้มเหลว ${state_error}", "failed_authentication": "การยืนยันสิทธิ์ล้มเหลว ${state_error}",
@ -371,7 +372,10 @@
"litecoin_mweb_description": "MWEB เป็นโปรโตคอลใหม่ที่นำการทำธุรกรรมที่เร็วกว่าราคาถูกกว่าและเป็นส่วนตัวมากขึ้นไปยัง Litecoin", "litecoin_mweb_description": "MWEB เป็นโปรโตคอลใหม่ที่นำการทำธุรกรรมที่เร็วกว่าราคาถูกกว่าและเป็นส่วนตัวมากขึ้นไปยัง Litecoin",
"litecoin_mweb_dismiss": "อนุญาตให้ออกไป", "litecoin_mweb_dismiss": "อนุญาตให้ออกไป",
"litecoin_mweb_display_card": "แสดงการ์ด mweb", "litecoin_mweb_display_card": "แสดงการ์ด mweb",
"litecoin_mweb_enable": "เปิดใช้งาน mweb",
"litecoin_mweb_enable_later": "คุณสามารถเลือกเปิดใช้งาน MWEB อีกครั้งภายใต้การตั้งค่าการแสดงผล", "litecoin_mweb_enable_later": "คุณสามารถเลือกเปิดใช้งาน MWEB อีกครั้งภายใต้การตั้งค่าการแสดงผล",
"litecoin_mweb_logs": "บันทึก MWEB",
"litecoin_mweb_node": "โหนด MWEB",
"litecoin_mweb_pegin": "หมุด", "litecoin_mweb_pegin": "หมุด",
"litecoin_mweb_pegout": "ตรึง", "litecoin_mweb_pegout": "ตรึง",
"litecoin_mweb_scanning": "การสแกน MWEB", "litecoin_mweb_scanning": "การสแกน MWEB",

View file

@ -295,6 +295,7 @@
"expiresOn": "Mag-e-expire sa", "expiresOn": "Mag-e-expire sa",
"expiry_and_validity": "Pag-expire at Bisa", "expiry_and_validity": "Pag-expire at Bisa",
"export_backup": "I-export ang backup", "export_backup": "I-export ang backup",
"export_logs": "Mga log ng pag -export",
"extra_id": "Dagdag na ID:", "extra_id": "Dagdag na ID:",
"extracted_address_content": "Magpapadala ka ng pondo sa\n${recipient_name}", "extracted_address_content": "Magpapadala ka ng pondo sa\n${recipient_name}",
"failed_authentication": "Nabigo ang pagpapatunay. ${state_error}", "failed_authentication": "Nabigo ang pagpapatunay. ${state_error}",
@ -371,7 +372,10 @@
"litecoin_mweb_description": "Ang MWeb ay isang bagong protocol na nagdadala ng mas mabilis, mas mura, at mas maraming pribadong mga transaksyon sa Litecoin", "litecoin_mweb_description": "Ang MWeb ay isang bagong protocol na nagdadala ng mas mabilis, mas mura, at mas maraming pribadong mga transaksyon sa Litecoin",
"litecoin_mweb_dismiss": "Tanggalin", "litecoin_mweb_dismiss": "Tanggalin",
"litecoin_mweb_display_card": "Ipakita ang MWEB Card", "litecoin_mweb_display_card": "Ipakita ang MWEB Card",
"litecoin_mweb_enable": "Paganahin ang MWeb",
"litecoin_mweb_enable_later": "Maaari kang pumili upang paganahin muli ang MWeb sa ilalim ng mga setting ng pagpapakita.", "litecoin_mweb_enable_later": "Maaari kang pumili upang paganahin muli ang MWeb sa ilalim ng mga setting ng pagpapakita.",
"litecoin_mweb_logs": "MWEB log",
"litecoin_mweb_node": "Mweb node",
"litecoin_mweb_pegin": "Peg in", "litecoin_mweb_pegin": "Peg in",
"litecoin_mweb_pegout": "Peg out", "litecoin_mweb_pegout": "Peg out",
"litecoin_mweb_scanning": "Pag -scan ng Mweb", "litecoin_mweb_scanning": "Pag -scan ng Mweb",

View file

@ -295,6 +295,7 @@
"expiresOn": "Tarihinde sona eriyor", "expiresOn": "Tarihinde sona eriyor",
"expiry_and_validity": "Sona erme ve geçerlilik", "expiry_and_validity": "Sona erme ve geçerlilik",
"export_backup": "Yedeği dışa aktar", "export_backup": "Yedeği dışa aktar",
"export_logs": "Dışa aktarma günlükleri",
"extra_id": "Ekstra ID:", "extra_id": "Ekstra ID:",
"extracted_address_content": "Parayı buraya gönderceksin:\n${recipient_name}", "extracted_address_content": "Parayı buraya gönderceksin:\n${recipient_name}",
"failed_authentication": "Doğrulama başarısız oldu. ${state_error}", "failed_authentication": "Doğrulama başarısız oldu. ${state_error}",
@ -371,7 +372,10 @@
"litecoin_mweb_description": "MWEB, Litecoin'e daha hızlı, daha ucuz ve daha fazla özel işlem getiren yeni bir protokoldür", "litecoin_mweb_description": "MWEB, Litecoin'e daha hızlı, daha ucuz ve daha fazla özel işlem getiren yeni bir protokoldür",
"litecoin_mweb_dismiss": "Azletmek", "litecoin_mweb_dismiss": "Azletmek",
"litecoin_mweb_display_card": "MWEB kartını göster", "litecoin_mweb_display_card": "MWEB kartını göster",
"litecoin_mweb_enable": "MWEB'i etkinleştir",
"litecoin_mweb_enable_later": "Ekran ayarlarının altında MWEB'yi tekrar etkinleştirmeyi seçebilirsiniz.", "litecoin_mweb_enable_later": "Ekran ayarlarının altında MWEB'yi tekrar etkinleştirmeyi seçebilirsiniz.",
"litecoin_mweb_logs": "MWEB günlükleri",
"litecoin_mweb_node": "MWEB düğümü",
"litecoin_mweb_pegin": "Takılmak", "litecoin_mweb_pegin": "Takılmak",
"litecoin_mweb_pegout": "Çiğnemek", "litecoin_mweb_pegout": "Çiğnemek",
"litecoin_mweb_scanning": "MWEB taraması", "litecoin_mweb_scanning": "MWEB taraması",

View file

@ -295,6 +295,7 @@
"expiresOn": "Термін дії закінчується", "expiresOn": "Термін дії закінчується",
"expiry_and_validity": "Закінчення та обгрунтованість", "expiry_and_validity": "Закінчення та обгрунтованість",
"export_backup": "Експортувати резервну копію", "export_backup": "Експортувати резервну копію",
"export_logs": "Експортні журнали",
"extra_id": "Додатковий ID:", "extra_id": "Додатковий ID:",
"extracted_address_content": "Ви будете відправляти кошти\n${recipient_name}", "extracted_address_content": "Ви будете відправляти кошти\n${recipient_name}",
"failed_authentication": "Помилка аутентифікації. ${state_error}", "failed_authentication": "Помилка аутентифікації. ${state_error}",
@ -371,7 +372,10 @@
"litecoin_mweb_description": "MWEB - це новий протокол, який приносить швидкі, дешевші та більш приватні транзакції Litecoin", "litecoin_mweb_description": "MWEB - це новий протокол, який приносить швидкі, дешевші та більш приватні транзакції Litecoin",
"litecoin_mweb_dismiss": "Звільнити", "litecoin_mweb_dismiss": "Звільнити",
"litecoin_mweb_display_card": "Показати карту MWeb", "litecoin_mweb_display_card": "Показати карту MWeb",
"litecoin_mweb_enable": "Увімкнути mweb",
"litecoin_mweb_enable_later": "Ви можете знову ввімкнути MWEB в налаштуваннях дисплея.", "litecoin_mweb_enable_later": "Ви можете знову ввімкнути MWEB в налаштуваннях дисплея.",
"litecoin_mweb_logs": "Журнали MWeb",
"litecoin_mweb_node": "Вузол MWeb",
"litecoin_mweb_pegin": "Подякувати", "litecoin_mweb_pegin": "Подякувати",
"litecoin_mweb_pegout": "Подякувати", "litecoin_mweb_pegout": "Подякувати",
"litecoin_mweb_scanning": "Сканування Mweb", "litecoin_mweb_scanning": "Сканування Mweb",

View file

@ -295,6 +295,7 @@
"expiresOn": "ﺩﺎﻌﯿﻣ ﯽﻣﺎﺘﺘﺧﺍ", "expiresOn": "ﺩﺎﻌﯿﻣ ﯽﻣﺎﺘﺘﺧﺍ",
"expiry_and_validity": "میعاد ختم اور صداقت", "expiry_and_validity": "میعاد ختم اور صداقت",
"export_backup": "بیک اپ برآمد کریں۔", "export_backup": "بیک اپ برآمد کریں۔",
"export_logs": "نوشتہ جات برآمد کریں",
"extra_id": "اضافی ID:", "extra_id": "اضافی ID:",
"extracted_address_content": "آپ فنڈز بھیج رہے ہوں گے\n${recipient_name}", "extracted_address_content": "آپ فنڈز بھیج رہے ہوں گے\n${recipient_name}",
"failed_authentication": "ناکام تصدیق۔ ${state_error}", "failed_authentication": "ناکام تصدیق۔ ${state_error}",
@ -371,7 +372,10 @@
"litecoin_mweb_description": "MWEB ایک نیا پروٹوکول ہے جو لیٹیکوئن میں تیز ، سستا اور زیادہ نجی لین دین لاتا ہے", "litecoin_mweb_description": "MWEB ایک نیا پروٹوکول ہے جو لیٹیکوئن میں تیز ، سستا اور زیادہ نجی لین دین لاتا ہے",
"litecoin_mweb_dismiss": "خارج", "litecoin_mweb_dismiss": "خارج",
"litecoin_mweb_display_card": "MWEB کارڈ دکھائیں", "litecoin_mweb_display_card": "MWEB کارڈ دکھائیں",
"litecoin_mweb_enable": "MWEB کو فعال کریں",
"litecoin_mweb_enable_later": "آپ ڈسپلے کی ترتیبات کے تحت MWEB کو دوبارہ فعال کرنے کا انتخاب کرسکتے ہیں۔", "litecoin_mweb_enable_later": "آپ ڈسپلے کی ترتیبات کے تحت MWEB کو دوبارہ فعال کرنے کا انتخاب کرسکتے ہیں۔",
"litecoin_mweb_logs": "MWEB لاگز",
"litecoin_mweb_node": "MWEB نوڈ",
"litecoin_mweb_pegin": "پیگ میں", "litecoin_mweb_pegin": "پیگ میں",
"litecoin_mweb_pegout": "پیگ آؤٹ", "litecoin_mweb_pegout": "پیگ آؤٹ",
"litecoin_mweb_scanning": "MWEB اسکیننگ", "litecoin_mweb_scanning": "MWEB اسکیننگ",

View file

@ -296,6 +296,7 @@
"expiresOn": "Hết hạn vào", "expiresOn": "Hết hạn vào",
"expiry_and_validity": "Hạn và hiệu lực", "expiry_and_validity": "Hạn và hiệu lực",
"export_backup": "Xuất sao lưu", "export_backup": "Xuất sao lưu",
"export_logs": "Nhật ký xuất khẩu",
"extra_id": "ID bổ sung:", "extra_id": "ID bổ sung:",
"extracted_address_content": "Bạn sẽ gửi tiền cho\n${recipient_name}", "extracted_address_content": "Bạn sẽ gửi tiền cho\n${recipient_name}",
"failed_authentication": "Xác thực không thành công. ${state_error}", "failed_authentication": "Xác thực không thành công. ${state_error}",
@ -368,7 +369,10 @@
"light_theme": "Chủ đề sáng", "light_theme": "Chủ đề sáng",
"litecoin_mweb_description": "MWEB là một giao thức mới mang lại các giao dịch nhanh hơn, rẻ hơn và riêng tư hơn cho Litecoin", "litecoin_mweb_description": "MWEB là một giao thức mới mang lại các giao dịch nhanh hơn, rẻ hơn và riêng tư hơn cho Litecoin",
"litecoin_mweb_dismiss": "Miễn nhiệm", "litecoin_mweb_dismiss": "Miễn nhiệm",
"litecoin_mweb_enable": "Bật MWEB",
"litecoin_mweb_enable_later": "Bạn có thể chọn bật lại MWEB trong cài đặt hiển thị.", "litecoin_mweb_enable_later": "Bạn có thể chọn bật lại MWEB trong cài đặt hiển thị.",
"litecoin_mweb_logs": "Nhật ký MWEB",
"litecoin_mweb_node": "Nút MWEB",
"litecoin_mweb_pegin": "Chốt vào", "litecoin_mweb_pegin": "Chốt vào",
"litecoin_mweb_pegout": "Chốt ra", "litecoin_mweb_pegout": "Chốt ra",
"live_fee_rates": "Tỷ lệ phí hiện tại qua API", "live_fee_rates": "Tỷ lệ phí hiện tại qua API",

View file

@ -296,6 +296,7 @@
"expiresOn": "Ipari lori", "expiresOn": "Ipari lori",
"expiry_and_validity": "Ipari ati idaniloju", "expiry_and_validity": "Ipari ati idaniloju",
"export_backup": "Sún ẹ̀dà nípamọ́ síta", "export_backup": "Sún ẹ̀dà nípamọ́ síta",
"export_logs": "Wọle si okeere",
"extra_id": "Àmì ìdánimọ̀ tó fikún:", "extra_id": "Àmì ìdánimọ̀ tó fikún:",
"extracted_address_content": "Ẹ máa máa fi owó ránṣẹ́ sí\n${recipient_name}", "extracted_address_content": "Ẹ máa máa fi owó ránṣẹ́ sí\n${recipient_name}",
"failed_authentication": "Ìfẹ̀rílàdí pipòfo. ${state_error}", "failed_authentication": "Ìfẹ̀rílàdí pipòfo. ${state_error}",
@ -372,7 +373,10 @@
"litecoin_mweb_description": "Mweb jẹ ilana ilana tuntun ti o mu iyara wa yiyara, din owo, ati awọn iṣowo ikọkọ diẹ sii si Livcoin", "litecoin_mweb_description": "Mweb jẹ ilana ilana tuntun ti o mu iyara wa yiyara, din owo, ati awọn iṣowo ikọkọ diẹ sii si Livcoin",
"litecoin_mweb_dismiss": "Tuka", "litecoin_mweb_dismiss": "Tuka",
"litecoin_mweb_display_card": "Fihan kaadi Mweb", "litecoin_mweb_display_card": "Fihan kaadi Mweb",
"litecoin_mweb_enable": "Mu mweb",
"litecoin_mweb_enable_later": "O le yan lati ṣiṣẹ Mweb lẹẹkansi labẹ awọn eto ifihan.", "litecoin_mweb_enable_later": "O le yan lati ṣiṣẹ Mweb lẹẹkansi labẹ awọn eto ifihan.",
"litecoin_mweb_logs": "MTweb logs",
"litecoin_mweb_node": "Alweb joko",
"litecoin_mweb_pegin": "Peg in", "litecoin_mweb_pegin": "Peg in",
"litecoin_mweb_pegout": "Peg jade", "litecoin_mweb_pegout": "Peg jade",
"litecoin_mweb_scanning": "Mweb scanning", "litecoin_mweb_scanning": "Mweb scanning",

View file

@ -295,6 +295,7 @@
"expiresOn": "到期", "expiresOn": "到期",
"expiry_and_validity": "到期和有效性", "expiry_and_validity": "到期和有效性",
"export_backup": "导出备份", "export_backup": "导出备份",
"export_logs": "导出日志",
"extra_id": "额外ID:", "extra_id": "额外ID:",
"extracted_address_content": "您将汇款至\n${recipient_name}", "extracted_address_content": "您将汇款至\n${recipient_name}",
"failed_authentication": "身份验证失败. ${state_error}", "failed_authentication": "身份验证失败. ${state_error}",
@ -371,7 +372,10 @@
"litecoin_mweb_description": "MWEB是一项新协议它将更快更便宜和更多的私人交易带给Litecoin", "litecoin_mweb_description": "MWEB是一项新协议它将更快更便宜和更多的私人交易带给Litecoin",
"litecoin_mweb_dismiss": "解雇", "litecoin_mweb_dismiss": "解雇",
"litecoin_mweb_display_card": "显示MWEB卡", "litecoin_mweb_display_card": "显示MWEB卡",
"litecoin_mweb_enable": "启用MWEB",
"litecoin_mweb_enable_later": "您可以选择在显示设置下再次启用MWEB。", "litecoin_mweb_enable_later": "您可以选择在显示设置下再次启用MWEB。",
"litecoin_mweb_logs": "MWEB日志",
"litecoin_mweb_node": "MWEB节点",
"litecoin_mweb_pegin": "钉进", "litecoin_mweb_pegin": "钉进",
"litecoin_mweb_pegout": "昏倒", "litecoin_mweb_pegout": "昏倒",
"litecoin_mweb_scanning": "MWEB扫描", "litecoin_mweb_scanning": "MWEB扫描",