feat: change scanning to subscription model, sync improvements

This commit is contained in:
Rafael Saes 2024-04-17 16:35:11 -03:00
parent a887ea74b5
commit e4156ba282
51 changed files with 696 additions and 406 deletions

View file

@ -90,7 +90,8 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
String? scriptHash; String? scriptHash;
String updateScriptHash(BasedUtxoNetwork network) { String getScriptHash(BasedUtxoNetwork network) {
if (scriptHash != null) return scriptHash!;
scriptHash = sh.scriptHash(address, network: network); scriptHash = sh.scriptHash(address, network: network);
return scriptHash!; return scriptHash!;
} }

View file

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

View file

@ -64,19 +64,9 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
), ),
); );
hasSilentPaymentsScanning = true;
autorun((_) { autorun((_) {
this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress; this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress;
}); });
reaction((_) => walletAddresses.addressPageType, (BitcoinAddressType addressPageType) {
final prev = hasSilentPaymentsScanning;
hasSilentPaymentsScanning = addressPageType == SilentPaymentsAddresType.p2sp;
if (prev != hasSilentPaymentsScanning) {
startSync();
}
});
} }
static Future<BitcoinWallet> create({ static Future<BitcoinWallet> create({

View file

@ -36,14 +36,15 @@ class ElectrumClient {
_errors = {}, _errors = {},
unterminatedString = ''; unterminatedString = '';
static const connectionTimeout = Duration(seconds: 300); static const connectionTimeout = Duration(seconds: 10);
static const aliveTimerDuration = Duration(seconds: 300); static const aliveTimerDuration = Duration(seconds: 10);
bool get isConnected => _isConnected; bool get isConnected => _isConnected;
Socket? socket; Socket? socket;
void Function(bool)? onConnectionStatusChange; void Function(bool)? onConnectionStatusChange;
int _id; int _id;
final Map<String, SocketTask> _tasks; final Map<String, SocketTask> _tasks;
Map<String, SocketTask> get tasks => _tasks;
final Map<String, String> _errors; final Map<String, String> _errors;
bool _isConnected; bool _isConnected;
Timer? _aliveTimer; Timer? _aliveTimer;
@ -277,11 +278,18 @@ class ElectrumClient {
Future<Map<String, dynamic>> getHeader({required int height}) async => Future<Map<String, dynamic>> getHeader({required int height}) async =>
await call(method: 'blockchain.block.get_header', params: [height]) as Map<String, dynamic>; await call(method: 'blockchain.block.get_header', params: [height]) as Map<String, dynamic>;
Future<Map<String, dynamic>> getTweaks({required int height, required int count}) async => BehaviorSubject<Object>? tweaksSubscribe({required int height}) {
await callWithTimeout( _id += 1;
method: 'blockchain.block.tweaks', return subscribe<Object>(
params: [height, count], id: 'blockchain.tweaks.subscribe',
timeout: 10000) as Map<String, dynamic>; method: 'blockchain.tweaks.subscribe',
params: [height],
);
}
Future<Map<String, dynamic>> getTweaks({required int height}) async =>
await callWithTimeout(method: 'blockchain.tweaks.get', params: [height], timeout: 10000)
as Map<String, dynamic>;
Future<double> estimatefee({required int p}) => Future<double> estimatefee({required int p}) =>
call(method: 'blockchain.estimatefee', params: [p]).then((dynamic result) { call(method: 'blockchain.estimatefee', params: [p]).then((dynamic result) {
@ -325,9 +333,6 @@ class ElectrumClient {
}); });
Future<List<int>> feeRates({BasedUtxoNetwork? network}) async { Future<List<int>> feeRates({BasedUtxoNetwork? network}) async {
if (network == BitcoinNetwork.testnet) {
return [1, 1, 1];
}
try { try {
final topDoubleString = await estimatefee(p: 1); final topDoubleString = await estimatefee(p: 1);
final middleDoubleString = await estimatefee(p: 5); final middleDoubleString = await estimatefee(p: 5);
@ -349,7 +354,7 @@ class ElectrumClient {
// "hex": "00000020890208a0ae3a3892aa047c5468725846577cfcd9b512b50000000000000000005dc2b02f2d297a9064ee103036c14d678f9afc7e3d9409cf53fd58b82e938e8ecbeca05a2d2103188ce804c4" // "hex": "00000020890208a0ae3a3892aa047c5468725846577cfcd9b512b50000000000000000005dc2b02f2d297a9064ee103036c14d678f9afc7e3d9409cf53fd58b82e938e8ecbeca05a2d2103188ce804c4"
// } // }
Future<int?> getCurrentBlockChainTip() => Future<int?> getCurrentBlockChainTip() =>
call(method: 'blockchain.headers.subscribe').then((result) { callWithTimeout(method: 'blockchain.headers.subscribe').then((result) {
if (result is Map<String, dynamic>) { if (result is Map<String, dynamic>) {
return result["height"] as int; return result["height"] as int;
} }
@ -357,6 +362,12 @@ class ElectrumClient {
return null; return null;
}); });
BehaviorSubject<Object>? chainTipSubscribe() {
_id += 1;
return subscribe<Object>(
id: 'blockchain.headers.subscribe', method: 'blockchain.headers.subscribe');
}
BehaviorSubject<Object>? chainTipUpdate() { BehaviorSubject<Object>? chainTipUpdate() {
_id += 1; _id += 1;
return subscribe<Object>( return subscribe<Object>(
@ -456,6 +467,11 @@ class ElectrumClient {
_tasks[id]?.subject?.add(params.last); _tasks[id]?.subject?.add(params.last);
break; break;
case 'blockchain.tweaks.subscribe':
case 'blockchain.headers.subscribe':
final params = request['params'] as List<dynamic>;
_tasks[method]?.subject?.add(params.last);
break;
default: default:
break; break;
} }

View file

@ -23,7 +23,7 @@ class ElectrumBalance extends Balance {
} }
int confirmed; int confirmed;
final int unconfirmed; int unconfirmed;
final int frozen; final int frozen;
@override @override

View file

@ -18,7 +18,7 @@ class ElectrumTransactionBundle {
} }
class ElectrumTransactionInfo extends TransactionInfo { class ElectrumTransactionInfo extends TransactionInfo {
List<BitcoinUnspent>? unspents; List<BitcoinSilentPaymentsUnspent>? unspents;
ElectrumTransactionInfo(this.type, ElectrumTransactionInfo(this.type,
{required String id, {required String id,
@ -178,9 +178,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
outputAddresses.isEmpty ? [] : outputAddresses.map((e) => e.toString()).toList(), outputAddresses.isEmpty ? [] : outputAddresses.map((e) => e.toString()).toList(),
to: data['to'] as String?, to: data['to'] as String?,
unspents: unspents unspents: unspents
.map((unspent) => BitcoinUnspent.fromJSON( .map((unspent) =>
BitcoinSilentPaymentAddressRecord.fromJSON(unspent['address_record'].toString()), BitcoinSilentPaymentsUnspent.fromJSON(null, unspent as Map<String, dynamic>))
unspent as Map<String, dynamic>))
.toList(), .toList(),
); );
} }

View file

@ -91,7 +91,13 @@ abstract class ElectrumWalletBase
transactionHistory = ElectrumTransactionHistory(walletInfo: walletInfo, password: password); transactionHistory = ElectrumTransactionHistory(walletInfo: walletInfo, password: password);
reaction((_) => syncStatus, (SyncStatus syncStatus) { reaction((_) => syncStatus, (SyncStatus syncStatus) {
if (syncStatus is! AttemptingSyncStatus)
silentPaymentsScanningActive = syncStatus is SyncingSyncStatus; silentPaymentsScanningActive = syncStatus is SyncingSyncStatus;
if (syncStatus is NotConnectedSyncStatus) {
// Needs to re-subscribe to all scripthashes when reconnected
_scripthashesUpdateSubject = {};
}
}); });
} }
@ -122,14 +128,7 @@ abstract class ElectrumWalletBase
@observable @observable
SyncStatus syncStatus; SyncStatus syncStatus;
List<String> get scriptHashes => walletAddresses.allAddresses Set<String> get addressesSet => walletAddresses.allAddresses.map((addr) => addr.address).toSet();
.map((addr) => scriptHash(addr.address, network: network))
.toList();
List<String> get publicScriptHashes => walletAddresses.allAddresses
.where((addr) => !addr.isHidden)
.map((addr) => scriptHash(addr.address, network: network))
.toList();
String get xpub => hd.base58!; String get xpub => hd.base58!;
@ -142,8 +141,8 @@ abstract class ElectrumWalletBase
@override @override
bool? isTestnet; bool? isTestnet;
@observable bool get hasSilentPaymentsScanning => type == WalletType.bitcoin;
bool hasSilentPaymentsScanning = false;
@observable @observable
bool nodeSupportsSilentPayments = true; bool nodeSupportsSilentPayments = true;
@observable @observable
@ -151,13 +150,14 @@ abstract class ElectrumWalletBase
@action @action
Future<void> setSilentPaymentsScanning(bool active) async { Future<void> setSilentPaymentsScanning(bool active) async {
syncStatus = AttemptingSyncStatus();
silentPaymentsScanningActive = active; silentPaymentsScanningActive = active;
if (active) { if (active) {
await _setInitialHeight(); await _setInitialHeight();
if ((currentChainTip ?? 0) > walletInfo.restoreHeight) { if (await currentChainTip > walletInfo.restoreHeight) {
_setListeners(walletInfo.restoreHeight, chainTip: currentChainTip); _setListeners(walletInfo.restoreHeight, chainTipParam: _currentChainTip);
} }
} else { } else {
_isolate?.then((runningIsolate) => runningIsolate.kill(priority: Isolate.immediate)); _isolate?.then((runningIsolate) => runningIsolate.kill(priority: Isolate.immediate));
@ -174,7 +174,11 @@ abstract class ElectrumWalletBase
} }
@observable @observable
int? currentChainTip; int? _currentChainTip;
@computed
Future<int> get currentChainTip async =>
_currentChainTip ?? await electrumClient.getCurrentBlockChainTip() ?? 0;
@override @override
BitcoinWalletKeys get keys => BitcoinWalletKeys get keys =>
@ -183,7 +187,9 @@ abstract class ElectrumWalletBase
String _password; String _password;
List<BitcoinUnspent> unspentCoins; List<BitcoinUnspent> unspentCoins;
List<int> _feeRates; List<int> _feeRates;
// ignore: prefer_final_fields
Map<String, BehaviorSubject<Object>?> _scripthashesUpdateSubject; Map<String, BehaviorSubject<Object>?> _scripthashesUpdateSubject;
// ignore: prefer_final_fields
BehaviorSubject<Object>? _chainTipUpdateSubject; BehaviorSubject<Object>? _chainTipUpdateSubject;
bool _isTransactionUpdating; bool _isTransactionUpdating;
Future<Isolate>? _isolate; Future<Isolate>? _isolate;
@ -201,8 +207,8 @@ abstract class ElectrumWalletBase
} }
@action @action
Future<void> _setListeners(int height, {int? chainTip, bool? doSingleScan}) async { Future<void> _setListeners(int height, {int? chainTipParam, bool? doSingleScan}) async {
final currentChainTip = chainTip ?? await electrumClient.getCurrentBlockChainTip() ?? 0; final chainTip = chainTipParam ?? await currentChainTip;
syncStatus = AttemptingSyncStatus(); syncStatus = AttemptingSyncStatus();
if (_isolate != null) { if (_isolate != null) {
@ -218,7 +224,7 @@ abstract class ElectrumWalletBase
silentAddress: walletAddresses.silentAddress!, silentAddress: walletAddresses.silentAddress!,
network: network, network: network,
height: height, height: height,
chainTip: currentChainTip, chainTip: chainTip,
electrumClient: ElectrumClient(), electrumClient: ElectrumClient(),
transactionHistoryIds: transactionHistory.transactions.keys.toList(), transactionHistoryIds: transactionHistory.transactions.keys.toList(),
node: ScanNode(node!.uri, node!.useSSL), node: ScanNode(node!.uri, node!.useSSL),
@ -237,12 +243,33 @@ abstract class ElectrumWalletBase
final tx = map.value; final tx = map.value;
if (tx.unspents != null) { if (tx.unspents != null) {
tx.unspents!.forEach((unspent) => walletAddresses.addSilentAddresses(
[unspent.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord]));
final existingTxInfo = transactionHistory.transactions[txid]; final existingTxInfo = transactionHistory.transactions[txid];
final txAlreadyExisted = existingTxInfo != null; final txAlreadyExisted = existingTxInfo != null;
void updateSilentAddressRecord(BitcoinSilentPaymentsUnspent unspent) {
final silentAddress = walletAddresses.silentAddress!;
final silentPaymentAddress = SilentPaymentAddress(
version: silentAddress.version,
B_scan: silentAddress.B_scan,
B_spend: unspent.silentPaymentLabel != null
? silentAddress.B_spend.tweakAdd(
BigintUtils.fromBytes(
BytesUtils.fromHexString(unspent.silentPaymentLabel!)),
)
: silentAddress.B_spend,
hrp: silentAddress.hrp,
);
final addressRecord = walletAddresses.silentAddresses.firstWhereOrNull(
(address) => address.address == silentPaymentAddress.toString());
addressRecord?.txCount += 1;
addressRecord?.balance += unspent.value;
walletAddresses.addSilentAddresses(
[unspent.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord],
);
}
// Updating tx after re-scanned // Updating tx after re-scanned
if (txAlreadyExisted) { if (txAlreadyExisted) {
existingTxInfo.amount = tx.amount; existingTxInfo.amount = tx.amount;
@ -258,37 +285,39 @@ abstract class ElectrumWalletBase
.toList(); .toList();
if (newUnspents.isNotEmpty) { if (newUnspents.isNotEmpty) {
newUnspents.forEach(updateSilentAddressRecord);
existingTxInfo.unspents ??= []; existingTxInfo.unspents ??= [];
existingTxInfo.unspents!.addAll(newUnspents); existingTxInfo.unspents!.addAll(newUnspents);
final amount = newUnspents.length > 1 final newAmount = newUnspents.length > 1
? newUnspents.map((e) => e.value).reduce((value, unspent) => value + unspent) ? newUnspents.map((e) => e.value).reduce((value, unspent) => value + unspent)
: newUnspents[0].value; : newUnspents[0].value;
if (existingTxInfo.direction == TransactionDirection.incoming) { if (existingTxInfo.direction == TransactionDirection.incoming) {
existingTxInfo.amount += amount; existingTxInfo.amount += newAmount;
} else {
existingTxInfo.amount = amount;
existingTxInfo.direction = TransactionDirection.incoming;
} }
// Updates existing TX
transactionHistory.addOne(existingTxInfo); transactionHistory.addOne(existingTxInfo);
// Update balance record
balance[currency]!.confirmed += newAmount;
} }
} else { } else {
final addressRecord = // else: First time seeing this TX after scanning
walletAddresses.silentAddresses.firstWhere((addr) => addr.address == tx.to); tx.unspents!.forEach(updateSilentAddressRecord);
addressRecord.txCount += 1;
// Add new TX record
transactionHistory.addMany(message); transactionHistory.addMany(message);
// Update balance record
balance[currency]!.confirmed += tx.amount;
} }
await transactionHistory.save(); await updateAllUnspents();
await updateUnspent();
await updateBalance();
} }
} }
} }
// check if is a SyncStatus type since "is SyncStatus" doesn't work here
if (message is SyncResponse) { if (message is SyncResponse) {
if (message.syncStatus is UnsupportedSyncStatus) { if (message.syncStatus is UnsupportedSyncStatus) {
nodeSupportsSilentPayments = false; nodeSupportsSilentPayments = false;
@ -296,7 +325,6 @@ abstract class ElectrumWalletBase
syncStatus = message.syncStatus; syncStatus = message.syncStatus;
walletInfo.restoreHeight = message.height; walletInfo.restoreHeight = message.height;
await walletInfo.save();
} }
} }
} }
@ -305,32 +333,25 @@ abstract class ElectrumWalletBase
@override @override
Future<void> startSync() async { Future<void> startSync() async {
try { try {
syncStatus = AttemptingSyncStatus(); syncStatus = SyncronizingSyncStatus();
if (hasSilentPaymentsScanning) { if (hasSilentPaymentsScanning) {
try {
await _setInitialHeight(); await _setInitialHeight();
} catch (_) {}
} }
if (silentPaymentsScanningActive) { await _subscribeForUpdates();
if ((currentChainTip ?? 0) > walletInfo.restoreHeight) {
_setListeners(walletInfo.restoreHeight, chainTip: currentChainTip); int finished = 0;
} void checkFinishedAllUpdates(void _) {
finished++;
if (finished == 2) syncStatus = SyncedSyncStatus();
} }
// Always await first as it discovers new addresses in the process
await updateTransactions(); await updateTransactions();
_subscribeForUpdates();
await updateUnspent();
await updateBalance();
_feeRates = await electrumClient.feeRates(network: network);
Timer.periodic( updateAllUnspents().then(checkFinishedAllUpdates);
const Duration(minutes: 1), (timer) async => _feeRates = await electrumClient.feeRates()); updateBalance().then(checkFinishedAllUpdates);
if (!silentPaymentsScanningActive || walletInfo.restoreHeight == currentChainTip) {
syncStatus = SyncedSyncStatus();
}
} catch (e, stacktrace) { } catch (e, stacktrace) {
print(stacktrace); print(stacktrace);
print(e.toString()); print(e.toString());
@ -338,29 +359,39 @@ abstract class ElectrumWalletBase
} }
} }
@action
Future<void> updateFeeRates() async {
final feeRates = await electrumClient.feeRates(network: network);
if (feeRates != [0, 0, 0]) {
_feeRates = feeRates;
}
}
Node? node; Node? node;
@action @action
@override @override
Future<void> connectToNode({required Node node}) async { Future<void> connectToNode({required Node node}) async {
final differentNode = this.node?.uri != node.uri || this.node?.useSSL != node.useSSL;
this.node = node; this.node = node;
try { try {
syncStatus = ConnectingSyncStatus(); syncStatus = ConnectingSyncStatus();
if (!electrumClient.isConnected) { electrumClient.onConnectionStatusChange = null;
await electrumClient.close(); await electrumClient.close();
}
await Timer(Duration(seconds: differentNode ? 0 : 10), () async {
electrumClient.onConnectionStatusChange = (bool isConnected) async { electrumClient.onConnectionStatusChange = (bool isConnected) async {
if (isConnected) { if (isConnected && syncStatus is! SyncedSyncStatus) {
syncStatus = ConnectedSyncStatus(); syncStatus = ConnectedSyncStatus();
} else if (isConnected == false) { } else if (!isConnected) {
syncStatus = LostConnectionSyncStatus(); syncStatus = LostConnectionSyncStatus();
} }
}; };
await electrumClient.connectToUri(node.uri, useSSL: node.useSSL); await electrumClient.connectToUri(node.uri, useSSL: node.useSSL);
});
} catch (e) { } catch (e) {
print(e.toString()); print(e.toString());
syncStatus = FailedSyncStatus(); syncStatus = FailedSyncStatus();
@ -436,8 +467,15 @@ abstract class ElectrumWalletBase
for (int i = 0; i < unspentCoins.length; i++) { for (int i = 0; i < unspentCoins.length; i++) {
final utx = unspentCoins[i]; final utx = unspentCoins[i];
if (utx.isSending) { if (utx.isSending && !utx.isFrozen) {
spendsCPFP = utx.confirmations == 0; if (hasSilentPayment) {
// Check inputs for shared secret derivation
if (utx.bitcoinAddressRecord.type == SegwitAddresType.p2wsh) {
throw BitcoinTransactionSilentPaymentsNotSupported();
}
}
if (!spendsCPFP) spendsCPFP = utx.confirmations == 0;
allInputsAmount += utx.value; allInputsAmount += utx.value;
@ -447,11 +485,10 @@ abstract class ElectrumWalletBase
bool? isSilentPayment = false; bool? isSilentPayment = false;
if (utx.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) { if (utx.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) {
final unspentAddress = utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord;
privkey = walletAddresses.silentAddress!.b_spend.tweakAdd( privkey = walletAddresses.silentAddress!.b_spend.tweakAdd(
BigintUtils.fromBytes( BigintUtils.fromBytes(
BytesUtils.fromHexString( BytesUtils.fromHexString(unspentAddress.silentPaymentTweak!),
(utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord).silentPaymentTweak!,
),
), ),
); );
spendsSilentPayment = true; spendsSilentPayment = true;
@ -464,7 +501,11 @@ abstract class ElectrumWalletBase
); );
} }
inputPrivKeyInfos.add(ECPrivateInfo(privkey, address.type == SegwitAddresType.p2tr)); inputPrivKeyInfos.add(ECPrivateInfo(
privkey,
address.type == SegwitAddresType.p2tr,
tweak: !isSilentPayment,
));
inputPubKeys.add(privkey.getPublic()); inputPubKeys.add(privkey.getPublic());
vinOutpoints.add(Outpoint(txid: utx.hash, index: utx.vout)); vinOutpoints.add(Outpoint(txid: utx.hash, index: utx.vout));
@ -520,6 +561,10 @@ abstract class ElectrumWalletBase
// Here, when sending all, the output amount equals to the input value - fee to fully spend every input on the transaction and have no amount left for change // Here, when sending all, the output amount equals to the input value - fee to fully spend every input on the transaction and have no amount left for change
int amount = allInputsAmount - fee; int amount = allInputsAmount - fee;
if (amount <= 0) {
throw BitcoinTransactionWrongBalanceException();
}
// Attempting to send less than the dust limit // Attempting to send less than the dust limit
if (_isBelowDust(amount)) { if (_isBelowDust(amount)) {
throw BitcoinTransactionNoDustException(); throw BitcoinTransactionNoDustException();
@ -580,11 +625,10 @@ abstract class ElectrumWalletBase
bool? isSilentPayment = false; bool? isSilentPayment = false;
if (utx.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) { if (utx.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) {
final unspentAddress = utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord;
privkey = walletAddresses.silentAddress!.b_spend.tweakAdd( privkey = walletAddresses.silentAddress!.b_spend.tweakAdd(
BigintUtils.fromBytes( BigintUtils.fromBytes(
BytesUtils.fromHexString( BytesUtils.fromHexString(unspentAddress.silentPaymentTweak!),
(utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord).silentPaymentTweak!,
),
), ),
); );
spendsSilentPayment = true; spendsSilentPayment = true;
@ -597,7 +641,11 @@ abstract class ElectrumWalletBase
); );
} }
inputPrivKeyInfos.add(ECPrivateInfo(privkey, address.type == SegwitAddresType.p2tr)); inputPrivKeyInfos.add(ECPrivateInfo(
privkey,
address.type == SegwitAddresType.p2tr,
tweak: !isSilentPayment,
));
inputPubKeys.add(privkey.getPublic()); inputPubKeys.add(privkey.getPublic());
vinOutpoints.add(Outpoint(txid: utx.hash, index: utx.vout)); vinOutpoints.add(Outpoint(txid: utx.hash, index: utx.vout));
@ -637,6 +685,16 @@ abstract class ElectrumWalletBase
int amountLeftForChangeAndFee = allInputsAmount - credentialsAmount; int amountLeftForChangeAndFee = allInputsAmount - credentialsAmount;
if (amountLeftForChangeAndFee <= 0) { if (amountLeftForChangeAndFee <= 0) {
if (!spendingAllCoins) {
return estimateTxForAmount(
credentialsAmount,
outputs,
feeRate,
inputsCount: utxos.length + 1,
memo: memo,
hasSilentPayment: hasSilentPayment,
);
}
throw BitcoinTransactionWrongBalanceException(); throw BitcoinTransactionWrongBalanceException();
} }
@ -684,6 +742,7 @@ abstract class ElectrumWalletBase
// Still has inputs to spend before failing // Still has inputs to spend before failing
if (!spendingAllCoins) { if (!spendingAllCoins) {
outputs.removeLast();
return estimateTxForAmount( return estimateTxForAmount(
credentialsAmount, credentialsAmount,
outputs, outputs,
@ -730,6 +789,7 @@ abstract class ElectrumWalletBase
outputs.removeLast(); outputs.removeLast();
} }
outputs.removeLast();
return estimateTxForAmount( return estimateTxForAmount(
credentialsAmount, credentialsAmount,
outputs, outputs,
@ -1025,7 +1085,8 @@ abstract class ElectrumWalletBase
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type); Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
Future<void> updateUnspent() async { @action
Future<void> updateAllUnspents() async {
List<BitcoinUnspent> updatedUnspentCoins = []; List<BitcoinUnspent> updatedUnspentCoins = [];
if (hasSilentPaymentsScanning) { if (hasSilentPaymentsScanning) {
@ -1037,20 +1098,9 @@ abstract class ElectrumWalletBase
}); });
} }
final addressesSet = walletAddresses.allAddresses.map((addr) => addr.address).toSet(); await Future.wait(walletAddresses.allAddresses.map((address) async {
updatedUnspentCoins.addAll(await fetchUnspent(address));
await Future.wait(walletAddresses.allAddresses.map((address) => electrumClient }));
.getListUnspentWithAddress(address.address, network)
.then((unspent) => Future.forEach<Map<String, dynamic>>(unspent, (unspent) async {
try {
final coin = BitcoinUnspent.fromJSON(address, unspent);
final tx = await fetchTransactionInfo(
hash: coin.hash, height: 0, myAddresses: addressesSet);
coin.isChange = tx?.direction == TransactionDirection.outgoing;
coin.confirmations = tx?.confirmations;
updatedUnspentCoins.add(coin);
} catch (_) {}
}))));
unspentCoins = updatedUnspentCoins; unspentCoins = updatedUnspentCoins;
@ -1072,6 +1122,7 @@ abstract class ElectrumWalletBase
coin.isFrozen = coinInfo.isFrozen; coin.isFrozen = coinInfo.isFrozen;
coin.isSending = coinInfo.isSending; coin.isSending = coinInfo.isSending;
coin.note = coinInfo.note; coin.note = coinInfo.note;
coin.bitcoinAddressRecord.balance += coinInfo.value;
} else { } else {
_addCoinInfo(coin); _addCoinInfo(coin);
} }
@ -1081,6 +1132,55 @@ abstract class ElectrumWalletBase
await _refreshUnspentCoinsInfo(); await _refreshUnspentCoinsInfo();
} }
@action
Future<void> updateUnspents(BitcoinAddressRecord address) async {
final newUnspentCoins = await fetchUnspent(address);
if (newUnspentCoins.isNotEmpty) {
unspentCoins.addAll(newUnspentCoins);
newUnspentCoins.forEach((coin) {
final coinInfoList = unspentCoinsInfo.values.where(
(element) =>
element.walletId.contains(id) &&
element.hash.contains(coin.hash) &&
element.vout == coin.vout,
);
if (coinInfoList.isNotEmpty) {
final coinInfo = coinInfoList.first;
coin.isFrozen = coinInfo.isFrozen;
coin.isSending = coinInfo.isSending;
coin.note = coinInfo.note;
coin.bitcoinAddressRecord.balance += coinInfo.value;
} else {
_addCoinInfo(coin);
}
});
}
}
@action
Future<List<BitcoinUnspent>> fetchUnspent(BitcoinAddressRecord address) async {
final unspents = await electrumClient.getListUnspent(address.getScriptHash(network));
List<BitcoinUnspent> updatedUnspentCoins = [];
await Future.wait(unspents.map((unspent) async {
try {
final coin = BitcoinUnspent.fromJSON(address, unspent);
final tx = await fetchTransactionInfo(hash: coin.hash, height: 0);
coin.isChange = address.isHidden;
coin.confirmations = tx?.confirmations;
updatedUnspentCoins.add(coin);
} catch (_) {}
}));
return updatedUnspentCoins;
}
@action @action
Future<void> _addCoinInfo(BitcoinUnspent coin) async { Future<void> _addCoinInfo(BitcoinUnspent coin) async {
final newInfo = UnspentCoinsInfo( final newInfo = UnspentCoinsInfo(
@ -1093,6 +1193,7 @@ abstract class ElectrumWalletBase
value: coin.value, value: coin.value,
vout: coin.vout, vout: coin.vout,
isChange: coin.isChange, isChange: coin.isChange,
isSilentPayment: coin is BitcoinSilentPaymentsUnspent,
); );
await unspentCoinsInfo.add(newInfo); await unspentCoinsInfo.add(newInfo);
@ -1309,8 +1410,8 @@ abstract class ElectrumWalletBase
time = status["block_time"] as int?; time = status["block_time"] as int?;
final height = status["block_height"] as int? ?? 0; final height = status["block_height"] as int? ?? 0;
confirmations = final tip = await currentChainTip;
height > 0 ? (await electrumClient.getCurrentBlockChainTip())! - height + 1 : 0; if (tip > 0) confirmations = height > 0 ? tip - height + 1 : 0;
} else { } else {
final verboseTransaction = await electrumClient.getTransactionRaw(hash: hash); final verboseTransaction = await electrumClient.getTransactionRaw(hash: hash);
@ -1335,18 +1436,15 @@ abstract class ElectrumWalletBase
} }
Future<ElectrumTransactionInfo?> fetchTransactionInfo( Future<ElectrumTransactionInfo?> fetchTransactionInfo(
{required String hash, {required String hash, required int height, bool? retryOnFailure}) async {
required int height,
required Set<String> myAddresses,
bool? retryOnFailure}) async {
try { try {
return ElectrumTransactionInfo.fromElectrumBundle( return ElectrumTransactionInfo.fromElectrumBundle(
await getTransactionExpanded(hash: hash), walletInfo.type, network, await getTransactionExpanded(hash: hash), walletInfo.type, network,
addresses: myAddresses, height: height); addresses: addressesSet, height: height);
} catch (e) { } catch (e) {
if (e is FormatException && retryOnFailure == true) { if (e is FormatException && retryOnFailure == true) {
await Future.delayed(const Duration(seconds: 2)); await Future.delayed(const Duration(seconds: 2));
return fetchTransactionInfo(hash: hash, height: height, myAddresses: myAddresses); return fetchTransactionInfo(hash: hash, height: height);
} }
return null; return null;
} }
@ -1356,11 +1454,15 @@ abstract class ElectrumWalletBase
Future<Map<String, ElectrumTransactionInfo>> fetchTransactions() async { Future<Map<String, ElectrumTransactionInfo>> fetchTransactions() async {
try { try {
final Map<String, ElectrumTransactionInfo> historiesWithDetails = {}; final Map<String, ElectrumTransactionInfo> historiesWithDetails = {};
final addressesSet = walletAddresses.allAddresses.map((addr) => addr.address).toSet();
currentChainTip ??= await electrumClient.getCurrentBlockChainTip() ?? 0;
await Future.wait(ADDRESS_TYPES.map( if (type == WalletType.bitcoin) {
(type) => fetchTransactionsForAddressType(addressesSet, historiesWithDetails, type))); await Future.wait(ADDRESS_TYPES
.map((type) => fetchTransactionsForAddressType(historiesWithDetails, type)));
} else if (type == WalletType.bitcoinCash) {
await fetchTransactionsForAddressType(historiesWithDetails, P2pkhAddressType.p2pkh);
} else if (type == WalletType.litecoin) {
await fetchTransactionsForAddressType(historiesWithDetails, SegwitAddresType.p2wpkh);
}
return historiesWithDetails; return historiesWithDetails;
} catch (e) { } catch (e) {
@ -1370,7 +1472,6 @@ abstract class ElectrumWalletBase
} }
Future<void> fetchTransactionsForAddressType( Future<void> fetchTransactionsForAddressType(
Set<String> addressesSet,
Map<String, ElectrumTransactionInfo> historiesWithDetails, Map<String, ElectrumTransactionInfo> historiesWithDetails,
BitcoinAddressType type, BitcoinAddressType type,
) async { ) async {
@ -1379,39 +1480,50 @@ abstract class ElectrumWalletBase
final receiveAddresses = addressesByType.where((addr) => addr.isHidden == false); final receiveAddresses = addressesByType.where((addr) => addr.isHidden == false);
await Future.wait(addressesByType.map((addressRecord) async { await Future.wait(addressesByType.map((addressRecord) async {
final history = await _fetchAddressHistory(addressRecord, addressesSet, currentChainTip!); final history = await _fetchAddressHistory(addressRecord, await currentChainTip);
if (history.isNotEmpty) { if (history.isNotEmpty) {
addressRecord.txCount = history.length; addressRecord.txCount = history.length;
historiesWithDetails.addAll(history); historiesWithDetails.addAll(history);
final matchedAddresses = addressRecord.isHidden ? hiddenAddresses : receiveAddresses; final matchedAddresses = addressRecord.isHidden ? hiddenAddresses : receiveAddresses;
final isLastUsedAddress = history.isNotEmpty && matchedAddresses.last == addressRecord; final isUsedAddressUnderGap = matchedAddresses.toList().indexOf(addressRecord) >=
matchedAddresses.length -
(addressRecord.isHidden
? ElectrumWalletAddressesBase.defaultChangeAddressesCount
: ElectrumWalletAddressesBase.defaultReceiveAddressesCount);
if (isLastUsedAddress) { if (isUsedAddressUnderGap) {
// The last address by gap limit is used, discover new addresses for the same address type final prevLength = walletAddresses.allAddresses.length;
// Discover new addresses for the same address type until the gap limit is respected
await walletAddresses.discoverAddresses( await walletAddresses.discoverAddresses(
matchedAddresses.toList(), matchedAddresses.toList(),
addressRecord.isHidden, addressRecord.isHidden,
(address, addressesSet) => _fetchAddressHistory(address, addressesSet, currentChainTip!) (address) async {
.then((history) => history.isNotEmpty ? address.address : null), await _subscribeForUpdates();
return _fetchAddressHistory(address, await currentChainTip)
.then((history) => history.isNotEmpty ? address.address : null);
},
type: type, type: type,
); );
// Continue until the last address by this address type is not used yet final newLength = walletAddresses.allAddresses.length;
await fetchTransactionsForAddressType(addressesSet, historiesWithDetails, type);
if (newLength > prevLength) {
await fetchTransactionsForAddressType(historiesWithDetails, type);
}
} }
} }
})); }));
} }
Future<Map<String, ElectrumTransactionInfo>> _fetchAddressHistory( Future<Map<String, ElectrumTransactionInfo>> _fetchAddressHistory(
BitcoinAddressRecord addressRecord, Set<String> addressesSet, int currentHeight) async { BitcoinAddressRecord addressRecord, int? currentHeight) async {
try { try {
final Map<String, ElectrumTransactionInfo> historiesWithDetails = {}; final Map<String, ElectrumTransactionInfo> historiesWithDetails = {};
final history = await electrumClient final history = await electrumClient.getHistory(addressRecord.getScriptHash(network));
.getHistory(addressRecord.scriptHash ?? addressRecord.updateScriptHash(network));
if (history.isNotEmpty) { if (history.isNotEmpty) {
addressRecord.setAsUsed(); addressRecord.setAsUsed();
@ -1425,14 +1537,13 @@ abstract class ElectrumWalletBase
if (height > 0) { if (height > 0) {
storedTx.height = height; storedTx.height = height;
// the tx's block itself is the first confirmation so add 1 // the tx's block itself is the first confirmation so add 1
storedTx.confirmations = currentHeight - height + 1; if (currentHeight != null) storedTx.confirmations = currentHeight - height + 1;
storedTx.isPending = storedTx.confirmations == 0; storedTx.isPending = storedTx.confirmations == 0;
} }
historiesWithDetails[txid] = storedTx; historiesWithDetails[txid] = storedTx;
} else { } else {
final tx = await fetchTransactionInfo( final tx = await fetchTransactionInfo(hash: txid, height: height, retryOnFailure: true);
hash: txid, height: height, myAddresses: addressesSet, retryOnFailure: true);
if (tx != null) { if (tx != null) {
historiesWithDetails[txid] = tx; historiesWithDetails[txid] = tx;
@ -1462,9 +1573,9 @@ abstract class ElectrumWalletBase
return; return;
} }
transactionHistory.transactions.values.forEach((tx) { transactionHistory.transactions.values.forEach((tx) async {
if (tx.unspents != null && currentChainTip != null) { if (tx.unspents != null && tx.unspents!.isNotEmpty && tx.height > 0) {
tx.confirmations = currentChainTip! - tx.height + 1; tx.confirmations = await currentChainTip - tx.height + 1;
} }
}); });
@ -1479,33 +1590,24 @@ abstract class ElectrumWalletBase
} }
} }
void _subscribeForUpdates() async { Future<void> _subscribeForUpdates() async {
scriptHashes.forEach((sh) async { final unsubscribedScriptHashes = walletAddresses.allAddresses.where(
(address) => !_scripthashesUpdateSubject.containsKey(address.getScriptHash(network)),
);
await Future.wait(unsubscribedScriptHashes.map((address) async {
final sh = address.getScriptHash(network);
await _scripthashesUpdateSubject[sh]?.close(); await _scripthashesUpdateSubject[sh]?.close();
_scripthashesUpdateSubject[sh] = electrumClient.scripthashUpdate(sh); _scripthashesUpdateSubject[sh] = await electrumClient.scripthashUpdate(sh);
_scripthashesUpdateSubject[sh]?.listen((event) async { _scripthashesUpdateSubject[sh]?.listen((event) async {
try { try {
await updateUnspent(); await updateUnspents(address);
await updateBalance();
await updateTransactions();
} catch (e, s) {
print(e.toString());
_onError?.call(FlutterErrorDetails(
exception: e,
stack: s,
library: this.runtimeType.toString(),
));
}
});
});
await _chainTipUpdateSubject?.close(); final newBalance = await _fetchBalance(sh);
_chainTipUpdateSubject = electrumClient.chainTipUpdate(); balance[currency]?.confirmed += newBalance.confirmed;
_chainTipUpdateSubject?.listen((_) async { balance[currency]?.unconfirmed += newBalance.unconfirmed;
try {
final currentHeight = await electrumClient.getCurrentBlockChainTip(); await _fetchAddressHistory(address, await currentChainTip);
if (currentHeight != null) walletInfo.restoreHeight = currentHeight;
_setListeners(walletInfo.restoreHeight, chainTip: currentHeight);
} catch (e, s) { } catch (e, s) {
print(e.toString()); print(e.toString());
_onError?.call(FlutterErrorDetails( _onError?.call(FlutterErrorDetails(
@ -1515,6 +1617,7 @@ abstract class ElectrumWalletBase
)); ));
} }
}); });
}));
} }
Future<ElectrumBalance> _fetchBalances() async { Future<ElectrumBalance> _fetchBalances() async {
@ -1534,17 +1637,15 @@ abstract class ElectrumWalletBase
if (hasSilentPaymentsScanning) { if (hasSilentPaymentsScanning) {
// Add values from unspent coins that are not fetched by the address list // Add values from unspent coins that are not fetched by the address list
// i.e. scanned silent payments // i.e. scanned silent payments
unspentCoinsInfo.values.forEach((info) { transactionHistory.transactions.values.forEach((tx) {
unspentCoins.forEach((element) { if (tx.unspents != null) {
if (element.hash == info.hash && tx.unspents!.forEach((unspent) {
element.bitcoinAddressRecord.address == info.address && if (unspent.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) {
element.value == info.value) { if (unspent.isFrozen) totalFrozen += unspent.value;
if (info.isFrozen) totalFrozen += element.value; totalConfirmed += unspent.value;
if (element.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) {
totalConfirmed += element.value;
}
} }
}); });
}
}); });
} }
@ -1567,6 +1668,13 @@ abstract class ElectrumWalletBase
confirmed: totalConfirmed, unconfirmed: totalUnconfirmed, frozen: totalFrozen); confirmed: totalConfirmed, unconfirmed: totalUnconfirmed, frozen: totalFrozen);
} }
Future<ElectrumBalance> _fetchBalance(String sh) async {
final balance = await electrumClient.getBalance(sh);
final confirmed = balance['confirmed'] as int? ?? 0;
final unconfirmed = balance['unconfirmed'] as int? ?? 0;
return ElectrumBalance(confirmed: confirmed, unconfirmed: unconfirmed, frozen: 0);
}
Future<void> updateBalance() async { Future<void> updateBalance() async {
balance[currency] = await _fetchBalances(); balance[currency] = await _fetchBalances();
await save(); await save();
@ -1597,10 +1705,19 @@ abstract class ElectrumWalletBase
} }
Future<void> _setInitialHeight() async { Future<void> _setInitialHeight() async {
currentChainTip = await electrumClient.getCurrentBlockChainTip(); if (_chainTipUpdateSubject != null) return;
if (currentChainTip != null && walletInfo.restoreHeight == 0) {
walletInfo.restoreHeight = currentChainTip!; _chainTipUpdateSubject = await electrumClient.chainTipSubscribe();
_chainTipUpdateSubject?.listen((e) async {
final event = e as Map<String, dynamic>;
final height = int.parse(event['height'].toString());
_currentChainTip = height;
if (_currentChainTip != null && _currentChainTip! > 0 && walletInfo.restoreHeight == 0) {
walletInfo.restoreHeight = _currentChainTip!;
} }
});
} }
static BasedUtxoNetwork _getNetwork(bitcoin.NetworkType networkType, CryptoCurrency? currency) { static BasedUtxoNetwork _getNetwork(bitcoin.NetworkType networkType, CryptoCurrency? currency) {
@ -1679,8 +1796,6 @@ class SyncResponse {
} }
Future<void> startRefresh(ScanData scanData) async { Future<void> startRefresh(ScanData scanData) async {
var cachedBlockchainHeight = scanData.chainTip;
Future<ElectrumClient> getElectrumConnection() async { Future<ElectrumClient> getElectrumConnection() async {
final electrumClient = scanData.electrumClient; final electrumClient = scanData.electrumClient;
if (!electrumClient.isConnected) { if (!electrumClient.isConnected) {
@ -1690,11 +1805,6 @@ Future<void> startRefresh(ScanData scanData) async {
return electrumClient; return electrumClient;
} }
Future<int> getUpdatedNodeHeight() async {
final electrumClient = await getElectrumConnection();
return await electrumClient.getCurrentBlockChainTip() ?? cachedBlockchainHeight;
}
var lastKnownBlockHeight = 0; var lastKnownBlockHeight = 0;
var initialSyncHeight = 0; var initialSyncHeight = 0;
@ -1714,60 +1824,54 @@ Future<void> startRefresh(ScanData scanData) async {
return; return;
} }
// Run this until no more blocks left to scan txs. At first this was recursive BehaviorSubject<Object>? tweaksSubscription = null;
// i.e. re-calling the startRefresh function but this was easier for the above values to retain
// their initial values
while (true) {
lastKnownBlockHeight = syncHeight; lastKnownBlockHeight = syncHeight;
SyncingSyncStatus syncingStatus; SyncingSyncStatus syncingStatus;
if (scanData.isSingleScan) { if (scanData.isSingleScan) {
syncingStatus = SyncingSyncStatus(1, 0); syncingStatus = SyncingSyncStatus(1, 0);
} else { } else {
syncingStatus = SyncingSyncStatus.fromHeightValues( syncingStatus =
await getUpdatedNodeHeight(), initialSyncHeight, syncHeight); SyncingSyncStatus.fromHeightValues(scanData.chainTip, initialSyncHeight, syncHeight);
} }
scanData.sendPort.send(SyncResponse(syncHeight, syncingStatus)); scanData.sendPort.send(SyncResponse(syncHeight, syncingStatus));
if (syncingStatus.blocksLeft <= 0 || (scanData.isSingleScan && scanData.height != syncHeight)) { if (syncingStatus.blocksLeft <= 0 || (scanData.isSingleScan && scanData.height != syncHeight)) {
scanData.sendPort.send(SyncResponse(await getUpdatedNodeHeight(), SyncedSyncStatus())); scanData.sendPort.send(SyncResponse(scanData.chainTip, SyncedSyncStatus()));
return; return;
} }
try { try {
final electrumClient = await getElectrumConnection(); final electrumClient = await getElectrumConnection();
// TODO: hardcoded values, if timed out decrease amount of blocks per request? if (tweaksSubscription == null) {
final scanningBlockCount =
scanData.isSingleScan ? 1 : (scanData.network == BitcoinNetwork.testnet ? 25 : 10);
Map<String, dynamic>? tweaks;
try { try {
tweaks = await electrumClient.getTweaks(height: syncHeight, count: scanningBlockCount); tweaksSubscription = await electrumClient.tweaksSubscribe(height: syncHeight);
} catch (e) {
if (e is RequestFailedTimeoutException) {
return scanData.sendPort.send(
SyncResponse(syncHeight, TimedOutSyncStatus()),
);
}
}
if (tweaks == null) { tweaksSubscription?.listen((t) {
return scanData.sendPort.send( final tweaks = t as Map<String, dynamic>;
SyncResponse(syncHeight, UnsupportedSyncStatus()),
);
}
if (tweaks.isEmpty) { if (tweaks.isEmpty) {
syncHeight += scanningBlockCount; syncHeight += 1;
continue; scanData.sendPort.send(
SyncResponse(
syncHeight,
SyncingSyncStatus.fromHeightValues(
currentChainTip,
initialSyncHeight,
syncHeight,
),
),
);
return;
} }
final blockHeights = tweaks.keys; final blockHeight = tweaks.keys.first.toString();
for (var i = 0; i < blockHeights.length; i++) {
try { try {
final blockHeight = blockHeights.elementAt(i).toString();
final blockTweaks = tweaks[blockHeight] as Map<String, dynamic>; final blockTweaks = tweaks[blockHeight] as Map<String, dynamic>;
for (var j = 0; j < blockTweaks.keys.length; j++) { for (var j = 0; j < blockTweaks.keys.length; j++) {
@ -1777,6 +1881,7 @@ Future<void> startRefresh(ScanData scanData) async {
final tweak = details["tweak"].toString(); final tweak = details["tweak"].toString();
try { try {
// scanOutputs called from rust here
final addToWallet = scanOutputs( final addToWallet = scanOutputs(
outputPubkeys.values.map((o) => o[0].toString()).toList(), outputPubkeys.values.map((o) => o[0].toString()).toList(),
tweak, tweak,
@ -1794,8 +1899,22 @@ Future<void> startRefresh(ScanData scanData) async {
continue; continue;
} }
addToWallet.forEach((label, value) async { // placeholder ElectrumTransactionInfo object to update values based on new scanned unspent(s)
(value as Map<String, dynamic>).forEach((output, tweak) async { final txInfo = ElectrumTransactionInfo(
WalletType.bitcoin,
id: txid,
height: syncHeight,
amount: 0,
fee: 0,
direction: TransactionDirection.incoming,
isPending: false,
date: DateTime.now(),
confirmations: scanData.chainTip - int.parse(blockHeight) + 1,
unspents: [],
);
addToWallet.forEach((label, value) {
(value as Map<String, dynamic>).forEach((output, tweak) {
final t_k = tweak.toString(); final t_k = tweak.toString();
final receivingOutputAddress = ECPublic.fromHex(output) final receivingOutputAddress = ECPublic.fromHex(output)
@ -1816,8 +1935,8 @@ Future<void> startRefresh(ScanData scanData) async {
int? amount; int? amount;
int? pos; int? pos;
outputPubkeys.entries.firstWhere((k) { outputPubkeys.entries.firstWhere((k) {
final matches = k.value[0] == output; final isMatchingOutput = k.value[0] == output;
if (matches) { if (isMatchingOutput) {
amount = int.parse(k.value[1].toString()); amount = int.parse(k.value[1].toString());
pos = int.parse(k.key.toString()); pos = int.parse(k.key.toString());
return true; return true;
@ -1825,61 +1944,62 @@ Future<void> startRefresh(ScanData scanData) async {
return false; return false;
}); });
final json = <String, dynamic>{ final unspent = BitcoinSilentPaymentsUnspent(
'address_record': receivedAddressRecord.toJSON(), receivedAddressRecord,
'tx_hash': txid, txid,
'value': amount!, amount!,
'tx_pos': pos!, pos!,
'silent_payment_tweak': t_k, silentPaymentTweak: t_k,
}; silentPaymentLabel: label == "None" ? null : label,
final tx = BitcoinUnspent.fromJSON(receivedAddressRecord, json);
final silentPaymentAddress = SilentPaymentAddress(
version: scanData.silentAddress.version,
B_scan: scanData.silentAddress.B_scan,
B_spend: label == "None"
? scanData.silentAddress.B_spend
: scanData.silentAddress.B_spend
.tweakAdd(BigintUtils.fromBytes(BytesUtils.fromHexString(label))),
hrp: scanData.silentAddress.hrp,
); );
final txInfo = ElectrumTransactionInfo( txInfo.unspents!.add(unspent);
WalletType.bitcoin, txInfo.amount += unspent.value;
id: tx.hash, });
height: syncHeight, });
amount: amount!,
fee: 0,
direction: TransactionDirection.incoming,
isPending: false,
date: DateTime.now(),
confirmations: await getUpdatedNodeHeight() - int.parse(blockHeight) + 1,
to: silentPaymentAddress.toString(),
unspents: [tx],
);
scanData.sendPort.send({txInfo.id: txInfo}); scanData.sendPort.send({txInfo.id: txInfo});
});
});
} catch (_) {} } catch (_) {}
} }
} catch (e, s) { } catch (_) {}
print([e, s]);
syncHeight += 1;
scanData.sendPort.send(
SyncResponse(
syncHeight,
SyncingSyncStatus.fromHeightValues(
currentChainTip,
initialSyncHeight,
syncHeight,
),
),
);
if (int.parse(blockHeight) >= scanData.chainTip) {
scanData.sendPort.send(SyncResponse(syncHeight, SyncedSyncStatus()));
} }
// Finished scanning block, add 1 to height and continue to next block in loop return;
syncHeight += 1; });
scanData.sendPort.send(SyncResponse(syncHeight, } catch (e) {
SyncingSyncStatus.fromHeightValues(currentChainTip, initialSyncHeight, syncHeight))); if (e is RequestFailedTimeoutException) {
return scanData.sendPort.send(
SyncResponse(syncHeight, TimedOutSyncStatus()),
);
}
}
}
if (tweaksSubscription == null) {
return scanData.sendPort.send(
SyncResponse(syncHeight, UnsupportedSyncStatus()),
);
} }
} catch (e, stacktrace) { } catch (e, stacktrace) {
print(stacktrace); print(stacktrace);
print(e.toString()); print(e.toString());
scanData.sendPort.send(SyncResponse(syncHeight, NotConnectedSyncStatus())); scanData.sendPort.send(SyncResponse(syncHeight, NotConnectedSyncStatus()));
break;
}
} }
} }

View file

@ -57,7 +57,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
b_spend: ECPrivate.fromHex(masterHd.derivePath(SPEND_PATH).privKey!), b_spend: ECPrivate.fromHex(masterHd.derivePath(SPEND_PATH).privKey!),
hrp: network == BitcoinNetwork.testnet ? 'tsp' : 'sp'); hrp: network == BitcoinNetwork.testnet ? 'tsp' : 'sp');
if (silentAddresses.length == 0) if (silentAddresses.length == 0) {
silentAddresses.add(BitcoinSilentPaymentAddressRecord( silentAddresses.add(BitcoinSilentPaymentAddressRecord(
silentAddress.toString(), silentAddress.toString(),
index: 0, index: 0,
@ -67,6 +67,16 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
network: network, network: network,
type: SilentPaymentsAddresType.p2sp, type: SilentPaymentsAddresType.p2sp,
)); ));
silentAddresses.add(BitcoinSilentPaymentAddressRecord(
silentAddress!.toLabeledSilentPaymentAddress(0).toString(),
index: 0,
isHidden: true,
name: "",
silentPaymentTweak: BytesUtils.toHexString(silentAddress!.generateLabel(0)),
network: network,
type: SilentPaymentsAddresType.p2sp,
));
}
} }
updateAddressesByMatch(); updateAddressesByMatch();
@ -446,7 +456,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
@action @action
Future<void> discoverAddresses(List<BitcoinAddressRecord> addressList, bool isHidden, Future<void> discoverAddresses(List<BitcoinAddressRecord> addressList, bool isHidden,
Future<String?> Function(BitcoinAddressRecord, Set<String>) getAddressHistory, Future<String?> Function(BitcoinAddressRecord) getAddressHistory,
{BitcoinAddressType type = SegwitAddresType.p2wpkh}) async { {BitcoinAddressType type = SegwitAddresType.p2wpkh}) async {
if (!isHidden) { if (!isHidden) {
_validateSideHdAddresses(addressList.toList()); _validateSideHdAddresses(addressList.toList());
@ -456,8 +466,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
startIndex: addressList.length, isHidden: isHidden, type: type); startIndex: addressList.length, isHidden: isHidden, type: type);
addAddresses(newAddresses); addAddresses(newAddresses);
final addressesWithHistory = await Future.wait(newAddresses final addressesWithHistory = await Future.wait(newAddresses.map(getAddressHistory));
.map((addr) => getAddressHistory(addr, _addresses.map((e) => e.address).toSet())));
final isLastAddressUsed = addressesWithHistory.last == addressList.last.address; final isLastAddressUsed = addressesWithHistory.last == addressList.last.address;
if (isLastAddressUsed) { if (isLastAddressUsed) {

View file

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

View file

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

View file

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

View file

@ -31,6 +31,11 @@ class SyncedSyncStatus extends SyncStatus {
double progress() => 1.0; double progress() => 1.0;
} }
class SyncronizingSyncStatus extends SyncStatus {
@override
double progress() => 0.0;
}
class NotConnectedSyncStatus extends SyncStatus { class NotConnectedSyncStatus extends SyncStatus {
const NotConnectedSyncStatus(); const NotConnectedSyncStatus();
@ -43,10 +48,7 @@ class AttemptingSyncStatus extends SyncStatus {
double progress() => 0.0; double progress() => 0.0;
} }
class FailedSyncStatus extends SyncStatus { class FailedSyncStatus extends NotConnectedSyncStatus {}
@override
double progress() => 1.0;
}
class ConnectingSyncStatus extends SyncStatus { class ConnectingSyncStatus extends SyncStatus {
@override @override
@ -58,21 +60,14 @@ class ConnectedSyncStatus extends SyncStatus {
double progress() => 0.0; double progress() => 0.0;
} }
class UnsupportedSyncStatus extends SyncStatus { class UnsupportedSyncStatus extends NotConnectedSyncStatus {}
@override
double progress() => 1.0;
}
class TimedOutSyncStatus extends SyncStatus { class TimedOutSyncStatus extends NotConnectedSyncStatus {
@override
double progress() => 1.0;
@override @override
String toString() => 'Timed out'; String toString() => 'Timed out';
} }
class LostConnectionSyncStatus extends SyncStatus { class LostConnectionSyncStatus extends NotConnectedSyncStatus {
@override
double progress() => 1.0;
@override @override
String toString() => 'Reconnecting'; String toString() => 'Reconnecting';
} }

View file

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

View file

@ -187,7 +187,7 @@ class CWBitcoin extends Bitcoin {
Future<void> updateUnspents(Object wallet) async { Future<void> updateUnspents(Object wallet) async {
final bitcoinWallet = wallet as ElectrumWallet; final bitcoinWallet = wallet as ElectrumWallet;
await bitcoinWallet.updateUnspent(); await bitcoinWallet.updateAllUnspents();
} }
WalletService createBitcoinWalletService( WalletService createBitcoinWalletService(
@ -386,4 +386,10 @@ class CWBitcoin extends Bitcoin {
final bitcoinWallet = wallet as ElectrumWallet; final bitcoinWallet = wallet as ElectrumWallet;
bitcoinWallet.walletAddresses.deleteSilentPaymentAddress(address); bitcoinWallet.walletAddresses.deleteSilentPaymentAddress(address);
} }
@override
Future<void> updateFeeRates(Object wallet) async {
final bitcoinWallet = wallet as ElectrumWallet;
await bitcoinWallet.updateFeeRates();
}
} }

View file

@ -44,5 +44,9 @@ String syncStatusTitle(SyncStatus syncStatus) {
return S.current.sync_status_timed_out; return S.current.sync_status_timed_out;
} }
if (syncStatus is SyncronizingSyncStatus) {
return S.current.sync_status_syncronizing;
}
return ''; return '';
} }

View file

@ -818,14 +818,16 @@ Future<void> checkCurrentNodes(
} }
if (currentBitcoinElectrumServer == null) { if (currentBitcoinElectrumServer == null) {
final cakeWalletElectrum = Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin); final cakeWalletElectrum =
Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin, useSSL: true);
await nodeSource.add(cakeWalletElectrum); await nodeSource.add(cakeWalletElectrum);
await sharedPreferences.setInt( await sharedPreferences.setInt(
PreferencesKey.currentBitcoinElectrumSererIdKey, cakeWalletElectrum.key as int); PreferencesKey.currentBitcoinElectrumSererIdKey, cakeWalletElectrum.key as int);
} }
if (currentLitecoinElectrumServer == null) { if (currentLitecoinElectrumServer == null) {
final cakeWalletElectrum = Node(uri: cakeWalletLitecoinElectrumUri, type: WalletType.litecoin); final cakeWalletElectrum =
Node(uri: cakeWalletLitecoinElectrumUri, type: WalletType.litecoin, useSSL: true);
await nodeSource.add(cakeWalletElectrum); await nodeSource.add(cakeWalletElectrum);
await sharedPreferences.setInt( await sharedPreferences.setInt(
PreferencesKey.currentLitecoinElectrumSererIdKey, cakeWalletElectrum.key as int); PreferencesKey.currentLitecoinElectrumSererIdKey, cakeWalletElectrum.key as int);
@ -860,7 +862,8 @@ Future<void> checkCurrentNodes(
} }
if (currentBitcoinCashNodeServer == null) { if (currentBitcoinCashNodeServer == null) {
final node = Node(uri: cakeWalletBitcoinCashDefaultNodeUri, type: WalletType.bitcoinCash); final node =
Node(uri: cakeWalletBitcoinCashDefaultNodeUri, type: WalletType.bitcoinCash, useSSL: true);
await nodeSource.add(node); await nodeSource.add(node);
await sharedPreferences.setInt(PreferencesKey.currentBitcoinCashNodeIdKey, node.key as int); await sharedPreferences.setInt(PreferencesKey.currentBitcoinCashNodeIdKey, node.key as int);
} }
@ -888,7 +891,8 @@ Future<void> resetBitcoinElectrumServer(
.firstWhereOrNull((node) => node.uriRaw.toString() == cakeWalletBitcoinElectrumUri); .firstWhereOrNull((node) => node.uriRaw.toString() == cakeWalletBitcoinElectrumUri);
if (cakeWalletNode == null) { if (cakeWalletNode == null) {
cakeWalletNode = Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin); cakeWalletNode =
Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin, useSSL: true);
await nodeSource.add(cakeWalletNode); await nodeSource.add(cakeWalletNode);
} }

View file

@ -146,7 +146,7 @@ class AddressCell extends StatelessWidget {
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,
children: [ children: [
Text( Text(
'${S.of(context).balance}: $txCount', '${S.of(context).balance}: $balance',
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/core/auth_service.dart'; import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/entities/contact_record.dart'; import 'package:cake_wallet/entities/contact_record.dart';
import 'package:cake_wallet/entities/fiat_currency.dart'; import 'package:cake_wallet/entities/fiat_currency.dart';
@ -400,6 +401,10 @@ class SendPage extends BasePage {
return; return;
} }
if (sendViewModel.isElectrumWallet) {
bitcoin!.updateFeeRates(sendViewModel.wallet);
}
reaction((_) => sendViewModel.state, (ExecutionState state) { reaction((_) => sendViewModel.state, (ExecutionState state) {
if (state is FailureState) { if (state is FailureState) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
@ -456,10 +461,12 @@ class SendPage extends BasePage {
sendViewModel.selectedCryptoCurrency.toString()); sendViewModel.selectedCryptoCurrency.toString());
final waitMessage = sendViewModel.walletType == WalletType.solana final waitMessage = sendViewModel.walletType == WalletType.solana
? '. ${S.of(_dialogContext).waitFewSecondForTxUpdate}' : ''; ? '. ${S.of(_dialogContext).waitFewSecondForTxUpdate}'
: '';
final newContactMessage = newContactAddress != null final newContactMessage = newContactAddress != null
? '\n${S.of(_dialogContext).add_contact_to_address_book}' : ''; ? '\n${S.of(_dialogContext).add_contact_to_address_book}'
: '';
final alertContent = final alertContent =
"$successMessage$waitMessage$newContactMessage"; "$successMessage$waitMessage$newContactMessage";

View file

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

View file

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

View file

@ -567,9 +567,15 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
if (error is TransactionCommitFailedVoutNegative) { if (error is TransactionCommitFailedVoutNegative) {
return S.current.tx_rejected_vout_negative; return S.current.tx_rejected_vout_negative;
} }
if (error is TransactionCommitFailedBIP68Final) {
return S.current.tx_rejected_bip68_final;
}
if (error is TransactionNoDustOnChangeException) { if (error is TransactionNoDustOnChangeException) {
return S.current.tx_commit_exception_no_dust_on_change(error.min, error.max); return S.current.tx_commit_exception_no_dust_on_change(error.min, error.max);
} }
if (error is TransactionInputNotSupported) {
return S.current.tx_invalid_input;
}
} }
return errorMessage; return errorMessage;

View file

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

View file

@ -86,11 +86,14 @@ abstract class UnspentCoinsListViewModelBase with Store {
@action @action
void _updateUnspentCoinsInfo() { void _updateUnspentCoinsInfo() {
_items.clear(); _items.clear();
_items.addAll(_getUnspents().map((elem) {
List<UnspentCoinsItem> unspents = [];
_getUnspents().forEach((elem) {
try {
final info = final info =
getUnspentCoinInfo(elem.hash, elem.address, elem.value, elem.vout, elem.keyImage); getUnspentCoinInfo(elem.hash, elem.address, elem.value, elem.vout, elem.keyImage);
return UnspentCoinsItem( unspents.add(UnspentCoinsItem(
address: elem.address, address: elem.address,
amount: '${formatAmountToString(elem.value)} ${wallet.currency.title}', amount: '${formatAmountToString(elem.value)} ${wallet.currency.title}',
hash: elem.hash, hash: elem.hash,
@ -101,7 +104,14 @@ abstract class UnspentCoinsListViewModelBase with Store {
vout: elem.vout, vout: elem.vout,
keyImage: elem.keyImage, keyImage: elem.keyImage,
isChange: elem.isChange, isChange: elem.isChange,
); isSilentPayment: info.isSilentPayment ?? false,
})); ));
} catch (e, s) {
print(s);
print(e.toString());
}
});
_items.addAll(unspents);
} }
} }

View file

@ -316,8 +316,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
if (isElectrumWallet) { if (isElectrumWallet) {
if (bitcoin!.hasSelectedSilentPayments(wallet)) { if (bitcoin!.hasSelectedSilentPayments(wallet)) {
final addressItems = bitcoin!.getSilentPaymentAddresses(wallet).map((address) { final addressItems = bitcoin!.getSilentPaymentAddresses(wallet).map((address) {
// Silent Payments index 0 is change per BIP final isPrimary = address.index == 0;
final isPrimary = address.index == 1;
return WalletAddressListItem( return WalletAddressListItem(
id: address.index, id: address.index,
@ -335,11 +334,9 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
final receivedAddressItems = final receivedAddressItems =
bitcoin!.getSilentPaymentReceivedAddresses(wallet).map((address) { bitcoin!.getSilentPaymentReceivedAddresses(wallet).map((address) {
final isPrimary = address.index == 0;
return WalletAddressListItem( return WalletAddressListItem(
id: address.index, id: address.index,
isPrimary: isPrimary, isPrimary: false,
name: address.name, name: address.name,
address: address.address, address: address.address,
txCount: address.txCount, txCount: address.txCount,

View file

@ -745,8 +745,10 @@
"trusted": "موثوق به", "trusted": "موثوق به",
"tx_commit_exception_no_dust_on_change": "يتم رفض المعاملة مع هذا المبلغ. باستخدام هذه العملات المعدنية ، يمكنك إرسال ${min} دون تغيير أو ${max} الذي يعيد التغيير.", "tx_commit_exception_no_dust_on_change": "يتم رفض المعاملة مع هذا المبلغ. باستخدام هذه العملات المعدنية ، يمكنك إرسال ${min} دون تغيير أو ${max} الذي يعيد التغيير.",
"tx_commit_failed": "فشل ارتكاب المعاملة. يرجى الاتصال بالدعم.", "tx_commit_failed": "فشل ارتكاب المعاملة. يرجى الاتصال بالدعم.",
"tx_invalid_input": "أنت تستخدم نوع الإدخال الخاطئ لهذا النوع من الدفع",
"tx_no_dust_exception": "يتم رفض المعاملة عن طريق إرسال مبلغ صغير جدًا. يرجى محاولة زيادة المبلغ.", "tx_no_dust_exception": "يتم رفض المعاملة عن طريق إرسال مبلغ صغير جدًا. يرجى محاولة زيادة المبلغ.",
"tx_not_enough_inputs_exception": "لا يكفي المدخلات المتاحة. الرجاء تحديد المزيد تحت التحكم في العملة", "tx_not_enough_inputs_exception": "لا يكفي المدخلات المتاحة. الرجاء تحديد المزيد تحت التحكم في العملة",
"tx_rejected_bip68_final": "تحتوي المعاملة على مدخلات غير مؤكدة وفشلت في استبدال الرسوم.",
"tx_rejected_dust_change": "المعاملة التي يتم رفضها بموجب قواعد الشبكة ، ومبلغ التغيير المنخفض (الغبار). حاول إرسال كل أو تقليل المبلغ.", "tx_rejected_dust_change": "المعاملة التي يتم رفضها بموجب قواعد الشبكة ، ومبلغ التغيير المنخفض (الغبار). حاول إرسال كل أو تقليل المبلغ.",
"tx_rejected_dust_output": "المعاملة التي يتم رفضها بموجب قواعد الشبكة ، وكمية الإخراج المنخفض (الغبار). يرجى زيادة المبلغ.", "tx_rejected_dust_output": "المعاملة التي يتم رفضها بموجب قواعد الشبكة ، وكمية الإخراج المنخفض (الغبار). يرجى زيادة المبلغ.",
"tx_rejected_dust_output_send_all": "المعاملة التي يتم رفضها بموجب قواعد الشبكة ، وكمية الإخراج المنخفض (الغبار). يرجى التحقق من رصيد العملات المعدنية المحددة تحت التحكم في العملة.", "tx_rejected_dust_output_send_all": "المعاملة التي يتم رفضها بموجب قواعد الشبكة ، وكمية الإخراج المنخفض (الغبار). يرجى التحقق من رصيد العملات المعدنية المحددة تحت التحكم في العملة.",

View file

@ -745,8 +745,10 @@
"trusted": "Надежден", "trusted": "Надежден",
"tx_commit_exception_no_dust_on_change": "Сделката се отхвърля с тази сума. С тези монети можете да изпратите ${min} без промяна или ${max}, която връща промяна.", "tx_commit_exception_no_dust_on_change": "Сделката се отхвърля с тази сума. С тези монети можете да изпратите ${min} без промяна или ${max}, която връща промяна.",
"tx_commit_failed": "Компетацията на транзакцията не успя. Моля, свържете се с поддръжката.", "tx_commit_failed": "Компетацията на транзакцията не успя. Моля, свържете се с поддръжката.",
"tx_invalid_input": "Използвате грешен тип вход за този тип плащане",
"tx_no_dust_exception": "Сделката се отхвърля чрез изпращане на сума твърде малка. Моля, опитайте да увеличите сумата.", "tx_no_dust_exception": "Сделката се отхвърля чрез изпращане на сума твърде малка. Моля, опитайте да увеличите сумата.",
"tx_not_enough_inputs_exception": "Няма достатъчно налични входове. Моля, изберете повече под контрол на монети", "tx_not_enough_inputs_exception": "Няма достатъчно налични входове. Моля, изберете повече под контрол на монети",
"tx_rejected_bip68_final": "Сделката има непотвърдени входове и не успя да се замени с такса.",
"tx_rejected_dust_change": "Транзакция, отхвърлена от мрежови правила, ниска сума на промяна (прах). Опитайте да изпратите всички или да намалите сумата.", "tx_rejected_dust_change": "Транзакция, отхвърлена от мрежови правила, ниска сума на промяна (прах). Опитайте да изпратите всички или да намалите сумата.",
"tx_rejected_dust_output": "Транзакция, отхвърлена от мрежови правила, ниска стойност на изхода (прах). Моля, увеличете сумата.", "tx_rejected_dust_output": "Транзакция, отхвърлена от мрежови правила, ниска стойност на изхода (прах). Моля, увеличете сумата.",
"tx_rejected_dust_output_send_all": "Транзакция, отхвърлена от мрежови правила, ниска стойност на изхода (прах). Моля, проверете баланса на монетите, избрани под контрол на монети.", "tx_rejected_dust_output_send_all": "Транзакция, отхвърлена от мрежови правила, ниска стойност на изхода (прах). Моля, проверете баланса на монетите, избрани под контрол на монети.",

View file

@ -745,8 +745,10 @@
"trusted": "Důvěřovat", "trusted": "Důvěřovat",
"tx_commit_exception_no_dust_on_change": "Transakce je zamítnuta s touto částkou. S těmito mincemi můžete odeslat ${min} bez změny nebo ${max}, které se vrátí změna.", "tx_commit_exception_no_dust_on_change": "Transakce je zamítnuta s touto částkou. S těmito mincemi můžete odeslat ${min} bez změny nebo ${max}, které se vrátí změna.",
"tx_commit_failed": "Transakce COMPORT selhala. Kontaktujte prosím podporu.", "tx_commit_failed": "Transakce COMPORT selhala. Kontaktujte prosím podporu.",
"tx_invalid_input": "Pro tento typ platby používáte nesprávný typ vstupu",
"tx_no_dust_exception": "Transakce je zamítnuta odesláním příliš malé. Zkuste prosím zvýšit částku.", "tx_no_dust_exception": "Transakce je zamítnuta odesláním příliš malé. Zkuste prosím zvýšit částku.",
"tx_not_enough_inputs_exception": "Není k dispozici dostatek vstupů. Vyberte prosím více pod kontrolou mincí", "tx_not_enough_inputs_exception": "Není k dispozici dostatek vstupů. Vyberte prosím více pod kontrolou mincí",
"tx_rejected_bip68_final": "Transakce má nepotvrzené vstupy a nepodařilo se nahradit poplatkem.",
"tx_rejected_dust_change": "Transakce zamítnuta podle síťových pravidel, množství nízké změny (prach). Zkuste odeslat vše nebo snížit částku.", "tx_rejected_dust_change": "Transakce zamítnuta podle síťových pravidel, množství nízké změny (prach). Zkuste odeslat vše nebo snížit částku.",
"tx_rejected_dust_output": "Transakce zamítnuta síťovými pravidly, nízkým množstvím výstupu (prach). Zvyšte prosím částku.", "tx_rejected_dust_output": "Transakce zamítnuta síťovými pravidly, nízkým množstvím výstupu (prach). Zvyšte prosím částku.",
"tx_rejected_dust_output_send_all": "Transakce zamítnuta síťovými pravidly, nízkým množstvím výstupu (prach). Zkontrolujte prosím zůstatek mincí vybraných pod kontrolou mincí.", "tx_rejected_dust_output_send_all": "Transakce zamítnuta síťovými pravidly, nízkým množstvím výstupu (prach). Zkontrolujte prosím zůstatek mincí vybraných pod kontrolou mincí.",

View file

@ -746,8 +746,10 @@
"trusted": "Vertrauenswürdige", "trusted": "Vertrauenswürdige",
"tx_commit_exception_no_dust_on_change": "Die Transaktion wird diesen Betrag abgelehnt. Mit diesen Münzen können Sie ${min} ohne Veränderung oder ${max} senden, die Änderungen zurückgeben.", "tx_commit_exception_no_dust_on_change": "Die Transaktion wird diesen Betrag abgelehnt. Mit diesen Münzen können Sie ${min} ohne Veränderung oder ${max} senden, die Änderungen zurückgeben.",
"tx_commit_failed": "Transaktionsausschüsse ist fehlgeschlagen. Bitte wenden Sie sich an Support.", "tx_commit_failed": "Transaktionsausschüsse ist fehlgeschlagen. Bitte wenden Sie sich an Support.",
"tx_invalid_input": "Sie verwenden den falschen Eingangstyp für diese Art von Zahlung",
"tx_no_dust_exception": "Die Transaktion wird abgelehnt, indem eine Menge zu klein gesendet wird. Bitte versuchen Sie, die Menge zu erhöhen.", "tx_no_dust_exception": "Die Transaktion wird abgelehnt, indem eine Menge zu klein gesendet wird. Bitte versuchen Sie, die Menge zu erhöhen.",
"tx_not_enough_inputs_exception": "Nicht genügend Eingänge verfügbar. Bitte wählen Sie mehr unter Münzkontrolle aus", "tx_not_enough_inputs_exception": "Nicht genügend Eingänge verfügbar. Bitte wählen Sie mehr unter Münzkontrolle aus",
"tx_rejected_bip68_final": "Die Transaktion hat unbestätigte Inputs und konnte nicht durch Gebühr ersetzt werden.",
"tx_rejected_dust_change": "Transaktion abgelehnt durch Netzwerkregeln, niedriger Änderungsbetrag (Staub). Versuchen Sie, alle zu senden oder die Menge zu reduzieren.", "tx_rejected_dust_change": "Transaktion abgelehnt durch Netzwerkregeln, niedriger Änderungsbetrag (Staub). Versuchen Sie, alle zu senden oder die Menge zu reduzieren.",
"tx_rejected_dust_output": "Transaktion durch Netzwerkregeln, niedriger Ausgangsmenge (Staub) abgelehnt. Bitte erhöhen Sie den Betrag.", "tx_rejected_dust_output": "Transaktion durch Netzwerkregeln, niedriger Ausgangsmenge (Staub) abgelehnt. Bitte erhöhen Sie den Betrag.",
"tx_rejected_dust_output_send_all": "Transaktion durch Netzwerkregeln, niedriger Ausgangsmenge (Staub) abgelehnt. Bitte überprüfen Sie den Gleichgewicht der unter Münzkontrolle ausgewählten Münzen.", "tx_rejected_dust_output_send_all": "Transaktion durch Netzwerkregeln, niedriger Ausgangsmenge (Staub) abgelehnt. Bitte überprüfen Sie den Gleichgewicht der unter Münzkontrolle ausgewählten Münzen.",

View file

@ -745,8 +745,10 @@
"trusted": "Trusted", "trusted": "Trusted",
"tx_commit_exception_no_dust_on_change": "The transaction is rejected with this amount. With these coins you can send ${min} without change or ${max} that returns change.", "tx_commit_exception_no_dust_on_change": "The transaction is rejected with this amount. With these coins you can send ${min} without change or ${max} that returns change.",
"tx_commit_failed": "Transaction commit failed. Please contact support.", "tx_commit_failed": "Transaction commit failed. Please contact support.",
"tx_invalid_input": "You are using the wrong input type for this type of payment",
"tx_no_dust_exception": "The transaction is rejected by sending an amount too small. Please try increasing the amount.", "tx_no_dust_exception": "The transaction is rejected by sending an amount too small. Please try increasing the amount.",
"tx_not_enough_inputs_exception": "Not enough inputs available. Please select more under Coin Control", "tx_not_enough_inputs_exception": "Not enough inputs available. Please select more under Coin Control",
"tx_rejected_bip68_final": "Transaction has unconfirmed inputs and failed to replace by fee.",
"tx_rejected_dust_change": "Transaction rejected by network rules, low change amount (dust). Try sending ALL or reducing the amount.", "tx_rejected_dust_change": "Transaction rejected by network rules, low change amount (dust). Try sending ALL or reducing the amount.",
"tx_rejected_dust_output": "Transaction rejected by network rules, low output amount (dust). Please increase the amount.", "tx_rejected_dust_output": "Transaction rejected by network rules, low output amount (dust). Please increase the amount.",
"tx_rejected_dust_output_send_all": "Transaction rejected by network rules, low output amount (dust). Please check the balance of coins selected under Coin Control.", "tx_rejected_dust_output_send_all": "Transaction rejected by network rules, low output amount (dust). Please check the balance of coins selected under Coin Control.",

View file

@ -746,8 +746,10 @@
"trusted": "de confianza", "trusted": "de confianza",
"tx_commit_exception_no_dust_on_change": "La transacción se rechaza con esta cantidad. Con estas monedas puede enviar ${min} sin cambios o ${max} que devuelve el cambio.", "tx_commit_exception_no_dust_on_change": "La transacción se rechaza con esta cantidad. Con estas monedas puede enviar ${min} sin cambios o ${max} que devuelve el cambio.",
"tx_commit_failed": "La confirmación de transacción falló. Póngase en contacto con el soporte.", "tx_commit_failed": "La confirmación de transacción falló. Póngase en contacto con el soporte.",
"tx_invalid_input": "Está utilizando el tipo de entrada incorrecto para este tipo de pago",
"tx_no_dust_exception": "La transacción se rechaza enviando una cantidad demasiado pequeña. Intente aumentar la cantidad.", "tx_no_dust_exception": "La transacción se rechaza enviando una cantidad demasiado pequeña. Intente aumentar la cantidad.",
"tx_not_enough_inputs_exception": "No hay suficientes entradas disponibles. Seleccione más bajo control de monedas", "tx_not_enough_inputs_exception": "No hay suficientes entradas disponibles. Seleccione más bajo control de monedas",
"tx_rejected_bip68_final": "La transacción tiene entradas no confirmadas y no ha podido reemplazar por tarifa.",
"tx_rejected_dust_change": "Transacción rechazada por reglas de red, bajo cambio de cambio (polvo). Intente enviar todo o reducir la cantidad.", "tx_rejected_dust_change": "Transacción rechazada por reglas de red, bajo cambio de cambio (polvo). Intente enviar todo o reducir la cantidad.",
"tx_rejected_dust_output": "Transacción rechazada por reglas de red, baja cantidad de salida (polvo). Aumente la cantidad.", "tx_rejected_dust_output": "Transacción rechazada por reglas de red, baja cantidad de salida (polvo). Aumente la cantidad.",
"tx_rejected_dust_output_send_all": "Transacción rechazada por reglas de red, baja cantidad de salida (polvo). Verifique el saldo de monedas seleccionadas bajo control de monedas.", "tx_rejected_dust_output_send_all": "Transacción rechazada por reglas de red, baja cantidad de salida (polvo). Verifique el saldo de monedas seleccionadas bajo control de monedas.",

View file

@ -745,8 +745,10 @@
"trusted": "de confiance", "trusted": "de confiance",
"tx_commit_exception_no_dust_on_change": "La transaction est rejetée avec ce montant. Avec ces pièces, vous pouvez envoyer ${min} sans changement ou ${max} qui renvoie le changement.", "tx_commit_exception_no_dust_on_change": "La transaction est rejetée avec ce montant. Avec ces pièces, vous pouvez envoyer ${min} sans changement ou ${max} qui renvoie le changement.",
"tx_commit_failed": "La validation de la transaction a échoué. Veuillez contacter l'assistance.", "tx_commit_failed": "La validation de la transaction a échoué. Veuillez contacter l'assistance.",
"tx_invalid_input": "Vous utilisez le mauvais type d'entrée pour ce type de paiement",
"tx_no_dust_exception": "La transaction est rejetée en envoyant un montant trop faible. Veuillez essayer d'augmenter le montant.", "tx_no_dust_exception": "La transaction est rejetée en envoyant un montant trop faible. Veuillez essayer d'augmenter le montant.",
"tx_not_enough_inputs_exception": "Pas assez d'entrées disponibles. Veuillez sélectionner plus sous Control Control", "tx_not_enough_inputs_exception": "Pas assez d'entrées disponibles. Veuillez sélectionner plus sous Control Control",
"tx_rejected_bip68_final": "La transaction a des entrées non confirmées et n'a pas réussi à remplacer par les frais.",
"tx_rejected_dust_change": "Transaction rejetée par les règles du réseau, montant de faible variation (poussière). Essayez d'envoyer tout ou de réduire le montant.", "tx_rejected_dust_change": "Transaction rejetée par les règles du réseau, montant de faible variation (poussière). Essayez d'envoyer tout ou de réduire le montant.",
"tx_rejected_dust_output": "Transaction rejetée par les règles du réseau, faible quantité de sortie (poussière). Veuillez augmenter le montant.", "tx_rejected_dust_output": "Transaction rejetée par les règles du réseau, faible quantité de sortie (poussière). Veuillez augmenter le montant.",
"tx_rejected_dust_output_send_all": "Transaction rejetée par les règles du réseau, faible quantité de sortie (poussière). Veuillez vérifier le solde des pièces sélectionnées sous le contrôle des pièces de monnaie.", "tx_rejected_dust_output_send_all": "Transaction rejetée par les règles du réseau, faible quantité de sortie (poussière). Veuillez vérifier le solde des pièces sélectionnées sous le contrôle des pièces de monnaie.",

View file

@ -747,8 +747,10 @@
"trusted": "Amintacce", "trusted": "Amintacce",
"tx_commit_exception_no_dust_on_change": "An ƙi ma'amala da wannan adadin. Tare da waɗannan tsabar kudi Zaka iya aika ${min}, ba tare da canji ba ko ${max} wanda ya dawo canzawa.", "tx_commit_exception_no_dust_on_change": "An ƙi ma'amala da wannan adadin. Tare da waɗannan tsabar kudi Zaka iya aika ${min}, ba tare da canji ba ko ${max} wanda ya dawo canzawa.",
"tx_commit_failed": "Ma'amala ya kasa. Da fatan za a tuntuɓi goyan baya.", "tx_commit_failed": "Ma'amala ya kasa. Da fatan za a tuntuɓi goyan baya.",
"tx_invalid_input": "Kuna amfani da nau'in shigar da ba daidai ba don wannan nau'in biyan kuɗi",
"tx_no_dust_exception": "An ƙi ma'amala ta hanyar aika adadin ƙarami. Da fatan za a gwada ƙara adadin.", "tx_no_dust_exception": "An ƙi ma'amala ta hanyar aika adadin ƙarami. Da fatan za a gwada ƙara adadin.",
"tx_not_enough_inputs_exception": "Bai isa ba hanyoyin da ake samu. Da fatan za selectiari a karkashin Kwarewar Coin", "tx_not_enough_inputs_exception": "Bai isa ba hanyoyin da ake samu. Da fatan za selectiari a karkashin Kwarewar Coin",
"tx_rejected_bip68_final": "Ma'amala tana da abubuwan da basu dace ba kuma sun kasa maye gurbin ta.",
"tx_rejected_dust_change": "Ma'amala ta ƙi ta dokokin cibiyar sadarwa, ƙarancin canji (ƙura). Gwada aikawa da duka ko rage adadin.", "tx_rejected_dust_change": "Ma'amala ta ƙi ta dokokin cibiyar sadarwa, ƙarancin canji (ƙura). Gwada aikawa da duka ko rage adadin.",
"tx_rejected_dust_output": "Ma'adar da aka ƙi ta dokokin cibiyar sadarwa, ƙananan fitarwa (ƙura). Da fatan za a ƙara adadin.", "tx_rejected_dust_output": "Ma'adar da aka ƙi ta dokokin cibiyar sadarwa, ƙananan fitarwa (ƙura). Da fatan za a ƙara adadin.",
"tx_rejected_dust_output_send_all": "Ma'adar da aka ƙi ta dokokin cibiyar sadarwa, ƙananan fitarwa (ƙura). Da fatan za a duba daidaiton tsabar kudi a ƙarƙashin ikon tsabar kudin.", "tx_rejected_dust_output_send_all": "Ma'adar da aka ƙi ta dokokin cibiyar sadarwa, ƙananan fitarwa (ƙura). Da fatan za a duba daidaiton tsabar kudi a ƙarƙashin ikon tsabar kudin.",

View file

@ -747,8 +747,10 @@
"trusted": "भरोसा", "trusted": "भरोसा",
"tx_commit_exception_no_dust_on_change": "लेनदेन को इस राशि से खारिज कर दिया जाता है। इन सिक्कों के साथ आप चेंज या ${min} के बिना ${max} को भेज सकते हैं जो परिवर्तन लौटाता है।", "tx_commit_exception_no_dust_on_change": "लेनदेन को इस राशि से खारिज कर दिया जाता है। इन सिक्कों के साथ आप चेंज या ${min} के बिना ${max} को भेज सकते हैं जो परिवर्तन लौटाता है।",
"tx_commit_failed": "लेन -देन प्रतिबद्ध विफल। कृपया संपर्क समर्थन करें।", "tx_commit_failed": "लेन -देन प्रतिबद्ध विफल। कृपया संपर्क समर्थन करें।",
"tx_invalid_input": "आप इस प्रकार के भुगतान के लिए गलत इनपुट प्रकार का उपयोग कर रहे हैं",
"tx_no_dust_exception": "लेनदेन को बहुत छोटी राशि भेजकर अस्वीकार कर दिया जाता है। कृपया राशि बढ़ाने का प्रयास करें।", "tx_no_dust_exception": "लेनदेन को बहुत छोटी राशि भेजकर अस्वीकार कर दिया जाता है। कृपया राशि बढ़ाने का प्रयास करें।",
"tx_not_enough_inputs_exception": "पर्याप्त इनपुट उपलब्ध नहीं है। कृपया सिक्का नियंत्रण के तहत अधिक चुनें", "tx_not_enough_inputs_exception": "पर्याप्त इनपुट उपलब्ध नहीं है। कृपया सिक्का नियंत्रण के तहत अधिक चुनें",
"tx_rejected_bip68_final": "लेन -देन में अपुष्ट इनपुट हैं और शुल्क द्वारा प्रतिस्थापित करने में विफल रहे हैं।",
"tx_rejected_dust_change": "नेटवर्क नियमों, कम परिवर्तन राशि (धूल) द्वारा खारिज किए गए लेनदेन। सभी भेजने या राशि को कम करने का प्रयास करें।", "tx_rejected_dust_change": "नेटवर्क नियमों, कम परिवर्तन राशि (धूल) द्वारा खारिज किए गए लेनदेन। सभी भेजने या राशि को कम करने का प्रयास करें।",
"tx_rejected_dust_output": "नेटवर्क नियमों, कम आउटपुट राशि (धूल) द्वारा खारिज किए गए लेनदेन। कृपया राशि बढ़ाएं।", "tx_rejected_dust_output": "नेटवर्क नियमों, कम आउटपुट राशि (धूल) द्वारा खारिज किए गए लेनदेन। कृपया राशि बढ़ाएं।",
"tx_rejected_dust_output_send_all": "नेटवर्क नियमों, कम आउटपुट राशि (धूल) द्वारा खारिज किए गए लेनदेन। कृपया सिक्का नियंत्रण के तहत चुने गए सिक्कों के संतुलन की जाँच करें।", "tx_rejected_dust_output_send_all": "नेटवर्क नियमों, कम आउटपुट राशि (धूल) द्वारा खारिज किए गए लेनदेन। कृपया सिक्का नियंत्रण के तहत चुने गए सिक्कों के संतुलन की जाँच करें।",

View file

@ -745,8 +745,10 @@
"trusted": "vjerovao", "trusted": "vjerovao",
"tx_commit_exception_no_dust_on_change": "Transakcija se odbija s tim iznosom. Pomoću ovih kovanica možete poslati ${min} bez promjene ili ${max} koja vraća promjenu.", "tx_commit_exception_no_dust_on_change": "Transakcija se odbija s tim iznosom. Pomoću ovih kovanica možete poslati ${min} bez promjene ili ${max} koja vraća promjenu.",
"tx_commit_failed": "Obveza transakcije nije uspjela. Molimo kontaktirajte podršku.", "tx_commit_failed": "Obveza transakcije nije uspjela. Molimo kontaktirajte podršku.",
"tx_invalid_input": "Koristite pogrešnu vrstu ulaza za ovu vrstu plaćanja",
"tx_no_dust_exception": "Transakcija se odbija slanjem iznosa premalo. Pokušajte povećati iznos.", "tx_no_dust_exception": "Transakcija se odbija slanjem iznosa premalo. Pokušajte povećati iznos.",
"tx_not_enough_inputs_exception": "Nema dovoljno unosa. Molimo odaberite više pod kontrolom novčića", "tx_not_enough_inputs_exception": "Nema dovoljno unosa. Molimo odaberite više pod kontrolom novčića",
"tx_rejected_bip68_final": "Transakcija ima nepotvrđene unose i nije zamijenila naknadom.",
"tx_rejected_dust_change": "Transakcija odbijena mrežnim pravilima, niska količina promjene (prašina). Pokušajte poslati sve ili smanjiti iznos.", "tx_rejected_dust_change": "Transakcija odbijena mrežnim pravilima, niska količina promjene (prašina). Pokušajte poslati sve ili smanjiti iznos.",
"tx_rejected_dust_output": "Transakcija odbijena mrežnim pravilima, niska količina izlaza (prašina). Molimo povećajte iznos.", "tx_rejected_dust_output": "Transakcija odbijena mrežnim pravilima, niska količina izlaza (prašina). Molimo povećajte iznos.",
"tx_rejected_dust_output_send_all": "Transakcija odbijena mrežnim pravilima, niska količina izlaza (prašina). Molimo provjerite ravnotežu kovanica odabranih pod kontrolom novčića.", "tx_rejected_dust_output_send_all": "Transakcija odbijena mrežnim pravilima, niska količina izlaza (prašina). Molimo provjerite ravnotežu kovanica odabranih pod kontrolom novčića.",

View file

@ -748,8 +748,10 @@
"trusted": "Dipercayai", "trusted": "Dipercayai",
"tx_commit_exception_no_dust_on_change": "Transaksi ditolak dengan jumlah ini. Dengan koin ini Anda dapat mengirim ${min} tanpa perubahan atau ${max} yang mengembalikan perubahan.", "tx_commit_exception_no_dust_on_change": "Transaksi ditolak dengan jumlah ini. Dengan koin ini Anda dapat mengirim ${min} tanpa perubahan atau ${max} yang mengembalikan perubahan.",
"tx_commit_failed": "Transaksi Gagal. Silakan hubungi Dukungan.", "tx_commit_failed": "Transaksi Gagal. Silakan hubungi Dukungan.",
"tx_invalid_input": "Anda menggunakan jenis input yang salah untuk jenis pembayaran ini",
"tx_no_dust_exception": "Transaksi ditolak dengan mengirimkan jumlah yang terlalu kecil. Silakan coba tingkatkan jumlahnya.", "tx_no_dust_exception": "Transaksi ditolak dengan mengirimkan jumlah yang terlalu kecil. Silakan coba tingkatkan jumlahnya.",
"tx_not_enough_inputs_exception": "Tidak cukup input yang tersedia. Pilih lebih banyak lagi di bawah Kontrol Koin", "tx_not_enough_inputs_exception": "Tidak cukup input yang tersedia. Pilih lebih banyak lagi di bawah Kontrol Koin",
"tx_rejected_bip68_final": "Transaksi memiliki input yang belum dikonfirmasi dan gagal mengganti dengan biaya.",
"tx_rejected_dust_change": "Transaksi ditolak oleh aturan jaringan, jumlah perubahan rendah (debu). Coba kirim semua atau mengurangi jumlahnya.", "tx_rejected_dust_change": "Transaksi ditolak oleh aturan jaringan, jumlah perubahan rendah (debu). Coba kirim semua atau mengurangi jumlahnya.",
"tx_rejected_dust_output": "Transaksi ditolak oleh aturan jaringan, jumlah output rendah (debu). Harap tingkatkan jumlahnya.", "tx_rejected_dust_output": "Transaksi ditolak oleh aturan jaringan, jumlah output rendah (debu). Harap tingkatkan jumlahnya.",
"tx_rejected_dust_output_send_all": "Transaksi ditolak oleh aturan jaringan, jumlah output rendah (debu). Silakan periksa saldo koin yang dipilih di bawah kontrol koin.", "tx_rejected_dust_output_send_all": "Transaksi ditolak oleh aturan jaringan, jumlah output rendah (debu). Silakan periksa saldo koin yang dipilih di bawah kontrol koin.",

View file

@ -747,8 +747,10 @@
"trusted": "di fiducia", "trusted": "di fiducia",
"tx_commit_exception_no_dust_on_change": "La transazione viene respinta con questo importo. Con queste monete è possibile inviare ${min} senza modifiche o ${max} che restituisce il cambiamento.", "tx_commit_exception_no_dust_on_change": "La transazione viene respinta con questo importo. Con queste monete è possibile inviare ${min} senza modifiche o ${max} che restituisce il cambiamento.",
"tx_commit_failed": "Commit di transazione non riuscita. Si prega di contattare il supporto.", "tx_commit_failed": "Commit di transazione non riuscita. Si prega di contattare il supporto.",
"tx_invalid_input": "Stai usando il tipo di input sbagliato per questo tipo di pagamento",
"tx_no_dust_exception": "La transazione viene respinta inviando un importo troppo piccolo. Per favore, prova ad aumentare l'importo.", "tx_no_dust_exception": "La transazione viene respinta inviando un importo troppo piccolo. Per favore, prova ad aumentare l'importo.",
"tx_not_enough_inputs_exception": "Input non sufficienti disponibili. Seleziona di più sotto il controllo delle monete", "tx_not_enough_inputs_exception": "Input non sufficienti disponibili. Seleziona di più sotto il controllo delle monete",
"tx_rejected_bip68_final": "La transazione ha input non confermati e non è stata sostituita per tassa.",
"tx_rejected_dust_change": "Transazione respinta dalle regole di rete, quantità bassa variazione (polvere). Prova a inviare tutto o ridurre l'importo.", "tx_rejected_dust_change": "Transazione respinta dalle regole di rete, quantità bassa variazione (polvere). Prova a inviare tutto o ridurre l'importo.",
"tx_rejected_dust_output": "Transazione respinta dalle regole di rete, bassa quantità di output (polvere). Si prega di aumentare l'importo.", "tx_rejected_dust_output": "Transazione respinta dalle regole di rete, bassa quantità di output (polvere). Si prega di aumentare l'importo.",
"tx_rejected_dust_output_send_all": "Transazione respinta dalle regole di rete, bassa quantità di output (polvere). Si prega di controllare il saldo delle monete selezionate sotto controllo delle monete.", "tx_rejected_dust_output_send_all": "Transazione respinta dalle regole di rete, bassa quantità di output (polvere). Si prega di controllare il saldo delle monete selezionate sotto controllo delle monete.",

View file

@ -746,8 +746,10 @@
"trusted": "信頼できる", "trusted": "信頼できる",
"tx_commit_exception_no_dust_on_change": "この金額ではトランザクションは拒否されます。 これらのコインを使用すると、おつりなしの ${min} またはおつりを返す ${max} を送信できます。", "tx_commit_exception_no_dust_on_change": "この金額ではトランザクションは拒否されます。 これらのコインを使用すると、おつりなしの ${min} またはおつりを返す ${max} を送信できます。",
"tx_commit_failed": "トランザクションコミットは失敗しました。サポートに連絡してください。", "tx_commit_failed": "トランザクションコミットは失敗しました。サポートに連絡してください。",
"tx_invalid_input": "このタイプの支払いに間違った入力タイプを使用しています",
"tx_no_dust_exception": "トランザクションは、小さすぎる金額を送信することにより拒否されます。量を増やしてみてください。", "tx_no_dust_exception": "トランザクションは、小さすぎる金額を送信することにより拒否されます。量を増やしてみてください。",
"tx_not_enough_inputs_exception": "利用可能な入力が十分ではありません。コイン制御下でもっと選択してください", "tx_not_enough_inputs_exception": "利用可能な入力が十分ではありません。コイン制御下でもっと選択してください",
"tx_rejected_bip68_final": "トランザクションには未確認の入力があり、料金で交換できませんでした。",
"tx_rejected_dust_change": "ネットワークルール、低い変更量(ほこり)によって拒否されたトランザクション。すべてを送信するか、金額を減らしてみてください。", "tx_rejected_dust_change": "ネットワークルール、低い変更量(ほこり)によって拒否されたトランザクション。すべてを送信するか、金額を減らしてみてください。",
"tx_rejected_dust_output": "ネットワークルール、低出力量(ダスト)によって拒否されたトランザクション。金額を増やしてください。", "tx_rejected_dust_output": "ネットワークルール、低出力量(ダスト)によって拒否されたトランザクション。金額を増やしてください。",
"tx_rejected_dust_output_send_all": "ネットワークルール、低出力量(ダスト)によって拒否されたトランザクション。コイン管理下で選択されたコインのバランスを確認してください。", "tx_rejected_dust_output_send_all": "ネットワークルール、低出力量(ダスト)によって拒否されたトランザクション。コイン管理下で選択されたコインのバランスを確認してください。",

View file

@ -746,8 +746,10 @@
"trusted": "신뢰할 수 있는", "trusted": "신뢰할 수 있는",
"tx_commit_exception_no_dust_on_change": "이 금액으로 거래가 거부되었습니다. 이 코인을 사용하면 거스름돈 없이 ${min}를 보내거나 거스름돈을 반환하는 ${max}를 보낼 수 있습니다.", "tx_commit_exception_no_dust_on_change": "이 금액으로 거래가 거부되었습니다. 이 코인을 사용하면 거스름돈 없이 ${min}를 보내거나 거스름돈을 반환하는 ${max}를 보낼 수 있습니다.",
"tx_commit_failed": "거래 커밋이 실패했습니다. 지원에 연락하십시오.", "tx_commit_failed": "거래 커밋이 실패했습니다. 지원에 연락하십시오.",
"tx_invalid_input": "이 유형의 지불에 잘못 입력 유형을 사용하고 있습니다.",
"tx_no_dust_exception": "너무 작은 금액을 보내면 거래가 거부됩니다. 금액을 늘리십시오.", "tx_no_dust_exception": "너무 작은 금액을 보내면 거래가 거부됩니다. 금액을 늘리십시오.",
"tx_not_enough_inputs_exception": "사용 가능한 입력이 충분하지 않습니다. 코인 컨트롤에서 더 많은 것을 선택하십시오", "tx_not_enough_inputs_exception": "사용 가능한 입력이 충분하지 않습니다. 코인 컨트롤에서 더 많은 것을 선택하십시오",
"tx_rejected_bip68_final": "거래는 확인되지 않은 입력을 받았으며 수수료로 교체하지 못했습니다.",
"tx_rejected_dust_change": "네트워크 규칙, 낮은 변경 금액 (먼지)에 의해 거부 된 거래. 전부를 보내거나 금액을 줄이십시오.", "tx_rejected_dust_change": "네트워크 규칙, 낮은 변경 금액 (먼지)에 의해 거부 된 거래. 전부를 보내거나 금액을 줄이십시오.",
"tx_rejected_dust_output": "네트워크 규칙, 낮은 출력 금액 (먼지)에 의해 거부 된 거래. 금액을 늘리십시오.", "tx_rejected_dust_output": "네트워크 규칙, 낮은 출력 금액 (먼지)에 의해 거부 된 거래. 금액을 늘리십시오.",
"tx_rejected_dust_output_send_all": "네트워크 규칙, 낮은 출력 금액 (먼지)에 의해 거부 된 거래. 동전 제어에서 선택한 동전의 균형을 확인하십시오.", "tx_rejected_dust_output_send_all": "네트워크 규칙, 낮은 출력 금액 (먼지)에 의해 거부 된 거래. 동전 제어에서 선택한 동전의 균형을 확인하십시오.",

View file

@ -745,8 +745,10 @@
"trusted": "ယုံတယ်။", "trusted": "ယုံတယ်။",
"tx_commit_exception_no_dust_on_change": "အဆိုပါငွေပေးငွေယူကဒီပမာဏနှင့်အတူပယ်ချခံရသည်။ ဤဒင်္ဂါးပြားများနှင့်အတူပြောင်းလဲမှုကိုပြန်လည်ပြောင်းလဲခြင်းသို့မဟုတ် ${min} မပါဘဲ ${max} ပေးပို့နိုင်သည်။", "tx_commit_exception_no_dust_on_change": "အဆိုပါငွေပေးငွေယူကဒီပမာဏနှင့်အတူပယ်ချခံရသည်။ ဤဒင်္ဂါးပြားများနှင့်အတူပြောင်းလဲမှုကိုပြန်လည်ပြောင်းလဲခြင်းသို့မဟုတ် ${min} မပါဘဲ ${max} ပေးပို့နိုင်သည်။",
"tx_commit_failed": "ငွေပေးငွေယူကျူးလွန်မှုပျက်ကွက်။ ကျေးဇူးပြုပြီးပံ့ပိုးမှုဆက်သွယ်ပါ။", "tx_commit_failed": "ငွေပေးငွေယူကျူးလွန်မှုပျက်ကွက်။ ကျေးဇူးပြုပြီးပံ့ပိုးမှုဆက်သွယ်ပါ။",
"tx_invalid_input": "သင်သည်ဤငွေပေးချေမှုအမျိုးအစားအတွက်မှားယွင်းသော input type ကိုအသုံးပြုနေသည်",
"tx_no_dust_exception": "ငွေပမာဏကိုသေးငယ်လွန်းသောငွေပမာဏကိုပေးပို့ခြင်းဖြင့်ပယ်ဖျက်ခြင်းကိုငြင်းပယ်သည်။ ကျေးဇူးပြုပြီးငွေပမာဏကိုတိုးမြှင့်ကြိုးစားပါ။", "tx_no_dust_exception": "ငွေပမာဏကိုသေးငယ်လွန်းသောငွေပမာဏကိုပေးပို့ခြင်းဖြင့်ပယ်ဖျက်ခြင်းကိုငြင်းပယ်သည်။ ကျေးဇူးပြုပြီးငွေပမာဏကိုတိုးမြှင့်ကြိုးစားပါ။",
"tx_not_enough_inputs_exception": "အလုံအလောက်သွင်းအားစုများမလုံလောက်။ ကျေးဇူးပြုပြီးဒင်္ဂါးပြားထိန်းချုပ်မှုအောက်တွင်ပိုမိုရွေးချယ်ပါ", "tx_not_enough_inputs_exception": "အလုံအလောက်သွင်းအားစုများမလုံလောက်။ ကျေးဇူးပြုပြီးဒင်္ဂါးပြားထိန်းချုပ်မှုအောက်တွင်ပိုမိုရွေးချယ်ပါ",
"tx_rejected_bip68_final": "ငွေပေးငွေယူသည်အတည်မပြုရသေးသောသွင်းအားစုများရှိပြီးအခကြေးငွေဖြင့်အစားထိုးရန်ပျက်ကွက်ခဲ့သည်။",
"tx_rejected_dust_change": "Network စည်းမျဉ်းစည်းကမ်းများဖြင့်ပယ်ဖျက်ခြင်းသည် Network စည်းမျဉ်းစည်းကမ်းများဖြင့်ငြင်းပယ်ခြင်း, အားလုံးပေးပို့ခြင်းသို့မဟုတ်ငွေပမာဏကိုလျှော့ချကြိုးစားပါ။", "tx_rejected_dust_change": "Network စည်းမျဉ်းစည်းကမ်းများဖြင့်ပယ်ဖျက်ခြင်းသည် Network စည်းမျဉ်းစည်းကမ်းများဖြင့်ငြင်းပယ်ခြင်း, အားလုံးပေးပို့ခြင်းသို့မဟုတ်ငွေပမာဏကိုလျှော့ချကြိုးစားပါ။",
"tx_rejected_dust_output": "Network စည်းမျဉ်းစည်းကမ်းများဖြင့် ပယ်ချ. ငွေပေးချေမှုသည် output output (ဖုန်မှုန့်) ဖြင့်ပယ်ချခဲ့သည်။ ကျေးဇူးပြုပြီးငွေပမာဏကိုတိုးမြှင့်ပေးပါ။", "tx_rejected_dust_output": "Network စည်းမျဉ်းစည်းကမ်းများဖြင့် ပယ်ချ. ငွေပေးချေမှုသည် output output (ဖုန်မှုန့်) ဖြင့်ပယ်ချခဲ့သည်။ ကျေးဇူးပြုပြီးငွေပမာဏကိုတိုးမြှင့်ပေးပါ။",
"tx_rejected_dust_output_send_all": "Network စည်းမျဉ်းစည်းကမ်းများဖြင့် ပယ်ချ. ငွေပေးချေမှုသည် output output (ဖုန်မှုန့်) ဖြင့်ပယ်ချခဲ့သည်။ ဒင်္ဂါးပြားထိန်းချုပ်မှုအောက်တွင်ရွေးချယ်ထားသောဒင်္ဂါးများ၏လက်ကျန်ငွေကိုစစ်ဆေးပါ။", "tx_rejected_dust_output_send_all": "Network စည်းမျဉ်းစည်းကမ်းများဖြင့် ပယ်ချ. ငွေပေးချေမှုသည် output output (ဖုန်မှုန့်) ဖြင့်ပယ်ချခဲ့သည်။ ဒင်္ဂါးပြားထိန်းချုပ်မှုအောက်တွင်ရွေးချယ်ထားသောဒင်္ဂါးများ၏လက်ကျန်ငွေကိုစစ်ဆေးပါ။",

View file

@ -745,8 +745,10 @@
"trusted": "vertrouwd", "trusted": "vertrouwd",
"tx_commit_exception_no_dust_on_change": "De transactie wordt afgewezen met dit bedrag. Met deze munten kunt u ${min} verzenden zonder verandering of ${max} die wijziging retourneert.", "tx_commit_exception_no_dust_on_change": "De transactie wordt afgewezen met dit bedrag. Met deze munten kunt u ${min} verzenden zonder verandering of ${max} die wijziging retourneert.",
"tx_commit_failed": "Transactiebewissing is mislukt. Neem contact op met de ondersteuning.", "tx_commit_failed": "Transactiebewissing is mislukt. Neem contact op met de ondersteuning.",
"tx_invalid_input": "U gebruikt het verkeerde invoertype voor dit type betaling",
"tx_no_dust_exception": "De transactie wordt afgewezen door een te klein bedrag te verzenden. Probeer het bedrag te verhogen.", "tx_no_dust_exception": "De transactie wordt afgewezen door een te klein bedrag te verzenden. Probeer het bedrag te verhogen.",
"tx_not_enough_inputs_exception": "Niet genoeg ingangen beschikbaar. Selecteer meer onder muntenbesturing", "tx_not_enough_inputs_exception": "Niet genoeg ingangen beschikbaar. Selecteer meer onder muntenbesturing",
"tx_rejected_bip68_final": "Transactie heeft onbevestigde ingangen en niet vervangen door vergoeding.",
"tx_rejected_dust_change": "Transactie afgewezen door netwerkregels, laag wijzigingsbedrag (stof). Probeer alles te verzenden of het bedrag te verminderen.", "tx_rejected_dust_change": "Transactie afgewezen door netwerkregels, laag wijzigingsbedrag (stof). Probeer alles te verzenden of het bedrag te verminderen.",
"tx_rejected_dust_output": "Transactie afgewezen door netwerkregels, laag outputbedrag (stof). Verhoog het bedrag.", "tx_rejected_dust_output": "Transactie afgewezen door netwerkregels, laag outputbedrag (stof). Verhoog het bedrag.",
"tx_rejected_dust_output_send_all": "Transactie afgewezen door netwerkregels, laag outputbedrag (stof). Controleer het saldo van munten die zijn geselecteerd onder muntcontrole.", "tx_rejected_dust_output_send_all": "Transactie afgewezen door netwerkregels, laag outputbedrag (stof). Controleer het saldo van munten die zijn geselecteerd onder muntcontrole.",

View file

@ -745,8 +745,10 @@
"trusted": "Zaufany", "trusted": "Zaufany",
"tx_commit_exception_no_dust_on_change": "Transakcja jest odrzucana z tą kwotą. Za pomocą tych monet możesz wysłać ${min} bez zmiany lub ${max}, które zwraca zmianę.", "tx_commit_exception_no_dust_on_change": "Transakcja jest odrzucana z tą kwotą. Za pomocą tych monet możesz wysłać ${min} bez zmiany lub ${max}, które zwraca zmianę.",
"tx_commit_failed": "Zatwierdzenie transakcji nie powiodło się. Skontaktuj się z obsługą.", "tx_commit_failed": "Zatwierdzenie transakcji nie powiodło się. Skontaktuj się z obsługą.",
"tx_invalid_input": "Używasz niewłaściwego typu wejściowego dla tego rodzaju płatności",
"tx_no_dust_exception": "Transakcja jest odrzucana przez wysyłanie zbyt małej ilości. Spróbuj zwiększyć kwotę.", "tx_no_dust_exception": "Transakcja jest odrzucana przez wysyłanie zbyt małej ilości. Spróbuj zwiększyć kwotę.",
"tx_not_enough_inputs_exception": "Za mało dostępnych danych wejściowych. Wybierz więcej pod kontrolą monet", "tx_not_enough_inputs_exception": "Za mało dostępnych danych wejściowych. Wybierz więcej pod kontrolą monet",
"tx_rejected_bip68_final": "Transakcja niepotwierdza wejściów i nie zastąpiła opłaty.",
"tx_rejected_dust_change": "Transakcja odrzucona według reguł sieciowych, niska ilość zmiany (kurz). Spróbuj wysłać całość lub zmniejszyć kwotę.", "tx_rejected_dust_change": "Transakcja odrzucona według reguł sieciowych, niska ilość zmiany (kurz). Spróbuj wysłać całość lub zmniejszyć kwotę.",
"tx_rejected_dust_output": "Transakcja odrzucona według reguł sieciowych, niskiej ilości wyjściowej (pyłu). Zwiększ kwotę.", "tx_rejected_dust_output": "Transakcja odrzucona według reguł sieciowych, niskiej ilości wyjściowej (pyłu). Zwiększ kwotę.",
"tx_rejected_dust_output_send_all": "Transakcja odrzucona według reguł sieciowych, niskiej ilości wyjściowej (pyłu). Sprawdź saldo monet wybranych pod kontrolą monet.", "tx_rejected_dust_output_send_all": "Transakcja odrzucona według reguł sieciowych, niskiej ilości wyjściowej (pyłu). Sprawdź saldo monet wybranych pod kontrolą monet.",

View file

@ -747,8 +747,10 @@
"trusted": "confiável", "trusted": "confiável",
"tx_commit_exception_no_dust_on_change": "A transação é rejeitada com esse valor. Com essas moedas, você pode enviar ${min} sem alteração ou ${max} que retorna alterações.", "tx_commit_exception_no_dust_on_change": "A transação é rejeitada com esse valor. Com essas moedas, você pode enviar ${min} sem alteração ou ${max} que retorna alterações.",
"tx_commit_failed": "A confirmação da transação falhou. Entre em contato com o suporte.", "tx_commit_failed": "A confirmação da transação falhou. Entre em contato com o suporte.",
"tx_invalid_input": "Você está usando o tipo de entrada errado para este tipo de pagamento",
"tx_no_dust_exception": "A transação é rejeitada enviando uma quantia pequena demais. Por favor, tente aumentar o valor.", "tx_no_dust_exception": "A transação é rejeitada enviando uma quantia pequena demais. Por favor, tente aumentar o valor.",
"tx_not_enough_inputs_exception": "Não há entradas disponíveis. Selecione mais sob controle de moedas", "tx_not_enough_inputs_exception": "Não há entradas disponíveis. Selecione mais sob controle de moedas",
"tx_rejected_bip68_final": "A transação tem entradas não confirmadas e não substituiu por taxa.",
"tx_rejected_dust_change": "Transação rejeitada pelas regras de rede, baixa quantidade de troco (poeira). Tente enviar tudo ou reduzir o valor.", "tx_rejected_dust_change": "Transação rejeitada pelas regras de rede, baixa quantidade de troco (poeira). Tente enviar tudo ou reduzir o valor.",
"tx_rejected_dust_output": "Transação rejeitada por regras de rede, baixa quantidade de saída (poeira). Por favor, aumente o valor.", "tx_rejected_dust_output": "Transação rejeitada por regras de rede, baixa quantidade de saída (poeira). Por favor, aumente o valor.",
"tx_rejected_dust_output_send_all": "Transação rejeitada por regras de rede, baixa quantidade de saída (poeira). Por favor, verifique o saldo de moedas selecionadas sob controle de moedas.", "tx_rejected_dust_output_send_all": "Transação rejeitada por regras de rede, baixa quantidade de saída (poeira). Por favor, verifique o saldo de moedas selecionadas sob controle de moedas.",
@ -760,7 +762,7 @@
"unconfirmed": "Saldo não confirmado", "unconfirmed": "Saldo não confirmado",
"understand": "Entendo", "understand": "Entendo",
"unmatched_currencies": "A moeda da sua carteira atual não corresponde à do QR digitalizado", "unmatched_currencies": "A moeda da sua carteira atual não corresponde à do QR digitalizado",
"unspent_change": "Mudar", "unspent_change": "Troco",
"unspent_coins_details_title": "Detalhes de moedas não gastas", "unspent_coins_details_title": "Detalhes de moedas não gastas",
"unspent_coins_title": "Moedas não gastas", "unspent_coins_title": "Moedas não gastas",
"unsupported_asset": "Não oferecemos suporte a esta ação para este recurso. Crie ou mude para uma carteira de um tipo de ativo compatível.", "unsupported_asset": "Não oferecemos suporte a esta ação para este recurso. Crie ou mude para uma carteira de um tipo de ativo compatível.",

View file

@ -746,8 +746,10 @@
"trusted": "доверенный", "trusted": "доверенный",
"tx_commit_exception_no_dust_on_change": "Транзакция отклоняется с этой суммой. С этими монетами вы можете отправлять ${min} без изменения или ${max}, которые возвращают изменение.", "tx_commit_exception_no_dust_on_change": "Транзакция отклоняется с этой суммой. С этими монетами вы можете отправлять ${min} без изменения или ${max}, которые возвращают изменение.",
"tx_commit_failed": "Комплект транзакции не удался. Пожалуйста, свяжитесь с поддержкой.", "tx_commit_failed": "Комплект транзакции не удался. Пожалуйста, свяжитесь с поддержкой.",
"tx_invalid_input": "Вы используете неправильный тип ввода для этого типа оплаты",
"tx_no_dust_exception": "Транзакция отклоняется путем отправки слишком маленькой суммы. Пожалуйста, попробуйте увеличить сумму.", "tx_no_dust_exception": "Транзакция отклоняется путем отправки слишком маленькой суммы. Пожалуйста, попробуйте увеличить сумму.",
"tx_not_enough_inputs_exception": "Недостаточно входов доступны. Пожалуйста, выберите больше под контролем монет", "tx_not_enough_inputs_exception": "Недостаточно входов доступны. Пожалуйста, выберите больше под контролем монет",
"tx_rejected_bip68_final": "Транзакция имеет неподтвержденные входные данные и не смогли заменить на плату.",
"tx_rejected_dust_change": "Транзакция отклоняется в соответствии с правилами сети, низкой суммой изменений (пыль). Попробуйте отправить все или уменьшить сумму.", "tx_rejected_dust_change": "Транзакция отклоняется в соответствии с правилами сети, низкой суммой изменений (пыль). Попробуйте отправить все или уменьшить сумму.",
"tx_rejected_dust_output": "Транзакция отклоняется в соответствии с правилами сети, низкой выходной суммой (пыль). Пожалуйста, увеличьте сумму.", "tx_rejected_dust_output": "Транзакция отклоняется в соответствии с правилами сети, низкой выходной суммой (пыль). Пожалуйста, увеличьте сумму.",
"tx_rejected_dust_output_send_all": "Транзакция отклоняется в соответствии с правилами сети, низкой выходной суммой (пыль). Пожалуйста, проверьте баланс монет, выбранных под контролем монет.", "tx_rejected_dust_output_send_all": "Транзакция отклоняется в соответствии с правилами сети, низкой выходной суммой (пыль). Пожалуйста, проверьте баланс монет, выбранных под контролем монет.",

View file

@ -745,8 +745,10 @@
"trusted": "มั่นคง", "trusted": "มั่นคง",
"tx_commit_exception_no_dust_on_change": "ธุรกรรมถูกปฏิเสธด้วยจำนวนเงินนี้ ด้วยเหรียญเหล่านี้คุณสามารถส่ง ${min} โดยไม่ต้องเปลี่ยนแปลงหรือ ${max} ที่ส่งคืนการเปลี่ยนแปลง", "tx_commit_exception_no_dust_on_change": "ธุรกรรมถูกปฏิเสธด้วยจำนวนเงินนี้ ด้วยเหรียญเหล่านี้คุณสามารถส่ง ${min} โดยไม่ต้องเปลี่ยนแปลงหรือ ${max} ที่ส่งคืนการเปลี่ยนแปลง",
"tx_commit_failed": "การทำธุรกรรมล้มเหลว กรุณาติดต่อฝ่ายสนับสนุน", "tx_commit_failed": "การทำธุรกรรมล้มเหลว กรุณาติดต่อฝ่ายสนับสนุน",
"tx_invalid_input": "คุณกำลังใช้ประเภทอินพุตที่ไม่ถูกต้องสำหรับการชำระเงินประเภทนี้",
"tx_no_dust_exception": "การทำธุรกรรมถูกปฏิเสธโดยการส่งจำนวนน้อยเกินไป โปรดลองเพิ่มจำนวนเงิน", "tx_no_dust_exception": "การทำธุรกรรมถูกปฏิเสธโดยการส่งจำนวนน้อยเกินไป โปรดลองเพิ่มจำนวนเงิน",
"tx_not_enough_inputs_exception": "มีอินพุตไม่เพียงพอ โปรดเลือกเพิ่มเติมภายใต้การควบคุมเหรียญ", "tx_not_enough_inputs_exception": "มีอินพุตไม่เพียงพอ โปรดเลือกเพิ่มเติมภายใต้การควบคุมเหรียญ",
"tx_rejected_bip68_final": "การทำธุรกรรมมีอินพุตที่ไม่ได้รับการยืนยันและไม่สามารถแทนที่ด้วยค่าธรรมเนียม",
"tx_rejected_dust_change": "ธุรกรรมถูกปฏิเสธโดยกฎเครือข่ายจำนวนการเปลี่ยนแปลงต่ำ (ฝุ่น) ลองส่งทั้งหมดหรือลดจำนวนเงิน", "tx_rejected_dust_change": "ธุรกรรมถูกปฏิเสธโดยกฎเครือข่ายจำนวนการเปลี่ยนแปลงต่ำ (ฝุ่น) ลองส่งทั้งหมดหรือลดจำนวนเงิน",
"tx_rejected_dust_output": "การทำธุรกรรมถูกปฏิเสธโดยกฎเครือข่ายจำนวนเอาต์พุตต่ำ (ฝุ่น) โปรดเพิ่มจำนวนเงิน", "tx_rejected_dust_output": "การทำธุรกรรมถูกปฏิเสธโดยกฎเครือข่ายจำนวนเอาต์พุตต่ำ (ฝุ่น) โปรดเพิ่มจำนวนเงิน",
"tx_rejected_dust_output_send_all": "การทำธุรกรรมถูกปฏิเสธโดยกฎเครือข่ายจำนวนเอาต์พุตต่ำ (ฝุ่น) โปรดตรวจสอบยอดคงเหลือของเหรียญที่เลือกภายใต้การควบคุมเหรียญ", "tx_rejected_dust_output_send_all": "การทำธุรกรรมถูกปฏิเสธโดยกฎเครือข่ายจำนวนเอาต์พุตต่ำ (ฝุ่น) โปรดตรวจสอบยอดคงเหลือของเหรียญที่เลือกภายใต้การควบคุมเหรียญ",

View file

@ -745,8 +745,10 @@
"trusted": "Pinagkakatiwalaan", "trusted": "Pinagkakatiwalaan",
"tx_commit_exception_no_dust_on_change": "Ang transaksyon ay tinanggihan sa halagang ito. Sa mga barya na ito maaari kang magpadala ng ${min} nang walang pagbabago o ${max} na nagbabalik ng pagbabago.", "tx_commit_exception_no_dust_on_change": "Ang transaksyon ay tinanggihan sa halagang ito. Sa mga barya na ito maaari kang magpadala ng ${min} nang walang pagbabago o ${max} na nagbabalik ng pagbabago.",
"tx_commit_failed": "Nabigo ang transaksyon sa transaksyon. Mangyaring makipag -ugnay sa suporta.", "tx_commit_failed": "Nabigo ang transaksyon sa transaksyon. Mangyaring makipag -ugnay sa suporta.",
"tx_invalid_input": "Gumagamit ka ng maling uri ng pag -input para sa ganitong uri ng pagbabayad",
"tx_no_dust_exception": "Ang transaksyon ay tinanggihan sa pamamagitan ng pagpapadala ng isang maliit na maliit. Mangyaring subukang dagdagan ang halaga.", "tx_no_dust_exception": "Ang transaksyon ay tinanggihan sa pamamagitan ng pagpapadala ng isang maliit na maliit. Mangyaring subukang dagdagan ang halaga.",
"tx_not_enough_inputs_exception": "Hindi sapat na magagamit ang mga input. Mangyaring pumili ng higit pa sa ilalim ng control ng barya", "tx_not_enough_inputs_exception": "Hindi sapat na magagamit ang mga input. Mangyaring pumili ng higit pa sa ilalim ng control ng barya",
"tx_rejected_bip68_final": "Ang transaksyon ay hindi nakumpirma na mga input at nabigo na palitan ng bayad.",
"tx_rejected_dust_change": "Ang transaksyon na tinanggihan ng mga patakaran sa network, mababang halaga ng pagbabago (alikabok). Subukang ipadala ang lahat o bawasan ang halaga.", "tx_rejected_dust_change": "Ang transaksyon na tinanggihan ng mga patakaran sa network, mababang halaga ng pagbabago (alikabok). Subukang ipadala ang lahat o bawasan ang halaga.",
"tx_rejected_dust_output": "Ang transaksyon na tinanggihan ng mga patakaran sa network, mababang halaga ng output (alikabok). Mangyaring dagdagan ang halaga.", "tx_rejected_dust_output": "Ang transaksyon na tinanggihan ng mga patakaran sa network, mababang halaga ng output (alikabok). Mangyaring dagdagan ang halaga.",
"tx_rejected_dust_output_send_all": "Ang transaksyon na tinanggihan ng mga patakaran sa network, mababang halaga ng output (alikabok). Mangyaring suriin ang balanse ng mga barya na napili sa ilalim ng kontrol ng barya.", "tx_rejected_dust_output_send_all": "Ang transaksyon na tinanggihan ng mga patakaran sa network, mababang halaga ng output (alikabok). Mangyaring suriin ang balanse ng mga barya na napili sa ilalim ng kontrol ng barya.",

View file

@ -745,8 +745,10 @@
"trusted": "Güvenilir", "trusted": "Güvenilir",
"tx_commit_exception_no_dust_on_change": "İşlem bu miktarla reddedilir. Bu madeni paralarla değişiklik yapmadan ${min} veya değişikliği döndüren ${max} gönderebilirsiniz.", "tx_commit_exception_no_dust_on_change": "İşlem bu miktarla reddedilir. Bu madeni paralarla değişiklik yapmadan ${min} veya değişikliği döndüren ${max} gönderebilirsiniz.",
"tx_commit_failed": "İşlem taahhüdü başarısız oldu. Lütfen Destek ile iletişime geçin.", "tx_commit_failed": "İşlem taahhüdü başarısız oldu. Lütfen Destek ile iletişime geçin.",
"tx_invalid_input": "Bu tür ödeme için yanlış giriş türünü kullanıyorsunuz",
"tx_no_dust_exception": "İşlem, çok küçük bir miktar gönderilerek reddedilir. Lütfen miktarı artırmayı deneyin.", "tx_no_dust_exception": "İşlem, çok küçük bir miktar gönderilerek reddedilir. Lütfen miktarı artırmayı deneyin.",
"tx_not_enough_inputs_exception": "Yeterli giriş yok. Lütfen madeni para kontrolü altında daha fazlasını seçin", "tx_not_enough_inputs_exception": "Yeterli giriş yok. Lütfen madeni para kontrolü altında daha fazlasını seçin",
"tx_rejected_bip68_final": "İşlemin doğrulanmamış girdileri var ve ücrete göre değiştirilemedi.",
"tx_rejected_dust_change": "Ağ kurallarına göre reddedilen işlem, düşük değişim miktarı (toz). Tümünü göndermeyi veya miktarı azaltmayı deneyin.", "tx_rejected_dust_change": "Ağ kurallarına göre reddedilen işlem, düşük değişim miktarı (toz). Tümünü göndermeyi veya miktarı azaltmayı deneyin.",
"tx_rejected_dust_output": "Ağ kurallarına göre reddedilen işlem, düşük çıktı miktarı (toz). Lütfen miktarı artırın.", "tx_rejected_dust_output": "Ağ kurallarına göre reddedilen işlem, düşük çıktı miktarı (toz). Lütfen miktarı artırın.",
"tx_rejected_dust_output_send_all": "Ağ kurallarına göre reddedilen işlem, düşük çıktı miktarı (toz). Lütfen madeni para kontrolü altında seçilen madeni para dengesini kontrol edin.", "tx_rejected_dust_output_send_all": "Ağ kurallarına göre reddedilen işlem, düşük çıktı miktarı (toz). Lütfen madeni para kontrolü altında seçilen madeni para dengesini kontrol edin.",

View file

@ -746,8 +746,10 @@
"trusted": "довіряють", "trusted": "довіряють",
"tx_commit_exception_no_dust_on_change": "Транзакція відхилена цією сумою. За допомогою цих монет ви можете надіслати ${min} без змін або ${max}, що повертає зміни.", "tx_commit_exception_no_dust_on_change": "Транзакція відхилена цією сумою. За допомогою цих монет ви можете надіслати ${min} без змін або ${max}, що повертає зміни.",
"tx_commit_failed": "Транзакційна комісія не вдалося. Будь ласка, зв'яжіться з підтримкою.", "tx_commit_failed": "Транзакційна комісія не вдалося. Будь ласка, зв'яжіться з підтримкою.",
"tx_invalid_input": "Ви використовуєте неправильний тип введення для цього типу оплати",
"tx_no_dust_exception": "Угода відхиляється, відправивши суму занадто мала. Будь ласка, спробуйте збільшити суму.", "tx_no_dust_exception": "Угода відхиляється, відправивши суму занадто мала. Будь ласка, спробуйте збільшити суму.",
"tx_not_enough_inputs_exception": "Недостатньо доступних входів. Виберіть більше під контролем монети", "tx_not_enough_inputs_exception": "Недостатньо доступних входів. Виберіть більше під контролем монети",
"tx_rejected_bip68_final": "Трансакція має непідтверджені входи і не замінила плату.",
"tx_rejected_dust_change": "Транзакція відхилена за допомогою мережевих правил, низька кількість змін (пил). Спробуйте надіслати все або зменшити суму.", "tx_rejected_dust_change": "Транзакція відхилена за допомогою мережевих правил, низька кількість змін (пил). Спробуйте надіслати все або зменшити суму.",
"tx_rejected_dust_output": "Транзакція відхилена за допомогою мережевих правил, низька кількість вихідної кількості (пил). Будь ласка, збільшуйте суму.", "tx_rejected_dust_output": "Транзакція відхилена за допомогою мережевих правил, низька кількість вихідної кількості (пил). Будь ласка, збільшуйте суму.",
"tx_rejected_dust_output_send_all": "Транзакція відхилена за допомогою мережевих правил, низька кількість вихідної кількості (пил). Будь ласка, перевірте баланс монет, вибраних під контролем монет.", "tx_rejected_dust_output_send_all": "Транзакція відхилена за допомогою мережевих правил, низька кількість вихідної кількості (пил). Будь ласка, перевірте баланс монет, вибраних під контролем монет.",

View file

@ -747,8 +747,10 @@
"trusted": "قابل اعتماد", "trusted": "قابل اعتماد",
"tx_commit_exception_no_dust_on_change": "اس رقم سے لین دین کو مسترد کردیا گیا ہے۔ ان سککوں کے ذریعہ آپ بغیر کسی تبدیلی کے ${min} یا ${max} بھیج سکتے ہیں جو لوٹتے ہیں۔", "tx_commit_exception_no_dust_on_change": "اس رقم سے لین دین کو مسترد کردیا گیا ہے۔ ان سککوں کے ذریعہ آپ بغیر کسی تبدیلی کے ${min} یا ${max} بھیج سکتے ہیں جو لوٹتے ہیں۔",
"tx_commit_failed": "ٹرانزیکشن کمٹ ناکام ہوگیا۔ براہ کرم سپورٹ سے رابطہ کریں۔", "tx_commit_failed": "ٹرانزیکشن کمٹ ناکام ہوگیا۔ براہ کرم سپورٹ سے رابطہ کریں۔",
"tx_invalid_input": "آپ اس قسم کی ادائیگی کے لئے غلط ان پٹ کی قسم استعمال کررہے ہیں",
"tx_no_dust_exception": "لین دین کو بہت چھوٹی رقم بھیج کر مسترد کردیا جاتا ہے۔ براہ کرم رقم میں اضافہ کرنے کی کوشش کریں۔", "tx_no_dust_exception": "لین دین کو بہت چھوٹی رقم بھیج کر مسترد کردیا جاتا ہے۔ براہ کرم رقم میں اضافہ کرنے کی کوشش کریں۔",
"tx_not_enough_inputs_exception": "کافی ان پٹ دستیاب نہیں ہے۔ براہ کرم سکے کے کنٹرول میں مزید منتخب کریں", "tx_not_enough_inputs_exception": "کافی ان پٹ دستیاب نہیں ہے۔ براہ کرم سکے کے کنٹرول میں مزید منتخب کریں",
"tx_rejected_bip68_final": "لین دین میں غیر مصدقہ آدانوں کی ہے اور وہ فیس کے ذریعہ تبدیل کرنے میں ناکام رہا ہے۔",
"tx_rejected_dust_change": "نیٹ ورک کے قواعد ، کم تبدیلی کی رقم (دھول) کے ذریعہ لین دین کو مسترد کردیا گیا۔ سب کو بھیجنے یا رقم کو کم کرنے کی کوشش کریں۔", "tx_rejected_dust_change": "نیٹ ورک کے قواعد ، کم تبدیلی کی رقم (دھول) کے ذریعہ لین دین کو مسترد کردیا گیا۔ سب کو بھیجنے یا رقم کو کم کرنے کی کوشش کریں۔",
"tx_rejected_dust_output": "لین دین کو نیٹ ورک کے قواعد ، کم آؤٹ پٹ رقم (دھول) کے ذریعہ مسترد کردیا گیا۔ براہ کرم رقم میں اضافہ کریں۔", "tx_rejected_dust_output": "لین دین کو نیٹ ورک کے قواعد ، کم آؤٹ پٹ رقم (دھول) کے ذریعہ مسترد کردیا گیا۔ براہ کرم رقم میں اضافہ کریں۔",
"tx_rejected_dust_output_send_all": "لین دین کو نیٹ ورک کے قواعد ، کم آؤٹ پٹ رقم (دھول) کے ذریعہ مسترد کردیا گیا۔ براہ کرم سکے کے کنٹرول میں منتخب کردہ سکے کا توازن چیک کریں۔", "tx_rejected_dust_output_send_all": "لین دین کو نیٹ ورک کے قواعد ، کم آؤٹ پٹ رقم (دھول) کے ذریعہ مسترد کردیا گیا۔ براہ کرم سکے کے کنٹرول میں منتخب کردہ سکے کا توازن چیک کریں۔",

View file

@ -746,8 +746,10 @@
"trusted": "A ti fọkàn ẹ̀ tán", "trusted": "A ti fọkàn ẹ̀ tán",
"tx_commit_exception_no_dust_on_change": "Iṣowo naa ti kọ pẹlu iye yii. Pẹlu awọn owó wọnyi o le firanṣẹ ${min} laisi ayipada tabi ${max} ni iyipada iyipada.", "tx_commit_exception_no_dust_on_change": "Iṣowo naa ti kọ pẹlu iye yii. Pẹlu awọn owó wọnyi o le firanṣẹ ${min} laisi ayipada tabi ${max} ni iyipada iyipada.",
"tx_commit_failed": "Idunadura iṣowo kuna. Jọwọ kan si atilẹyin.", "tx_commit_failed": "Idunadura iṣowo kuna. Jọwọ kan si atilẹyin.",
"tx_invalid_input": "O nlo iru titẹ nkan ti ko tọ fun iru isanwo yii",
"tx_no_dust_exception": "Iṣowo naa ni kọ nipa fifiranṣẹ iye ti o kere ju. Jọwọ gbiyanju pọ si iye naa.", "tx_no_dust_exception": "Iṣowo naa ni kọ nipa fifiranṣẹ iye ti o kere ju. Jọwọ gbiyanju pọ si iye naa.",
"tx_not_enough_inputs_exception": "Ko to awọn titẹsi to. Jọwọ yan diẹ sii labẹ iṣakoso owo", "tx_not_enough_inputs_exception": "Ko to awọn titẹsi to. Jọwọ yan diẹ sii labẹ iṣakoso owo",
"tx_rejected_bip68_final": "Iṣowo ni awọn igbewọle gbangba ati kuna lati rọpo nipasẹ owo.",
"tx_rejected_dust_change": "Idunadura kọ nipasẹ awọn ofin nẹtiwọọki, iye iyipada kekere (eruku). Gbiyanju lati firanṣẹ gbogbo rẹ tabi dinku iye.", "tx_rejected_dust_change": "Idunadura kọ nipasẹ awọn ofin nẹtiwọọki, iye iyipada kekere (eruku). Gbiyanju lati firanṣẹ gbogbo rẹ tabi dinku iye.",
"tx_rejected_dust_output": "Idunadura kọ nipasẹ awọn ofin nẹtiwọọki, iye ti o wuwe kekere (eruku). Jọwọ mu iye naa pọ si.", "tx_rejected_dust_output": "Idunadura kọ nipasẹ awọn ofin nẹtiwọọki, iye ti o wuwe kekere (eruku). Jọwọ mu iye naa pọ si.",
"tx_rejected_dust_output_send_all": "Idunadura kọ nipasẹ awọn ofin nẹtiwọọki, iye ti o wuwe kekere (eruku). Jọwọ ṣayẹwo dọgbadọgba ti awọn owo ti a yan labẹ iṣakoso owo.", "tx_rejected_dust_output_send_all": "Idunadura kọ nipasẹ awọn ofin nẹtiwọọki, iye ti o wuwe kekere (eruku). Jọwọ ṣayẹwo dọgbadọgba ti awọn owo ti a yan labẹ iṣakoso owo.",

View file

@ -745,8 +745,10 @@
"trusted": "值得信赖", "trusted": "值得信赖",
"tx_commit_exception_no_dust_on_change": "交易被此金额拒绝。使用这些硬币,您可以发送${min}无需更改或返回${max}的变化。", "tx_commit_exception_no_dust_on_change": "交易被此金额拒绝。使用这些硬币,您可以发送${min}无需更改或返回${max}的变化。",
"tx_commit_failed": "交易承诺失败。请联系支持。", "tx_commit_failed": "交易承诺失败。请联系支持。",
"tx_invalid_input": "您正在使用错误的输入类型进行此类付款",
"tx_no_dust_exception": "通过发送太小的金额来拒绝交易。请尝试增加金额。", "tx_no_dust_exception": "通过发送太小的金额来拒绝交易。请尝试增加金额。",
"tx_not_enough_inputs_exception": "没有足够的输入。请在硬币控制下选择更多", "tx_not_enough_inputs_exception": "没有足够的输入。请在硬币控制下选择更多",
"tx_rejected_bip68_final": "交易未确认投入,未能取代费用。",
"tx_rejected_dust_change": "交易被网络规则拒绝,较低的变化数量(灰尘)。尝试发送全部或减少金额。", "tx_rejected_dust_change": "交易被网络规则拒绝,较低的变化数量(灰尘)。尝试发送全部或减少金额。",
"tx_rejected_dust_output": "交易被网络规则,低输出量(灰尘)拒绝。请增加金额。", "tx_rejected_dust_output": "交易被网络规则,低输出量(灰尘)拒绝。请增加金额。",
"tx_rejected_dust_output_send_all": "交易被网络规则,低输出量(灰尘)拒绝。请检查在硬币控制下选择的硬币的余额。", "tx_rejected_dust_output_send_all": "交易被网络规则,低输出量(灰尘)拒绝。请检查在硬币控制下选择的硬币的余额。",

View file

@ -173,6 +173,7 @@ abstract class Bitcoin {
Future<void> rescan(Object wallet, {required int height, bool? doSingleScan}); Future<void> rescan(Object wallet, {required int height, bool? doSingleScan});
bool getNodeIsCakeElectrs(Object wallet); bool getNodeIsCakeElectrs(Object wallet);
void deleteSilentPaymentAddress(Object wallet, String address); void deleteSilentPaymentAddress(Object wallet, String address);
Future<void> updateFeeRates(Object wallet);
} }
"""; """;