Merge branch 'cake-tech:dev' into dev

This commit is contained in:
BSN ∞/21M
2026-05-29 07:56:49 +02:00
committed by GitHub
42 changed files with 535 additions and 231 deletions
+75
View File
@@ -173,6 +173,7 @@ class ElectrumClient {
if (isJSONStringCorrect(unterminatedString)) {
final response = json.decode(unterminatedString);
_handleResponse(response);
// unterminatedString = null;
unterminatedString = '';
}
@@ -340,6 +341,80 @@ class ElectrumClient {
return historyMap;
}
Future<Map<String, List<Map<String, dynamic>>>> getBatchUnspent(
List<String> scriptHashes, {
int timeout = 10000,
}) async {
final paramsList = scriptHashes.map((h) => <Object>[h]).toList(growable: false);
final batchResults = await callBatchWithTimeout(
method: 'blockchain.scripthash.listunspent',
paramsList: paramsList,
timeout: timeout,
);
final unspentMap = <String, List<Map<String, dynamic>>>{};
for (int i = 0; i < scriptHashes.length; i++) {
final sh = scriptHashes[i];
if (i >= batchResults.length) {
unspentMap[sh] = const [];
continue;
}
final result = batchResults[i];
if (result is List) {
unspentMap[sh] = result
.whereType<Map<dynamic, dynamic>>()
.map((m) => m.map((k, v) => MapEntry(k.toString(), v)))
.cast<Map<String, dynamic>>()
.toList();
} else {
unspentMap[sh] = const [];
}
}
return unspentMap;
}
Future<Map<String, Map<String, dynamic>>> getBatchBalance(
List<String> scriptHashes, {
int timeout = 10000,
}) async {
final paramsList = scriptHashes.map((h) => <Object>[h]).toList(growable: false);
final batchResults = await callBatchWithTimeout(
method: 'blockchain.scripthash.get_balance',
paramsList: paramsList,
timeout: timeout,
);
final balanceMap = <String, Map<String, dynamic>>{};
for (int i = 0; i < scriptHashes.length; i++) {
final sh = scriptHashes[i];
if (i >= batchResults.length) {
balanceMap[sh] = <String, dynamic>{};
continue;
}
final result = batchResults[i];
if (result is Map<String, dynamic>) {
balanceMap[sh] = result;
} else if (result is Map) {
balanceMap[sh] = Map<String, dynamic>.from(result);
} else {
balanceMap[sh] = <String, dynamic>{};
}
}
return balanceMap;
}
Future<Map<String, Map<String, dynamic>>> getBatchTransactionVerbose(
List<String> hashes, {
int timeout = 10000,
+318 -156
View File
@@ -4,6 +4,7 @@ import 'dart:io';
import 'dart:isolate';
import 'package:bitcoin_base/bitcoin_base.dart';
import 'package:cw_bitcoin/lightning/lightning_wallet.dart';
import 'package:cw_core/hardware/hardware_wallet_service.dart';
import 'package:cw_core/root_dir.dart';
import 'package:cw_core/utils/proxy_wrapper.dart';
@@ -76,7 +77,7 @@ abstract class ElectrumWalletBase
ElectrumBalance? initialBalance,
CryptoCurrency? currency,
bool? alwaysScan,
this.useLightning = true,
bool useLightning = true,
}) : _masterHD = getMasterHD(seedBytes, network, walletInfo.hardwareWalletType),
accountHD = getAccountHDWallet(
currency, network, seedBytes, xpub, derivationInfo, walletInfo.hardwareWalletType),
@@ -102,6 +103,7 @@ abstract class ElectrumWalletBase
this.unspentCoinsInfo = unspentCoinsInfo,
this.isTestnet = !network.isMainnet,
this._mnemonic = mnemonic,
_useLightning = useLightning,
super(walletInfo, derivationInfo) {
this.electrumClient = electrumClient ?? electrum.ElectrumClient();
this.walletInfo = walletInfo;
@@ -305,8 +307,13 @@ abstract class ElectrumWalletBase
@observable
bool? alwaysScan;
@computed
bool get useLightning => _useLightning && LightningWallet.isAvailable;
set useLightning(bool val) => _useLightning = val && LightningWallet.isAvailable;
@observable
bool useLightning;
bool _useLightning;
final Bip32Slip10Secp256k1? _masterHD;
final Bip32Slip10Secp256k1 accountHD;
@@ -360,6 +367,8 @@ abstract class ElectrumWalletBase
String get xpub => accountHD.publicKey.toExtended;
bool get shouldUseBatchFetching => useBatchForHistory && _isBatchSupported == true;
@override
String? get seed => _mnemonic;
@@ -701,7 +710,7 @@ abstract class ElectrumWalletBase
}
await subscribeForUpdates();
await _checkIfBatchSupported();
await checkIfBatchSupported();
await updateTransactions();
await updateAllUnspents();
@@ -1786,18 +1795,21 @@ abstract class ElectrumWalletBase
}
// Set the balance of all non-silent payment and non-mweb addresses to 0 before updating
walletAddresses.allAddresses
.where((element) => element.type != SegwitAddresType.mweb)
.forEach((addr) {
if (addr is! BitcoinSilentPaymentAddressRecord) addr.balance = 0;
});
final addressFutures = walletAddresses.allAddresses
final targetAddresses = walletAddresses.allAddresses
.where((element) => element.type != SegwitAddresType.mweb)
.map((address) => fetchUnspent(address))
.toList();
final results = await Future.wait(addressFutures);
for (final addr in targetAddresses) {
if (addr is! BitcoinSilentPaymentAddressRecord) {
addr.balance = 0;
}
}
final results = shouldUseBatchFetching
? await _fetchUnspentsBatch(targetAddresses)
: await _fetchUnspentsRegular(targetAddresses);
final failedCount = results.where((result) => result == null).length;
if (failedCount == 0) {
@@ -1829,6 +1841,74 @@ abstract class ElectrumWalletBase
await _refreshUnspentCoinsInfo();
}
Future<List<List<BitcoinUnspent>?>> _fetchUnspentsRegular(
List<BitcoinAddressRecord> addresses,
) async {
final addressFutures = addresses.map((address) => fetchUnspent(address)).toList();
return Future.wait(addressFutures);
}
Future<List<List<BitcoinUnspent>?>> _fetchUnspentsBatch(
List<BitcoinAddressRecord> addresses,
) async {
final byScriptHash = <String, BitcoinAddressRecord>{
for (final address in addresses) address.getScriptHash(network): address,
};
final scriptHashes = byScriptHash.keys.toList();
try {
final unspentByScriptHash =
await _processChunksToMap<String, String, List<Map<String, dynamic>>>(
items: scriptHashes,
chunkSize: addressHistoryChunkSize,
processChunk: _getListUnspentBatch,
);
final txHashes = <String>{};
final coinsByScriptHash = <String, List<BitcoinUnspent>>{};
for (final entry in unspentByScriptHash.entries) {
final addressRecord = byScriptHash[entry.key];
if (addressRecord == null) continue;
final coins = <BitcoinUnspent>[];
for (final unspent in entry.value) {
final coin = BitcoinUnspent.fromJSON(addressRecord, unspent);
coin.isChange = addressRecord.isHidden;
coins.add(coin);
txHashes.add(coin.hash);
}
coinsByScriptHash[entry.key] = coins;
}
final txInfoByHash = await fetchTransactionInfoBatch(
hashes: txHashes.toList(),
retryOnFailure: true,
retryDelay: const Duration(seconds: 1),
);
for (final coins in coinsByScriptHash.values) {
for (final coin in coins) {
final tx = txInfoByHash[coin.hash];
coin.confirmations = tx?.confirmations;
coin.isPegOut = tx?.isHogEx;
}
}
return addresses.map((address) {
final scriptHash = address.getScriptHash(network);
return coinsByScriptHash[scriptHash] ?? <BitcoinUnspent>[];
}).toList();
} catch (e) {
printV('fetchUnspentsBatch failed: $e');
return List<List<BitcoinUnspent>?>.filled(addresses.length, null);
}
}
List<BitcoinUnspent> handleFailedUtxoFetch({
required int failedCount,
required List<BitcoinUnspent> previousUnspentCoins,
@@ -2388,25 +2468,30 @@ abstract class ElectrumWalletBase
@override
Future<Map<String, ElectrumTransactionInfo>> fetchTransactions() async {
try {
final Map<String, ElectrumTransactionInfo> historiesWithDetails = {};
final shouldUseBatchForHistory = useBatchForHistory && _isBatchSupported == true;
final Map<String, ElectrumTransactionInfo> historiesWithDetails = {};;
printV('[BATCH_TEST] Fetching transactions with batch: $shouldUseBatchForHistory');
printV('[BATCH_TEST] Fetching transactions with batch: $shouldUseBatchFetching');
if (type == WalletType.bitcoin) {
await Future.wait(BITCOIN_ADDRESS_TYPES.map((type) => shouldUseBatchForHistory
await Future.wait(BITCOIN_ADDRESS_TYPES.map((type) => shouldUseBatchFetching
? fetchTransactionsForAddressTypeBatch(historiesWithDetails, type)
: fetchTransactionsForAddressType(historiesWithDetails, type)));
} else if (type == WalletType.bitcoinCash) {
await Future.wait(BITCOIN_CASH_ADDRESS_TYPES
.map((type) => fetchTransactionsForAddressType(historiesWithDetails, type)));
.map((type) => shouldUseBatchFetching
? fetchTransactionsForAddressTypeBatch(historiesWithDetails, type)
: fetchTransactionsForAddressType(historiesWithDetails, type)));
} else if (type == WalletType.litecoin) {
await Future.wait(LITECOIN_ADDRESS_TYPES
.where((type) => type != SegwitAddresType.mweb)
.map((type) => fetchTransactionsForAddressType(historiesWithDetails, type)));
.map((type) => shouldUseBatchFetching
? fetchTransactionsForAddressTypeBatch(historiesWithDetails, type)
: fetchTransactionsForAddressType(historiesWithDetails, type)));
} else if (type == WalletType.dogecoin) {
await Future.wait(DOGECOIN_ADDRESS_TYPES
.map((type) => fetchTransactionsForAddressType(historiesWithDetails, type)));
.map((type) => shouldUseBatchFetching
? fetchTransactionsForAddressTypeBatch(historiesWithDetails, type)
: fetchTransactionsForAddressType(historiesWithDetails, type)));
}
transactionHistory.transactions.values.forEach((tx) async {
@@ -2450,13 +2535,13 @@ abstract class ElectrumWalletBase
historiesWithDetails.addAll(history);
final matchedAddresses = addressRecord.isHidden ? hiddenAddresses : receiveAddresses;
final isUsedAddressUnderGap = matchedAddresses.toList().indexOf(addressRecord) >=
final isUsedAddressAboveGap = matchedAddresses.toList().indexOf(addressRecord) >=
matchedAddresses.length -
(addressRecord.isHidden
? ElectrumWalletAddressesBase.defaultChangeAddressesCount
: ElectrumWalletAddressesBase.defaultReceiveAddressesCount);
if (isUsedAddressUnderGap) {
if (isUsedAddressAboveGap) {
final prevLength = walletAddresses.allAddresses.length;
// Discover new addresses for the same address type until the gap limit is respected
@@ -2531,19 +2616,7 @@ abstract class ElectrumWalletBase
// Got a new transaction fetched, add it to the transaction history
// 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;
}
}
}
_applyLitecoinPegOutTag(tx);
transactionHistory.addOne(tx);
await transactionHistory.save();
}
@@ -2565,65 +2638,128 @@ abstract class ElectrumWalletBase
}
Future<void> fetchTransactionsForAddressTypeBatch(
Map<String, ElectrumTransactionInfo> historiesWithDetails, BitcoinAddressType type) async {
Map<String, ElectrumTransactionInfo> historiesWithDetails,
BitcoinAddressType type) async {
final addressesByType =
walletAddresses.allAddresses.where((addr) => addr.type == type).toList();
final hiddenAddresses = addressesByType.where((addr) => addr.isHidden).toList();
walletAddresses.allAddresses.where((addr) => addr.type == type).toList();
final receiveAddresses = addressesByType.where((addr) => !addr.isHidden).toList();
final hiddenAddresses = addressesByType.where((addr) => addr.isHidden).toList();
walletAddresses.hiddenAddresses.addAll(hiddenAddresses.map((e) => e.address));
await walletAddresses.saveAddressesInBox();
final tip = await getCurrentChainTip();
final addressHistory = await _processChunksToMap<BitcoinAddressRecord, String, ElectrumTransactionInfo>(
items: addressesByType,
chunkSize: addressHistoryChunkSize,
processChunk: (chunk) => _fetchBatchAddressHistory(chunk, tip, addressHistoryChunkSize),
await fetchTransactionsForAddressesBranchBatch(
historiesWithDetails,
type,
receiveAddresses,
isHidden: false,
isLegacyDerivation: false,
);
if (addressHistory.isNotEmpty) historiesWithDetails.addAll(addressHistory);
await fetchTransactionsForAddressesBranchBatch(
historiesWithDetails,
type,
hiddenAddresses,
isHidden: true,
isLegacyDerivation: false,
);
for (final addressRecord in addressesByType) {
final matchedAddresses = addressRecord.isHidden ? hiddenAddresses : receiveAddresses;
await fetchTransactionsForAddressesBranchBatch(
historiesWithDetails,
type,
receiveAddresses,
isHidden: false,
isLegacyDerivation: true,
);
final isUsedAddressUnderGap =
matchedAddresses.indexOf(addressRecord) >=
matchedAddresses.length - ElectrumWalletAddressesBase.gap;
if (isUsedAddressUnderGap && addressRecord.isUsed) {
final prevLength = walletAddresses.allAddresses.length;
await fetchTransactionsForAddressesBranchBatch(
historiesWithDetails,
type,
hiddenAddresses,
isHidden: true,
isLegacyDerivation: true,
);
}
await walletAddresses.discoverAddressesBatch(
matchedAddresses,
addressRecord.isHidden,
(newAddresses) async {
await _fetchBatchAddressHistory(
newAddresses,
tip,
discoveryHistoryChunkSize,
);
Future<void> fetchTransactionsForAddressesBranchBatch(
Map<String, ElectrumTransactionInfo> historiesWithDetails,
BitcoinAddressType type,
List<BitcoinAddressRecord> branchAddresses, {
required bool isHidden,
required bool isLegacyDerivation,
}) async {
if (branchAddresses.isEmpty) return;
return newAddresses
.where((addressRecord) => addressRecord.isUsed)
.map((addressRecord) => addressRecord.address)
.toSet();
},
type: type,
final tip = await getCurrentChainTip();
final currentBranch = [...branchAddresses];
final initialHistory =
await _processChunksToMap<BitcoinAddressRecord, String, ElectrumTransactionInfo>(
items: currentBranch,
chunkSize: addressHistoryChunkSize,
processChunk: (chunk) => _fetchBatchAddressHistory(
chunk,
tip,
addressHistoryChunkSize,
),
);
if (initialHistory.isNotEmpty) {
historiesWithDetails.addAll(initialHistory);
}
final gapLimit = isHidden
? ElectrumWalletAddressesBase.defaultChangeAddressesCount
: ElectrumWalletAddressesBase.defaultReceiveAddressesCount;
final highestUsedIndex = _highestUsedIndex(currentBranch);
final shouldDiscover =
highestUsedIndex >= 0 && highestUsedIndex >= currentBranch.length - gapLimit;
if (!shouldDiscover) return;
final newAddresses = await walletAddresses.discoverAddressesBatch(
currentBranch,
isHidden,
(newAddresses) async {
final newHistory = await _fetchBatchAddressHistory(
newAddresses,
tip,
discoveryHistoryChunkSize,
);
final newLength = walletAddresses.allAddresses.length;
if (newLength > prevLength) {
await fetchTransactionsForAddressTypeBatch(
historiesWithDetails,
type);
return;
if (newHistory.isNotEmpty) {
historiesWithDetails.addAll(newHistory);
}
return newAddresses
.where((addressRecord) => addressRecord.isUsed)
.map((addressRecord) => addressRecord.address)
.toSet();
},
type: type,
isLegacyDerivation: isLegacyDerivation,
);
if (newAddresses.isNotEmpty) {
currentBranch.addAll(newAddresses);
if (isHidden) {
walletAddresses.hiddenAddresses.addAll(newAddresses.map((e) => e.address));
await walletAddresses.saveAddressesInBox();
}
}
}
int _highestUsedIndex(List<BitcoinAddressRecord> addresses) {
for (int i = addresses.length - 1; i >= 0; i--) {
if (addresses[i].isUsed) return i;
}
return -1;
}
Future<Map<String, ElectrumTransactionInfo>> _fetchBatchAddressHistory(
List<BitcoinAddressRecord> addressRecords,
int? currentHeight,
@@ -2755,17 +2891,7 @@ abstract class ElectrumWalletBase
historiesWithDetails[tx.id] = tx;
// Litecoin peg-out tagging
if (this is LitecoinWallet) {
for (final tx2 in transactionHistory.transactions.values) {
final heightDiff = ((tx2.height ?? 0) - (tx.height ?? 0)).abs();
if (tx2.additionalInfo["isPegOut"] == true &&
tx2.amount == tx.amount &&
heightDiff <= 5) {
tx.additionalInfo["fromPegOut"] = true;
}
}
}
_applyLitecoinPegOutTag(tx);
transactionHistory.addOne(tx);
didUpdateHistory = true;
@@ -2812,6 +2938,22 @@ abstract class ElectrumWalletBase
);
}
Future<Map<String, List<Map<String, dynamic>>>> _getListUnspentBatch(
List<String> scriptHashes) {
return electrumClient.getBatchUnspent(
scriptHashes,
timeout: transactionBatchTimeoutMs,
);
}
Future<Map<String, Map<String, dynamic>>> _getBalanceBatch(
List<String> scriptHashes) {
return electrumClient.getBatchBalance(
scriptHashes,
timeout: transactionBatchTimeoutMs,
);
}
Future<Map<String, ElectrumTransactionInfo?>> fetchTransactionInfoBatch({
required List<String> hashes,
Map<String, int?>? heightsByHash,
@@ -2899,8 +3041,16 @@ abstract class ElectrumWalletBase
final inputTxIdsByHash = _collectInputTxIdsByHash(originalByHash);
final inputVerboseByTxId = await _fetchInputTransactionVerboseBatch(
inputTxIdsByHash);
final allInputTxids = <String>{};
for (final txids in inputTxIdsByHash.values) {
allInputTxids.addAll(txids);
}
final inputTxIds = allInputTxids.toList(growable: false);
final inputVerboseByTxId = inputTxIds.isEmpty
? <String, Map<String, dynamic>>{}
: await _fetchTransactionVerboseBatch(inputTxIds);
final parsedInputTxById = _parseTransactions(inputVerboseByTxId);
@@ -2992,65 +3142,6 @@ abstract class ElectrumWalletBase
return inputTxIdsByHash;
}
Future<Map<String, Map<String, dynamic>>> _fetchInputTransactionVerboseBatch(
Map<String, List<String>> inputTxidsByHash) async {
final allInputTxids = <String>{};
for (final txids in inputTxidsByHash.values) {
allInputTxids.addAll(txids);
}
final inputTxIds = allInputTxids.toList(growable: false);
final verboseTransactionByHash =
await _processChunksToMap<String, String, Map<String, dynamic>>(
items: inputTxIds,
chunkSize: inputTransactionChunkSize,
processChunk: _getTransactionVerboseBatch,
onChunkError: (chunk, error) {
if (error is electrum.RequestFailedTimeoutException) {
printV(
'fetchInputTransactionVerboseBatch timeout for ${chunk.length} txs: ${error.method}',
);
} else {
printV(
'fetchInputTransactionVerboseBatch failed for ${chunk.length} txs: $error,',
);
}
},
);
final emptyHex = <String>[];
for (final txId in inputTxIds) {
final vTx = verboseTransactionByHash[txId];
if (vTx == null || vTx.isEmpty || vTx['hex'] == null) {
emptyHex.add(txId);
}
}
final hexByHash = await _processChunksToMap<String, String, String?>(
items: emptyHex,
chunkSize: inputTransactionChunkSize,
processChunk: _getTransactionHexBatch,
);
for (final txId in inputTxIds) {
final verbose = verboseTransactionByHash[txId] ?? <String, dynamic>{};
if ((verbose['hex'] as String?) == null) {
final hex = hexByHash[txId];
if (hex != null && hex.isNotEmpty) {
verboseTransactionByHash[txId] = {
...verbose,
'hex': hex,
};
}
}
}
return verboseTransactionByHash;
}
Future<Map<String, ElectrumTransactionBundle>> _buildTransactionBundlesBatch({
required List<String> unique,
required Map<String, int?>? heightsByHash,
@@ -3283,18 +3374,64 @@ abstract class ElectrumWalletBase
}));
}
Future<List<Map<String, dynamic>>> fetchBalancesBatch(
List<BitcoinAddressRecord> addresses,
) async {
final scriptHashes = addresses.map((address) => address.getScriptHash(network)).toList();
if (scriptHashes.isEmpty) {
return <Map<String, dynamic>>[];
}
try {
final balancesByScriptHash =
await _processChunksToMap<String, String, Map<String, dynamic>>(
items: scriptHashes,
chunkSize: addressHistoryChunkSize,
processChunk: _getBalanceBatch,
);
final balances = scriptHashes
.map((scriptHash) => balancesByScriptHash[scriptHash] ?? <String, dynamic>{})
.toList();
final hasMissingBalance = balances.any((balance) => balance['confirmed'] == null);
if (hasMissingBalance) {
printV('fetchBalancesBatch returned missing balances, falling back to regular flow');
return fetchBalancesRegular(addresses);
}
return balances;
} catch (e) {
printV('fetchBalancesBatch failed, falling back to regular flow: $e');
return fetchBalancesRegular(addresses);
}
}
Future<List<Map<String, dynamic>>> fetchBalancesRegular(
List<BitcoinAddressRecord> addresses,
) async {
final balanceFutures = <Future<Map<String, dynamic>>>[];
for (final address in addresses) {
final sh = address.getScriptHash(network);
balanceFutures.add(electrumClient.getBalance(sh));
}
return Future.wait(balanceFutures);
}
Future<ElectrumBalance> fetchBalances() async {
final addresses = walletAddresses.allAddresses
.where((address) => address.address.isNotEmpty)
.where((address) => RegexUtils.addressTypeFromStr(address.address, network) is! MwebAddress)
.toList();
final balanceFutures = <Future<Map<String, dynamic>>>[];
for (var i = 0; i < addresses.length; i++) {
final addressRecord = addresses[i];
final sh = addressRecord.getScriptHash(network);
final balanceFuture = electrumClient.getBalance(sh);
balanceFutures.add(balanceFuture);
}
final balances = shouldUseBatchFetching
? await fetchBalancesBatch(addresses)
: await fetchBalancesRegular(addresses);
printV('Fetched balances for ${addresses.length} addresses. Batch fetching: $shouldUseBatchFetching');
var totalFrozen = 0;
var totalConfirmed = 0;
@@ -3330,8 +3467,6 @@ abstract class ElectrumWalletBase
});
});
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:
printV("got null balance responses from the server, setting connection status to lost");
@@ -3413,7 +3548,23 @@ abstract class ElectrumWalletBase
return base64Encode(decodedSig);
}
Future<void> _checkIfBatchSupported() async {
void _applyLitecoinPegOutTag(ElectrumTransactionInfo tx) {
if (this is! LitecoinWallet) return;
// 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;
}
}
}
Future<void> checkIfBatchSupported() async {
if (_isBatchSupported != null) {
printV('[BATCH_TEST] Already checked: $_isBatchSupported');
@@ -3433,12 +3584,23 @@ abstract class ElectrumWalletBase
printV('[BATCH_TEST] Start: hashes=${hashes.length}, timeout=${batchTestTimeoutMs}ms');
await electrumClient.callBatchWithTimeout(
final result = await electrumClient.callBatchWithTimeout(
method: 'blockchain.scripthash.get_history',
paramsList: paramsList,
timeout: batchTestTimeoutMs,
);
final hasError = result.any((item) =>
item is Map<String, dynamic> &&
item.containsKey('error') &&
item['error'] != null);
if (hasError) {
_isBatchSupported = false;
printV('[BATCH_TEST] Result: supported=false (server returned error)');
return;
}
_isBatchSupported = true;
printV('[BATCH_TEST] Result: supported=true');
} on electrum.RequestFailedTimeoutException catch (e) {
+13 -7
View File
@@ -715,38 +715,42 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
}
@action
Future<bool> discoverAddressesBatch(
Future<List<BitcoinAddressRecord>> discoverAddressesBatch(
List<BitcoinAddressRecord> addressList,
bool isHidden,
Future<Set<String>> Function(List<BitcoinAddressRecord>) getUsedAddresses, {
BitcoinAddressType type = SegwitAddresType.p2wpkh,
required bool isLegacyDerivation,
}) async {
final newAddresses = await _createNewAddresses(
gap,
startIndex: addressList.length,
isHidden: isHidden,
type: type,
isLegacyDerivation: isLegacyDerivation,
);
addAddresses(newAddresses);
final usedAddresses = await getUsedAddresses(newAddresses);
final isLastAddressUsed =
newAddresses.isNotEmpty && usedAddresses.contains(newAddresses.last.address);
if (!isLastAddressUsed) {
return false;
final hasUsedAddressInGap = newAddresses.any(
(addressRecord) => usedAddresses.contains(addressRecord.address));
if (!hasUsedAddressInGap) {
return newAddresses;
}
final updatedAddressList = [...addressList, ...newAddresses];
await discoverAddressesBatch(
final moreNewAddresses = await discoverAddressesBatch(
updatedAddressList,
isHidden,
getUsedAddresses,
type: type,
isLegacyDerivation: isLegacyDerivation,
);
return true;
return [...newAddresses, ...moreNewAddresses];
}
@@ -818,6 +822,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
this._addresses.clear();
this._addresses.addAll(addressesSet);
updateAddressesByMatch();
updateReceiveAddresses();
updateChangeAddresses();
}
@action
+1
View File
@@ -381,6 +381,7 @@ abstract class LitecoinWalletBase extends ElectrumWallet with Store {
} catch (e) {
printV("failed to subscribe for updates: $e");
}
await checkIfBatchSupported();
updateFeeRates();
_feeRatesTimer?.cancel();
_feeRatesTimer =
+35 -25
View File
@@ -92,15 +92,25 @@ class Node {
login = map['login'] as String?,
label = map['label'] as String?,
password = map['password'] as String?,
isPow = (map["isPow"] != null && map['isPow'] != 0) as bool? ?? false,
useSSL = (map['useSSL'] != 0) as bool?,
isPow = _getBoolFromDB(map['isPow']),
useSSL = _getBoolFromDB(map['useSSL']),
typeRaw = (map["typeRaw"] ?? 0) as int,
trusted = (map['trusted'] != 0) as bool? ?? false,
trusted = _getBoolFromDB(map['trusted']),
socksProxyAddress = map['socksProxyAddress'] as String?,
isEnabledForAutoSwitching = (map['isEnabledForAutoSwitching'] != 0) as bool? ?? false,
isOfficial = (map['isOfficial'] != 0) as bool? ?? false,
isBuiltin = (map['isBuiltin'] != 0) as bool? ?? false,
isDefault = (map['isDefault'] != 0) as bool? ?? false;
isEnabledForAutoSwitching = _getBoolFromDB(map['isEnabledForAutoSwitching']),
isOfficial = _getBoolFromDB(map['isOfficial']),
isBuiltin = _getBoolFromDB(map['isBuiltin']),
isDefault = _getBoolFromDB(map['isDefault']);
static bool _getBoolFromDB(value, {bool? defaultValue}) {
if (value is bool) {
return value;
} else if (value is int) {
return value == 1;
} else {
return defaultValue ?? false;
}
}
Map<String, dynamic> toMap() {
return {
@@ -110,15 +120,15 @@ class Node {
'login': login,
"label": label,
'password': password,
"isPow": isPow,
'useSSL': useSSL,
"isPow": isPow ? 1 : 0,
'useSSL': (useSSL??false) ? 1 : 0,
"typeRaw": typeRaw,
'trusted': trusted,
'trusted': trusted ? 1 : 0,
'socksProxyAddress': socksProxyAddress,
'isEnabledForAutoSwitching': isEnabledForAutoSwitching,
"isOfficial": isOfficial,
"isBuiltin": isBuiltin,
"isDefault": isDefault
'isEnabledForAutoSwitching': isEnabledForAutoSwitching ? 1 : 0,
"isOfficial": isOfficial ? 1 : 0,
"isBuiltin": isBuiltin ? 1 : 0,
"isDefault": isDefault ? 1 : 0
};
}
@@ -127,11 +137,11 @@ class Node {
}
static Future<int> deleteAll() async {
return await db!.delete(tableName, where: "isPow = ?", whereArgs: [false]);
return await db!.delete(tableName, where: "isPow = ?", whereArgs: [0]);
}
static Future<int> deleteAllPow() async {
return await db!.delete(tableName, where: "isPow = ?", whereArgs: [true]);
return await db!.delete(tableName, where: "isPow = ?", whereArgs: [1]);
}
Future<int> save() async {
@@ -173,35 +183,35 @@ class Node {
static Future<List<Node>> getAll() async {
return selectList("isPow = ?", [false]);
return selectList("isPow = ?", [0]);
}
static Future<List<Node>> getAllBuiltin() async {
return selectList("isPow = ? AND isBuiltin = ?", [false, true]);
return selectList("isPow = ? AND isBuiltin = ?", [0, 1]);
}
static Future<List<Node>> getAllPowBuiltin() async {
return selectList("isPow = ? AND isBuiltin = ?", [true, true]);
return selectList("isPow = ? AND isBuiltin = ?", [1, 1]);
}
static Future<List<Node>> getAllForWalletType(WalletType type) async {
return selectList("typeRaw = ? AND isPow = ?", [serializeToInt(type), false]);
return selectList("typeRaw = ? AND isPow = ?", [serializeToInt(type), 0]);
}
static Future<Node?> getDefaultForWalletType(WalletType type) async {
return (await selectList("typeRaw = ? AND isPow = ? AND isDefault = ?", [serializeToInt(type), false, true])).firstOrNull;
return (await selectList("typeRaw = ? AND isPow = ? AND isDefault = ?", [serializeToInt(type), 0, 1])).firstOrNull;
}
static Future<Node?> getDefaultPowForWalletType(WalletType type) async {
return (await selectList("typeRaw = ? AND isPow = ? AND isDefault = ?", [serializeToInt(type), true, true])).firstOrNull;
return (await selectList("typeRaw = ? AND isPow = ? AND isDefault = ?", [serializeToInt(type), 1, 1])).firstOrNull;
}
static Future<List<Node>> getAllForWalletTypePow(WalletType type) async {
return selectList("typeRaw = ? AND isPow = ?", [serializeToInt(type), true]);
return selectList("typeRaw = ? AND isPow = ?", [serializeToInt(type), 1]);
}
static Future<List<Node>> getAllPow() async {
return selectList("isPow = ?", [true]);
return selectList("isPow = ?", [1]);
}
static Future<Node?> get(int id) async {
@@ -475,7 +485,7 @@ class Node {
},
),
);
final data = jsonDecode(response.body);
if (response.statusCode != 200 ||
data["error"] != null ||
+1 -1
View File
@@ -342,7 +342,7 @@ class _NewReceivePageState extends State<NewReceivePage> {
setState(() {
_addressItemWithLabel = widget.addressListViewModel.forceRecomputeItems.firstWhereOrNull(
(item) => (item is WalletAddressListItem && item.address == newAddress.address))
as WalletAddressListItem;
as WalletAddressListItem?;
});
}
}
+1 -1
View File
@@ -972,7 +972,7 @@ class _NewSendPageState extends State<NewSendPage> {
onNext: (PaymentFlowResult newResult) {
final selectedChainId = newResult.chainId;
final isCompatible =
selectedChainId == evm!.getSelectedChainId(widget.sendViewModel.wallet);
selectedChainId == evm?.getSelectedChainId(widget.sendViewModel.wallet);
if (isCompatible) {
widget.sendViewModel.setSelectedCryptoCurrency(
@@ -263,7 +263,7 @@ class _SwapAddressSelectionModalRowState extends State<SwapAddressSelectionModal
Row(
spacing: 12,
children: [
Image.asset(widget.iconPath, width: 24, height: 24),
CakeImageWidget(imageUrl: widget.iconPath, width: 24, height: 24),
Text(widget.wallet.name)
],
),
+1 -5
View File
@@ -46,11 +46,7 @@ static void my_application_activate(GApplication* application) {
} else {
gtk_window_set_title(window, "Cake Wallet");
}
if (getenv("DESKTOP_FORCE_MOBILE")) {
gtk_window_set_default_size(window, 720, 1280);
} else {
gtk_window_set_default_size(window, 1280, 720);
}
gtk_window_set_default_size(window, 480, 800);
gtk_widget_show(GTK_WIDGET(window));
g_autoptr(FlDartProject) project = fl_dart_project_new();
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

+1 -1
View File
@@ -208,7 +208,7 @@
"coin_control": "التحكم في العملات (اختياري)",
"coin_control_desc": "تصفية المخرجات لهذه المعاملة. ستنعكس التغييرات التي تجريها هنا على المعاملة الحالية فقط.",
"coin_control_load_failed": "فشل تحميل العملات غير المصروفة",
"cold_or_recover_wallet": "أضف محفظة للعرض فقط من Cupcake أو محفظة باردة أو استعد محفظة ورقية",
"cold_or_recover_wallet": "أضف محفظة باردة معزولة عن الهواء أو استرد محفظة ورقية.",
"collection_address": "عنوان الاستلام",
"collection_description": "وصف المجموعة",
"collection_name": "اسم المجموعة",
+1 -1
View File
@@ -208,7 +208,7 @@
"coin_control": "Контрол на монетите (по избор)",
"coin_control_desc": "Филтрирайте изходите за тази транзакция. Промените, които направите тук, ще се отразят само на текущата транзакция.",
"coin_control_load_failed": "Неуспешно зареждане на неизхарчени монети",
"cold_or_recover_wallet": "Добавете портфейл само за четене от Cupcake или от студен портфейл, или възстановете хартиен портфейл",
"cold_or_recover_wallet": "Добавете cold wallet с въздушна изолация или възстановете хартиен портфейл.",
"collection_address": "Адрес за събиране",
"collection_description": "Описание на колекцията",
"collection_name": "Име на колекцията",
+1 -1
View File
@@ -208,7 +208,7 @@
"coin_control": "Kontrola mincí (nepovinné)",
"coin_control_desc": "Filtrujte výstupy pro tuto transakci. Změny, které zde provedete, se projeví pouze u aktuální transakce.",
"coin_control_load_failed": "Nepodařilo se načíst neutracené mince",
"cold_or_recover_wallet": "Přidejte peněženku pouze pro čtení z Cupcake nebo z cold peněženky, nebo obnovte papírovou peněženku",
"cold_or_recover_wallet": "Přidejte air-gapped cold wallet nebo obnovte papírovou peněženku.",
"collection_address": "Sběrná adresa",
"collection_description": "Popis kolekce",
"collection_name": "Název kolekce",
+1 -1
View File
@@ -208,7 +208,7 @@
"coin_control": "Coin Control (optional)",
"coin_control_desc": "Filtern Sie die Outputs für diese Transaktion. Die hier vorgenommenen Änderungen wirken sich nur auf die aktuelle Transaktion aus.",
"coin_control_load_failed": "Nicht ausgegebene Coins konnten nicht geladen werden",
"cold_or_recover_wallet": "Fügen Sie eine schreibgeschützte Wallet von Cupcake oder eine Cold Wallet hinzu oder stellen Sie eine Paper Wallet wieder her.",
"cold_or_recover_wallet": "Fügen Sie eine air-gapped Cold Wallet hinzu oder stellen Sie eine Paper Wallet wieder her.",
"collection_address": "Sammeladresse",
"collection_description": "Sammlungsbeschreibung",
"collection_name": "Sammlungsname",
+1 -1
View File
@@ -210,7 +210,7 @@
"coin_control": "Coin Control (optional)",
"coin_control_desc": "Filter outputs for this transaction. The changes you make here will only reflect on the current transaction.",
"coin_control_load_failed": "Failed to load unspent coins",
"cold_or_recover_wallet": "Add a read-only wallet from Cupcake or a cold wallet or recover a paper wallet",
"cold_or_recover_wallet": "Add an air-gapped cold wallet or recover a paper wallet.",
"collection_address": "Collection Address",
"collection_description": "Collection Description",
"collection_name": "Collection Name",
+1 -1
View File
@@ -208,7 +208,7 @@
"coin_control": "Control de monedas (opcional)",
"coin_control_desc": "Filtra las salidas de esta transacción. Los cambios que realices aquí solo se reflejarán en la transacción actual.",
"coin_control_load_failed": "No se pudieron cargar las monedas sin gastar",
"cold_or_recover_wallet": "Añadir una billetera de solo lectura desde Cupcake, una billetera fría o recuperar una billetera de papel",
"cold_or_recover_wallet": "Agrega una cold wallet air-gapped o recupera una paper wallet.",
"collection_address": "Dirección de cobro",
"collection_description": "Descripción de la colección",
"collection_name": "Nombre de la colección",
+1 -1
View File
@@ -206,7 +206,7 @@
"coin_control": "کنترل کوین (اختیاری)",
"coin_control_desc": "خروجی‌های این تراکنش را فیلتر کنید. تغییراتی که اینجا اعمال می‌کنید فقط روی تراکنش فعلی اثر می‌گذارد.",
"coin_control_load_failed": "بارگیری کوین‌های خرج‌نشده ناموفق بود",
"cold_or_recover_wallet": "افزودن یک کیف پول فقط‌خواندنی از Cupcake یا یک کیف پول سرد یا بازیابی یک کیف پول کاغذی",
"cold_or_recover_wallet": "یک کیف پول سرد ایزوله‌شده از هوا اضافه کنید یا یک کیف پول کاغذی را بازیابی کنید.",
"collection_address": "آدرس دریافت",
"collection_description": "توضیحات مجموعه",
"collection_name": "نام مجموعه",
+1 -1
View File
@@ -208,7 +208,7 @@
"coin_control": "Contrôle des pièces (optionnel)",
"coin_control_desc": "Filtrer les sorties pour cette transaction. Les modifications que vous apportez ici ne s’appliqueront qu’à la transaction en cours.",
"coin_control_load_failed": "Échec du chargement des sorties non dépensées",
"cold_or_recover_wallet": "Ajoutez un portefeuille en lecture seule depuis Cupcake, un portefeuille froid ou récupérez un portefeuille papier",
"cold_or_recover_wallet": "Ajoutez un cold wallet air-gapped ou récupérez un paper wallet.",
"collection_address": "Adresse de collecte",
"collection_description": "Description de la collection",
"collection_name": "Nom de la collection",
+1 -1
View File
@@ -170,7 +170,7 @@
"coin_control": "Jehecha moneda (poravokuaá)",
"coin_control_desc": "Embogua hag̃ua umi outputs ko transacción-pe guarã. Umi ñemoambue rejapóva ko’ápe ojehechaukáta pe transacción ko’ág̃aguáre añoite.",
"coin_control_load_failed": "Ndojekargai umi moneda ndojeporúiva .",
"cold_or_recover_wallet": "Emoĩ peteĩ billetera moñe’ẽ hag̃uánte Cupcake-gui, peteĩ billetera ro’ysã, térã eguerujey peteĩ billetera kuatiapegua",
"cold_or_recover_wallet": "Embojoapy peteĩ cold wallet air-gapped térã emboykeréi peteĩ paper wallet.",
"color": "Sa'y",
"color_theme": "Téma sa'y",
"commit_transaction_amount_fee": "Emoneĩ ñembohasa\nMboheta: ${amount}\nHepyme’ẽ: ${fee}",
+1 -1
View File
@@ -208,7 +208,7 @@
"coin_control": "Sarrafa tsabar kuɗi (na zaɓi)",
"coin_control_desc": "Tace fitarwa (outputs) don wannan ma'amala. Canje-canjen da kuke yi a nan za su shafi ma'amalar yanzu kawai.",
"coin_control_load_failed": "An kasa loda tsabar kuɗin da ba a kashe ba",
"cold_or_recover_wallet": "Ƙara walat mai karantawa-kawai daga Cupcake ko walat mai sanyi, ko dawo da walat na takarda",
"cold_or_recover_wallet": "Ƙara cold wallet mai air-gapped ko dawo da paper wallet.",
"collection_address": "Adireshin karɓa",
"collection_description": "Bayanin Tarin",
"collection_name": "Sunan tarin",
+1 -1
View File
@@ -208,7 +208,7 @@
"coin_control": "कॉइन नियंत्रण (वैकल्पिक)",
"coin_control_desc": "इस लेनदेन के लिए आउटपुट फ़िल्टर करें। यहां किए गए बदलाव केवल वर्तमान लेनदेन में ही दिखाई देंगे।",
"coin_control_load_failed": "अनखर्चित सिक्के लोड करने में विफल",
"cold_or_recover_wallet": "Cupcake या किसी कोल्ड वॉलेट से एक रीड-ओनली वॉलेट जोड़ें, या किसी पेपर वॉलेट को रिकवर करें",
"cold_or_recover_wallet": "एक एयर-गैप्ड कोल्ड वॉलेट जोड़ें या पेपर वॉलेट रिकवर करें।",
"collection_address": "कलेक्शन पता",
"collection_description": "संग्रह का विवरण",
"collection_name": "संग्रह का नाम",
+1 -1
View File
@@ -208,7 +208,7 @@
"coin_control": "Kontrola kovanica (nije obavezno)",
"coin_control_desc": "Filtrirajte izlaze za ovu transakciju. Promjene koje ovdje napravite odrazit će se samo na trenutnu transakciju.",
"coin_control_load_failed": "Učitavanje nepotrošenih kovanica nije uspjelo",
"cold_or_recover_wallet": "Dodajte novčanik samo za čitanje iz Cupcakea ili hladni novčanik ili oporavite papirnati novčanik",
"cold_or_recover_wallet": "Dodajte air-gapped cold wallet ili oporavite paper wallet.",
"collection_address": "Adresa za prikupljanje",
"collection_description": "Opis kolekcije",
"collection_name": "Naziv kolekcije",
+1 -1
View File
@@ -208,7 +208,7 @@
"coin_control": "Մետաղադրամների կառավարում (ըստ ցանկության)",
"coin_control_desc": "Զտեք ելքերը այս գործարքի համար։ Այստեղ կատարած փոփոխությունները կանդրադառնան միայն ընթացիկ գործարքի վրա։",
"coin_control_load_failed": "Չհաջողվեց բեռնել չծախսված մետաղադրամները",
"cold_or_recover_wallet": "Ավելացնել միայն դիտման դրամապանակ Cupcake-ից կամ սառը դրամապանակից, կամ վերականգնել թղթային դրամապանակը",
"cold_or_recover_wallet": "Ավելացրեք air-gapped cold wallet կամ վերականգնեք paper wallet:",
"collection_address": "Հավաքագրման հասցե",
"collection_description": "Հավաքածուի նկարագրություն",
"collection_name": "Հավաքածուի անունը",
+1 -1
View File
@@ -208,7 +208,7 @@
"coin_control": "Kontrol koin (opsional)",
"coin_control_desc": "Filter output untuk transaksi ini. Perubahan yang Anda buat di sini hanya akan berlaku pada transaksi saat ini.",
"coin_control_load_failed": "Gagal memuat koin yang belum dibelanjakan",
"cold_or_recover_wallet": "Tambahkan dompet hanya-baca dari Cupcake atau dompet dingin, atau pulihkan dompet kertas",
"cold_or_recover_wallet": "Tambahkan cold wallet air-gapped atau pulihkan paper wallet.",
"collection_address": "Alamat Penagihan",
"collection_description": "Deskripsi Koleksi",
"collection_name": "Nama Koleksi",
+1 -1
View File
@@ -208,7 +208,7 @@
"coin_control": "Controllo delle monete (opzionale)",
"coin_control_desc": "Filtra gli output per questa transazione. Le modifiche apportate qui avranno effetto solo sulla transazione corrente.",
"coin_control_load_failed": "Impossibile caricare le monete non spese",
"cold_or_recover_wallet": "Aggiungi un portafoglio di sola lettura da Cupcake o un portafoglio cold, oppure recupera un portafoglio cartaceo",
"cold_or_recover_wallet": "Aggiungi un cold wallet air-gapped o recupera un paper wallet.",
"collection_address": "Indirizzo di raccolta",
"collection_description": "Descrizione della collezione",
"collection_name": "Nome della collezione",
+1 -1
View File
@@ -208,7 +208,7 @@
"coin_control": "コインコントロール(任意)",
"coin_control_desc": "このトランザクションの出力を絞り込みます。ここで行った変更は、現在のトランザクションにのみ反映されます。",
"coin_control_load_failed": "未使用のコインの読み込みに失敗しました",
"cold_or_recover_wallet": "Cupcakeまたはコールドウォレットから読み取り専用ウォレットを追加するか、ペーパーウォレットを復元する",
"cold_or_recover_wallet": "エアギャップのコールドウォレットを追加するか、ペーパーウォレットを復元します。",
"collection_address": "コレクションアドレス",
"collection_description": "コレクションの説明",
"collection_name": "コレクション名",
+1 -1
View File
@@ -208,7 +208,7 @@
"coin_control": "코인 컨트롤(선택 사항)",
"coin_control_desc": "이 거래의 출력(UTXO)을 필터링합니다. 여기에서 변경한 내용은 현재 거래에만 반영됩니다.",
"coin_control_load_failed": "미사용 코인을 불러오지 못했습니다",
"cold_or_recover_wallet": "Cupcake 또는 콜드 월렛에서 읽기 전용 지갑을 추가하거나 페이퍼 월렛을 복구합니다",
"cold_or_recover_wallet": "에어갭 콜드 월렛을 추가하거나 페이퍼 월렛을 복구하세요.",
"collection_address": "컬렉션 주소",
"collection_description": "컬렉션 설명",
"collection_name": "컬렉션 이름",
+1 -1
View File
@@ -208,7 +208,7 @@
"coin_control": "Coin control (ရွေးချယ်နိုင်သည်)",
"coin_control_desc": "ဤငွေလွှဲပြောင်းမှုအတွက် အထွက်များကို စစ်ထုတ်ပါ။ ဤနေရာတွင် သင်ပြုလုပ်သော အပြောင်းအလဲများသည် လက်ရှိ ငွေလွှဲပြောင်းမှုတွင်သာ သက်ရောက်ပါမည်။",
"coin_control_load_failed": "မသုံးရသေးသော ကွိုင်များကို ဖွင့်၍မရပါ။",
"cold_or_recover_wallet": "Cupcake သို့မဟုတ် cold wallet မှ read-only wallet ကို ထည့်ပါ သို့မဟုတ် paper wallet ကို ပြန်လည်ရယူပါ",
"cold_or_recover_wallet": "Air-gapped cold wallet ထည့်ပါ သို့မဟုတ် paper wallet ကို ပြန်လည်ရယူပါ။",
"collection_address": "စုဆောင်းရန် လိပ်စာ",
"collection_description": "စုစည်းမှု ဖော်ပြချက်",
"collection_name": "စုစည်းမှုအမည်",
+1 -1
View File
@@ -206,7 +206,7 @@
"coin_control": "Coin Control (optioneel)",
"coin_control_desc": "Filter de outputs voor deze transactie. De wijzigingen die je hier aanbrengt, worden alleen toegepast op de huidige transactie.",
"coin_control_load_failed": "Kan niet-uitgegeven munten niet laden",
"cold_or_recover_wallet": "Voeg een alleen-lezen wallet toe van Cupcake of een koude wallet of herstel een papieren wallet",
"cold_or_recover_wallet": "Voeg een air-gapped cold wallet toe of herstel een paper wallet.",
"collection_address": "Verzameladres",
"collection_description": "Verzamelingsbeschrijving",
"collection_name": "Verzamelnaam",
+1 -1
View File
@@ -208,7 +208,7 @@
"coin_control": "Kontrola monet (opcjonalna)",
"coin_control_desc": "Filtruj wyjścia dla tej transakcji. Zmiany, które tu wprowadzisz, będą dotyczyć tylko bieżącej transakcji.",
"coin_control_load_failed": "Nie udało się wczytać niewydanych monet",
"cold_or_recover_wallet": "Dodaj portfel tylko do odczytu z Cupcake albo zimny portfel lub odzyskaj portfel papierowy",
"cold_or_recover_wallet": "Dodaj air-gapped cold wallet lub odzyskaj paper wallet.",
"collection_address": "Adres odbioru",
"collection_description": "Opis kolekcji",
"collection_name": "Nazwa kolekcji",
+1 -1
View File
@@ -208,7 +208,7 @@
"coin_control": "Controle de moedas (opcional)",
"coin_control_desc": "Filtre as saídas para esta transação. As alterações que você fizer aqui só serão refletidas na transação atual.",
"coin_control_load_failed": "Falha ao carregar moedas não gastas",
"cold_or_recover_wallet": "Adicione uma carteira somente leitura do Cupcake ou de uma carteira fria, ou recupere uma carteira de papel",
"cold_or_recover_wallet": "Adicione uma cold wallet air-gapped ou recupere uma paper wallet.",
"collection_address": "Endereço de coleta",
"collection_description": "Descrição da coleção",
"collection_name": "Nome da coleção",
+2 -2
View File
@@ -207,7 +207,7 @@
"coin_control": "Controle de moedas (opcional)",
"coin_control_desc": "Filtrar saídas para esta transação. As alterações feitas aqui refletirão apenas na transação atual.",
"coin_control_load_failed": "Falha ao carregar moedas não gastas",
"cold_or_recover_wallet": "Adicionar uma carteira somente leitura do Cupcake, uma carteira fria ou recuperar uma carteira de papel",
"cold_or_recover_wallet": "Adicione uma cold wallet air-gapped ou recupere uma paper wallet.",
"collection_address": "Endereço de coleta",
"collection_description": "Descrição da coleção",
"collection_name": "Nome da coleção",
@@ -1371,4 +1371,4 @@
"zcash_card_missing_funds": "Fundos ausentes?",
"zcash_card_scan": "Escanear",
"zcash_card_warning": "Não feche o app até que o procedimento seja concluído; caso contrário, este processo precisará ser reiniciado do zero."
}
}
+1 -1
View File
@@ -208,7 +208,7 @@
"coin_control": "Контроль монет (необязательно)",
"coin_control_desc": "Отфильтруйте выходы для этой транзакции. Изменения, которые вы внесёте здесь, отразятся только на текущей транзакции.",
"coin_control_load_failed": "Не удалось загрузить неизрасходованные монеты",
"cold_or_recover_wallet": "Добавить кошелёк только для чтения из Cupcake, холодный кошелёк или восстановить бумажный кошелёк",
"cold_or_recover_wallet": "Добавьте изолированный холодный кошелёк или восстановите бумажный кошелёк.",
"collection_address": "Адрес для сбора",
"collection_description": "Описание коллекции",
"collection_name": "Название коллекции",
+1 -1
View File
@@ -208,7 +208,7 @@
"coin_control": "การควบคุมเหรียญ (ไม่บังคับ)",
"coin_control_desc": "กรองเอาต์พุตสำหรับธุรกรรมนี้ การเปลี่ยนแปลงที่คุณทำที่นี่จะมีผลกับธุรกรรมปัจจุบันเท่านั้น",
"coin_control_load_failed": "ไม่สามารถโหลดเหรียญที่ยังไม่ได้ใช้จ่าย",
"cold_or_recover_wallet": "เพิ่มกระเป๋าเงินแบบอ่านอย่างเดียวจาก Cupcake หรือเพิ่มกระเป๋าเงินแบบ Cold Wallet หรือกู้คืนกระเป๋าเงินกระดาษ",
"cold_or_recover_wallet": "เพิ่ม cold wallet แบบ air-gapped หรือกู้คืน paper wallet",
"collection_address": "ที่อยู่สำหรับรับเงิน",
"collection_description": "คำอธิบายคอลเลกชัน",
"collection_name": "ชื่อคอลเลกชัน",
+1 -1
View File
@@ -208,7 +208,7 @@
"coin_control": "Kontrol ng coin (opsyonal)",
"coin_control_desc": "I-filter ang mga output para sa transaksyong ito. Ang mga pagbabagong gagawin mo rito ay makikita lamang sa kasalukuyang transaksyon.",
"coin_control_load_failed": "Nabigong i-load ang mga hindi nagastos na coin",
"cold_or_recover_wallet": "Magdagdag ng read-only na wallet mula sa Cupcake o cold wallet, o i-recover ang paper wallet",
"cold_or_recover_wallet": "Magdagdag ng air-gapped na cold wallet o i-recover ang isang paper wallet.",
"collection_address": "Address ng Koleksyon",
"collection_description": "Paglalarawan ng Koleksyon",
"collection_name": "Pangalan ng Koleksyon",
+1 -1
View File
@@ -208,7 +208,7 @@
"coin_control": "Coin kontrolü (isteğe bağlı)",
"coin_control_desc": "Bu işlem için çıktıları filtreleyin. Burada yapacağınız değişiklikler yalnızca mevcut işleme yansıyacaktır.",
"coin_control_load_failed": "Harcanmamış coin'ler yüklenemedi",
"cold_or_recover_wallet": "Cupcake’ten veya bir soğuk cüzdandan salt okunur bir cüzdan ekleyin ya da bir kâğıt cüzdanı kurtarın",
"cold_or_recover_wallet": "Hava boşluklu bir cold wallet ekleyin veya bir paper wallet kurtarın.",
"collection_address": "Tahsilat Adresi",
"collection_description": "Koleksiyon Açıklaması",
"collection_name": "Koleksiyon Adı",
+1 -1
View File
@@ -208,7 +208,7 @@
"coin_control": "Контроль монет (необов’язково)",
"coin_control_desc": "Відфільтрувати виходи для цієї транзакції. Зміни, які ви вносите тут, застосовуватимуться лише до поточної транзакції.",
"coin_control_load_failed": "Не вдалося завантажити невитрачені монети",
"cold_or_recover_wallet": "Додайте гаманець лише для читання з Cupcake, холодного гаманця або відновіть паперовий гаманець",
"cold_or_recover_wallet": "Додайте ізольований холодний гаманець або відновіть паперовий гаманець.",
"collection_address": "Адреса для збору",
"collection_description": "Опис колекції",
"collection_name": "Назва колекції",
+1 -1
View File
@@ -208,7 +208,7 @@
"coin_control": "کوائن کنٹرول (اختیاری)",
"coin_control_desc": "اس لین دین کے لیے آؤٹ پٹس فلٹر کریں۔ آپ یہاں جو تبدیلیاں کریں گے وہ صرف موجودہ لین دین پر ہی لاگو ہوں گی۔",
"coin_control_load_failed": "غیر خرچ شدہ سکے لوڈ کرنے میں ناکام",
"cold_or_recover_wallet": "Cupcake یا کولڈ والیٹ سے صرف پڑھنے کے لیے والیٹ شامل کریں یا پیپر والیٹ بازیافت کریں",
"cold_or_recover_wallet": "ایک ایئر-گیپڈ کولڈ والیٹ شامل کریں یا ایک پیپر والیٹ بازیاب کریں۔",
"collection_address": "وصولی کا پتہ",
"collection_description": "کلیکشن کی تفصیل",
"collection_name": "کلیکشن کا نام",
+1 -1
View File
@@ -206,7 +206,7 @@
"coin_control": "Kiểm soát coin (tùy chọn)",
"coin_control_desc": "Lọc các đầu ra cho giao dịch này. Những thay đổi bạn thực hiện ở đây sẽ chỉ áp dụng cho giao dịch hiện tại.",
"coin_control_load_failed": "Không thể tải các coin chưa sử dụng",
"cold_or_recover_wallet": "Thêm ví chỉ đọc từ Cupcake hoặc ví lạnh hoặc khôi phục ví giấy",
"cold_or_recover_wallet": "Thêm ví lạnh air-gapped hoặc khôi phục ví giấy.",
"collection_address": "Địa chỉ thu tiền",
"collection_description": "Mô tả bộ sưu tập",
"collection_name": "Tên bộ sưu tập",
+1 -1
View File
@@ -208,7 +208,7 @@
"coin_control": "Ìṣàkóso ẹyọ (àṣàyàn)",
"coin_control_desc": "Àlẹmọ àwọn àbájáde (outputs) fún ìdúnàdúrà yìí. Àwọn àtúnṣe tí o ṣe níbí yóò kan ìdúnàdúrà lọ́wọ́lọ́wọ́ nìkan.",
"coin_control_load_failed": "Kuna lati kojọpọ awọn owó tí a kò tíì ná",
"cold_or_recover_wallet": "Ṣafikun apamọwọ kika-nìkan láti Cupcake tàbí apamọwọ tútù, tàbí gba apamọwọ ìwé padà",
"cold_or_recover_wallet": "Ṣafikun air-gapped cold wallet tabi gba paper wallet padà.",
"collection_address": "Adirẹsi ìkójọpọ̀",
"collection_description": "Apejuwe akojọpọ",
"collection_name": "Orúkọ Àkójọpọ̀",
+1 -1
View File
@@ -208,7 +208,7 @@
"coin_control": "币种控制(可选)",
"coin_control_desc": "筛选此交易的输出。您在此处所做的更改仅会影响当前交易。",
"coin_control_load_failed": "无法加载未花费的币",
"cold_or_recover_wallet": "添加来自 Cupcake 的只读钱包、冷钱包,或恢复纸钱包",
"cold_or_recover_wallet": "添加气隙冷钱包或恢复纸钱包。",
"collection_address": "收款地址",
"collection_description": "收藏描述",
"collection_name": "收藏名称",
+2 -2
View File
@@ -172,7 +172,7 @@
"clearnet_link": "Clearnet 連結",
"close": "關閉",
"coin_control": "幣種控制(可選)",
"cold_or_recover_wallet": "新增來自 Cupcake 的唯讀錢包、冷錢包,或復原紙錢包",
"cold_or_recover_wallet": "新增氣隙冷錢包或復原紙錢包。",
"collection_address": "收款地址",
"collection_description": "收藏描述",
"collection_name": "收藏名稱",
@@ -1180,4 +1180,4 @@
"youCanGoBackToYourDapp": "您現在可以返回您的 dApp",
"your": "您的",
"yy": "YY"
}
}