mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-12-23 12:09:43 +00:00
feat: change scanning to subscription model, sync improvements
This commit is contained in:
parent
a887ea74b5
commit
e4156ba282
51 changed files with 696 additions and 406 deletions
|
@ -90,7 +90,8 @@ class BitcoinAddressRecord extends BaseBitcoinAddressRecord {
|
|||
|
||||
String? scriptHash;
|
||||
|
||||
String updateScriptHash(BasedUtxoNetwork network) {
|
||||
String getScriptHash(BasedUtxoNetwork network) {
|
||||
if (scriptHash != null) return scriptHash!;
|
||||
scriptHash = sh.scriptHash(address, network: network);
|
||||
return scriptHash!;
|
||||
}
|
||||
|
|
|
@ -2,18 +2,16 @@ import 'package:cw_bitcoin/bitcoin_address_record.dart';
|
|||
import 'package:cw_core/unspent_transaction_output.dart';
|
||||
|
||||
class BitcoinUnspent extends Unspent {
|
||||
BitcoinUnspent(BaseBitcoinAddressRecord addressRecord, String hash, int value, int vout,
|
||||
{this.silentPaymentTweak})
|
||||
BitcoinUnspent(BaseBitcoinAddressRecord addressRecord, String hash, int value, int vout)
|
||||
: bitcoinAddressRecord = addressRecord,
|
||||
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(
|
||||
address,
|
||||
address ?? BitcoinAddressRecord.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?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
|
@ -22,11 +20,48 @@ class BitcoinUnspent extends Unspent {
|
|||
'tx_hash': hash,
|
||||
'value': value,
|
||||
'tx_pos': vout,
|
||||
'silent_payment_tweak': silentPaymentTweak,
|
||||
};
|
||||
return json;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -64,19 +64,9 @@ abstract class BitcoinWalletBase extends ElectrumWallet with Store {
|
|||
),
|
||||
);
|
||||
|
||||
hasSilentPaymentsScanning = true;
|
||||
|
||||
autorun((_) {
|
||||
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({
|
||||
|
|
|
@ -36,14 +36,15 @@ class ElectrumClient {
|
|||
_errors = {},
|
||||
unterminatedString = '';
|
||||
|
||||
static const connectionTimeout = Duration(seconds: 300);
|
||||
static const aliveTimerDuration = Duration(seconds: 300);
|
||||
static const connectionTimeout = Duration(seconds: 10);
|
||||
static const aliveTimerDuration = Duration(seconds: 10);
|
||||
|
||||
bool get isConnected => _isConnected;
|
||||
Socket? socket;
|
||||
void Function(bool)? onConnectionStatusChange;
|
||||
int _id;
|
||||
final Map<String, SocketTask> _tasks;
|
||||
Map<String, SocketTask> get tasks => _tasks;
|
||||
final Map<String, String> _errors;
|
||||
bool _isConnected;
|
||||
Timer? _aliveTimer;
|
||||
|
@ -277,11 +278,18 @@ class ElectrumClient {
|
|||
Future<Map<String, dynamic>> getHeader({required int height}) async =>
|
||||
await call(method: 'blockchain.block.get_header', params: [height]) as Map<String, dynamic>;
|
||||
|
||||
Future<Map<String, dynamic>> getTweaks({required int height, required int count}) async =>
|
||||
await callWithTimeout(
|
||||
method: 'blockchain.block.tweaks',
|
||||
params: [height, count],
|
||||
timeout: 10000) as Map<String, dynamic>;
|
||||
BehaviorSubject<Object>? tweaksSubscribe({required int height}) {
|
||||
_id += 1;
|
||||
return subscribe<Object>(
|
||||
id: 'blockchain.tweaks.subscribe',
|
||||
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}) =>
|
||||
call(method: 'blockchain.estimatefee', params: [p]).then((dynamic result) {
|
||||
|
@ -325,9 +333,6 @@ class ElectrumClient {
|
|||
});
|
||||
|
||||
Future<List<int>> feeRates({BasedUtxoNetwork? network}) async {
|
||||
if (network == BitcoinNetwork.testnet) {
|
||||
return [1, 1, 1];
|
||||
}
|
||||
try {
|
||||
final topDoubleString = await estimatefee(p: 1);
|
||||
final middleDoubleString = await estimatefee(p: 5);
|
||||
|
@ -349,7 +354,7 @@ class ElectrumClient {
|
|||
// "hex": "00000020890208a0ae3a3892aa047c5468725846577cfcd9b512b50000000000000000005dc2b02f2d297a9064ee103036c14d678f9afc7e3d9409cf53fd58b82e938e8ecbeca05a2d2103188ce804c4"
|
||||
// }
|
||||
Future<int?> getCurrentBlockChainTip() =>
|
||||
call(method: 'blockchain.headers.subscribe').then((result) {
|
||||
callWithTimeout(method: 'blockchain.headers.subscribe').then((result) {
|
||||
if (result is Map<String, dynamic>) {
|
||||
return result["height"] as int;
|
||||
}
|
||||
|
@ -357,6 +362,12 @@ class ElectrumClient {
|
|||
return null;
|
||||
});
|
||||
|
||||
BehaviorSubject<Object>? chainTipSubscribe() {
|
||||
_id += 1;
|
||||
return subscribe<Object>(
|
||||
id: 'blockchain.headers.subscribe', method: 'blockchain.headers.subscribe');
|
||||
}
|
||||
|
||||
BehaviorSubject<Object>? chainTipUpdate() {
|
||||
_id += 1;
|
||||
return subscribe<Object>(
|
||||
|
@ -456,6 +467,11 @@ class ElectrumClient {
|
|||
|
||||
_tasks[id]?.subject?.add(params.last);
|
||||
break;
|
||||
case 'blockchain.tweaks.subscribe':
|
||||
case 'blockchain.headers.subscribe':
|
||||
final params = request['params'] as List<dynamic>;
|
||||
_tasks[method]?.subject?.add(params.last);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ class ElectrumBalance extends Balance {
|
|||
}
|
||||
|
||||
int confirmed;
|
||||
final int unconfirmed;
|
||||
int unconfirmed;
|
||||
final int frozen;
|
||||
|
||||
@override
|
||||
|
|
|
@ -18,7 +18,7 @@ class ElectrumTransactionBundle {
|
|||
}
|
||||
|
||||
class ElectrumTransactionInfo extends TransactionInfo {
|
||||
List<BitcoinUnspent>? unspents;
|
||||
List<BitcoinSilentPaymentsUnspent>? unspents;
|
||||
|
||||
ElectrumTransactionInfo(this.type,
|
||||
{required String id,
|
||||
|
@ -178,9 +178,8 @@ class ElectrumTransactionInfo extends TransactionInfo {
|
|||
outputAddresses.isEmpty ? [] : outputAddresses.map((e) => e.toString()).toList(),
|
||||
to: data['to'] as String?,
|
||||
unspents: unspents
|
||||
.map((unspent) => BitcoinUnspent.fromJSON(
|
||||
BitcoinSilentPaymentAddressRecord.fromJSON(unspent['address_record'].toString()),
|
||||
unspent as Map<String, dynamic>))
|
||||
.map((unspent) =>
|
||||
BitcoinSilentPaymentsUnspent.fromJSON(null, unspent as Map<String, dynamic>))
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -91,7 +91,13 @@ abstract class ElectrumWalletBase
|
|||
transactionHistory = ElectrumTransactionHistory(walletInfo: walletInfo, password: password);
|
||||
|
||||
reaction((_) => syncStatus, (SyncStatus syncStatus) {
|
||||
if (syncStatus is! AttemptingSyncStatus)
|
||||
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
|
||||
SyncStatus syncStatus;
|
||||
|
||||
List<String> get scriptHashes => walletAddresses.allAddresses
|
||||
.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();
|
||||
Set<String> get addressesSet => walletAddresses.allAddresses.map((addr) => addr.address).toSet();
|
||||
|
||||
String get xpub => hd.base58!;
|
||||
|
||||
|
@ -142,8 +141,8 @@ abstract class ElectrumWalletBase
|
|||
@override
|
||||
bool? isTestnet;
|
||||
|
||||
@observable
|
||||
bool hasSilentPaymentsScanning = false;
|
||||
bool get hasSilentPaymentsScanning => type == WalletType.bitcoin;
|
||||
|
||||
@observable
|
||||
bool nodeSupportsSilentPayments = true;
|
||||
@observable
|
||||
|
@ -151,13 +150,14 @@ abstract class ElectrumWalletBase
|
|||
|
||||
@action
|
||||
Future<void> setSilentPaymentsScanning(bool active) async {
|
||||
syncStatus = AttemptingSyncStatus();
|
||||
silentPaymentsScanningActive = active;
|
||||
|
||||
if (active) {
|
||||
await _setInitialHeight();
|
||||
|
||||
if ((currentChainTip ?? 0) > walletInfo.restoreHeight) {
|
||||
_setListeners(walletInfo.restoreHeight, chainTip: currentChainTip);
|
||||
if (await currentChainTip > walletInfo.restoreHeight) {
|
||||
_setListeners(walletInfo.restoreHeight, chainTipParam: _currentChainTip);
|
||||
}
|
||||
} else {
|
||||
_isolate?.then((runningIsolate) => runningIsolate.kill(priority: Isolate.immediate));
|
||||
|
@ -174,7 +174,11 @@ abstract class ElectrumWalletBase
|
|||
}
|
||||
|
||||
@observable
|
||||
int? currentChainTip;
|
||||
int? _currentChainTip;
|
||||
|
||||
@computed
|
||||
Future<int> get currentChainTip async =>
|
||||
_currentChainTip ?? await electrumClient.getCurrentBlockChainTip() ?? 0;
|
||||
|
||||
@override
|
||||
BitcoinWalletKeys get keys =>
|
||||
|
@ -183,7 +187,9 @@ abstract class ElectrumWalletBase
|
|||
String _password;
|
||||
List<BitcoinUnspent> unspentCoins;
|
||||
List<int> _feeRates;
|
||||
// ignore: prefer_final_fields
|
||||
Map<String, BehaviorSubject<Object>?> _scripthashesUpdateSubject;
|
||||
// ignore: prefer_final_fields
|
||||
BehaviorSubject<Object>? _chainTipUpdateSubject;
|
||||
bool _isTransactionUpdating;
|
||||
Future<Isolate>? _isolate;
|
||||
|
@ -201,8 +207,8 @@ abstract class ElectrumWalletBase
|
|||
}
|
||||
|
||||
@action
|
||||
Future<void> _setListeners(int height, {int? chainTip, bool? doSingleScan}) async {
|
||||
final currentChainTip = chainTip ?? await electrumClient.getCurrentBlockChainTip() ?? 0;
|
||||
Future<void> _setListeners(int height, {int? chainTipParam, bool? doSingleScan}) async {
|
||||
final chainTip = chainTipParam ?? await currentChainTip;
|
||||
syncStatus = AttemptingSyncStatus();
|
||||
|
||||
if (_isolate != null) {
|
||||
|
@ -218,7 +224,7 @@ abstract class ElectrumWalletBase
|
|||
silentAddress: walletAddresses.silentAddress!,
|
||||
network: network,
|
||||
height: height,
|
||||
chainTip: currentChainTip,
|
||||
chainTip: chainTip,
|
||||
electrumClient: ElectrumClient(),
|
||||
transactionHistoryIds: transactionHistory.transactions.keys.toList(),
|
||||
node: ScanNode(node!.uri, node!.useSSL),
|
||||
|
@ -237,12 +243,33 @@ abstract class ElectrumWalletBase
|
|||
final tx = map.value;
|
||||
|
||||
if (tx.unspents != null) {
|
||||
tx.unspents!.forEach((unspent) => walletAddresses.addSilentAddresses(
|
||||
[unspent.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord]));
|
||||
|
||||
final existingTxInfo = transactionHistory.transactions[txid];
|
||||
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
|
||||
if (txAlreadyExisted) {
|
||||
existingTxInfo.amount = tx.amount;
|
||||
|
@ -258,37 +285,39 @@ abstract class ElectrumWalletBase
|
|||
.toList();
|
||||
|
||||
if (newUnspents.isNotEmpty) {
|
||||
newUnspents.forEach(updateSilentAddressRecord);
|
||||
|
||||
existingTxInfo.unspents ??= [];
|
||||
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[0].value;
|
||||
|
||||
if (existingTxInfo.direction == TransactionDirection.incoming) {
|
||||
existingTxInfo.amount += amount;
|
||||
} else {
|
||||
existingTxInfo.amount = amount;
|
||||
existingTxInfo.direction = TransactionDirection.incoming;
|
||||
existingTxInfo.amount += newAmount;
|
||||
}
|
||||
|
||||
// Updates existing TX
|
||||
transactionHistory.addOne(existingTxInfo);
|
||||
// Update balance record
|
||||
balance[currency]!.confirmed += newAmount;
|
||||
}
|
||||
} else {
|
||||
final addressRecord =
|
||||
walletAddresses.silentAddresses.firstWhere((addr) => addr.address == tx.to);
|
||||
addressRecord.txCount += 1;
|
||||
// else: First time seeing this TX after scanning
|
||||
tx.unspents!.forEach(updateSilentAddressRecord);
|
||||
|
||||
// Add new TX record
|
||||
transactionHistory.addMany(message);
|
||||
// Update balance record
|
||||
balance[currency]!.confirmed += tx.amount;
|
||||
}
|
||||
|
||||
await transactionHistory.save();
|
||||
await updateUnspent();
|
||||
await updateBalance();
|
||||
await updateAllUnspents();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check if is a SyncStatus type since "is SyncStatus" doesn't work here
|
||||
if (message is SyncResponse) {
|
||||
if (message.syncStatus is UnsupportedSyncStatus) {
|
||||
nodeSupportsSilentPayments = false;
|
||||
|
@ -296,7 +325,6 @@ abstract class ElectrumWalletBase
|
|||
|
||||
syncStatus = message.syncStatus;
|
||||
walletInfo.restoreHeight = message.height;
|
||||
await walletInfo.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -305,32 +333,25 @@ abstract class ElectrumWalletBase
|
|||
@override
|
||||
Future<void> startSync() async {
|
||||
try {
|
||||
syncStatus = AttemptingSyncStatus();
|
||||
syncStatus = SyncronizingSyncStatus();
|
||||
|
||||
if (hasSilentPaymentsScanning) {
|
||||
try {
|
||||
await _setInitialHeight();
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
if (silentPaymentsScanningActive) {
|
||||
if ((currentChainTip ?? 0) > walletInfo.restoreHeight) {
|
||||
_setListeners(walletInfo.restoreHeight, chainTip: currentChainTip);
|
||||
}
|
||||
await _subscribeForUpdates();
|
||||
|
||||
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();
|
||||
_subscribeForUpdates();
|
||||
await updateUnspent();
|
||||
await updateBalance();
|
||||
_feeRates = await electrumClient.feeRates(network: network);
|
||||
|
||||
Timer.periodic(
|
||||
const Duration(minutes: 1), (timer) async => _feeRates = await electrumClient.feeRates());
|
||||
|
||||
if (!silentPaymentsScanningActive || walletInfo.restoreHeight == currentChainTip) {
|
||||
syncStatus = SyncedSyncStatus();
|
||||
}
|
||||
updateAllUnspents().then(checkFinishedAllUpdates);
|
||||
updateBalance().then(checkFinishedAllUpdates);
|
||||
} catch (e, stacktrace) {
|
||||
print(stacktrace);
|
||||
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;
|
||||
|
||||
@action
|
||||
@override
|
||||
Future<void> connectToNode({required Node node}) async {
|
||||
final differentNode = this.node?.uri != node.uri || this.node?.useSSL != node.useSSL;
|
||||
this.node = node;
|
||||
|
||||
try {
|
||||
syncStatus = ConnectingSyncStatus();
|
||||
|
||||
if (!electrumClient.isConnected) {
|
||||
electrumClient.onConnectionStatusChange = null;
|
||||
await electrumClient.close();
|
||||
}
|
||||
|
||||
await Timer(Duration(seconds: differentNode ? 0 : 10), () async {
|
||||
electrumClient.onConnectionStatusChange = (bool isConnected) async {
|
||||
if (isConnected) {
|
||||
if (isConnected && syncStatus is! SyncedSyncStatus) {
|
||||
syncStatus = ConnectedSyncStatus();
|
||||
} else if (isConnected == false) {
|
||||
} else if (!isConnected) {
|
||||
syncStatus = LostConnectionSyncStatus();
|
||||
}
|
||||
};
|
||||
|
||||
await electrumClient.connectToUri(node.uri, useSSL: node.useSSL);
|
||||
});
|
||||
} catch (e) {
|
||||
print(e.toString());
|
||||
syncStatus = FailedSyncStatus();
|
||||
|
@ -436,8 +467,15 @@ abstract class ElectrumWalletBase
|
|||
for (int i = 0; i < unspentCoins.length; i++) {
|
||||
final utx = unspentCoins[i];
|
||||
|
||||
if (utx.isSending) {
|
||||
spendsCPFP = utx.confirmations == 0;
|
||||
if (utx.isSending && !utx.isFrozen) {
|
||||
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;
|
||||
|
||||
|
@ -447,11 +485,10 @@ abstract class ElectrumWalletBase
|
|||
bool? isSilentPayment = false;
|
||||
|
||||
if (utx.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) {
|
||||
final unspentAddress = utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord;
|
||||
privkey = walletAddresses.silentAddress!.b_spend.tweakAdd(
|
||||
BigintUtils.fromBytes(
|
||||
BytesUtils.fromHexString(
|
||||
(utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord).silentPaymentTweak!,
|
||||
),
|
||||
BytesUtils.fromHexString(unspentAddress.silentPaymentTweak!),
|
||||
),
|
||||
);
|
||||
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());
|
||||
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
|
||||
int amount = allInputsAmount - fee;
|
||||
|
||||
if (amount <= 0) {
|
||||
throw BitcoinTransactionWrongBalanceException();
|
||||
}
|
||||
|
||||
// Attempting to send less than the dust limit
|
||||
if (_isBelowDust(amount)) {
|
||||
throw BitcoinTransactionNoDustException();
|
||||
|
@ -580,11 +625,10 @@ abstract class ElectrumWalletBase
|
|||
bool? isSilentPayment = false;
|
||||
|
||||
if (utx.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) {
|
||||
final unspentAddress = utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord;
|
||||
privkey = walletAddresses.silentAddress!.b_spend.tweakAdd(
|
||||
BigintUtils.fromBytes(
|
||||
BytesUtils.fromHexString(
|
||||
(utx.bitcoinAddressRecord as BitcoinSilentPaymentAddressRecord).silentPaymentTweak!,
|
||||
),
|
||||
BytesUtils.fromHexString(unspentAddress.silentPaymentTweak!),
|
||||
),
|
||||
);
|
||||
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());
|
||||
vinOutpoints.add(Outpoint(txid: utx.hash, index: utx.vout));
|
||||
|
||||
|
@ -637,6 +685,16 @@ abstract class ElectrumWalletBase
|
|||
int amountLeftForChangeAndFee = allInputsAmount - credentialsAmount;
|
||||
|
||||
if (amountLeftForChangeAndFee <= 0) {
|
||||
if (!spendingAllCoins) {
|
||||
return estimateTxForAmount(
|
||||
credentialsAmount,
|
||||
outputs,
|
||||
feeRate,
|
||||
inputsCount: utxos.length + 1,
|
||||
memo: memo,
|
||||
hasSilentPayment: hasSilentPayment,
|
||||
);
|
||||
}
|
||||
throw BitcoinTransactionWrongBalanceException();
|
||||
}
|
||||
|
||||
|
@ -684,6 +742,7 @@ abstract class ElectrumWalletBase
|
|||
|
||||
// Still has inputs to spend before failing
|
||||
if (!spendingAllCoins) {
|
||||
outputs.removeLast();
|
||||
return estimateTxForAmount(
|
||||
credentialsAmount,
|
||||
outputs,
|
||||
|
@ -730,6 +789,7 @@ abstract class ElectrumWalletBase
|
|||
outputs.removeLast();
|
||||
}
|
||||
|
||||
outputs.removeLast();
|
||||
return estimateTxForAmount(
|
||||
credentialsAmount,
|
||||
outputs,
|
||||
|
@ -1025,7 +1085,8 @@ abstract class ElectrumWalletBase
|
|||
|
||||
Future<String> makePath() async => pathForWallet(name: walletInfo.name, type: walletInfo.type);
|
||||
|
||||
Future<void> updateUnspent() async {
|
||||
@action
|
||||
Future<void> updateAllUnspents() async {
|
||||
List<BitcoinUnspent> updatedUnspentCoins = [];
|
||||
|
||||
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) => 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 (_) {}
|
||||
}))));
|
||||
await Future.wait(walletAddresses.allAddresses.map((address) async {
|
||||
updatedUnspentCoins.addAll(await fetchUnspent(address));
|
||||
}));
|
||||
|
||||
unspentCoins = updatedUnspentCoins;
|
||||
|
||||
|
@ -1072,6 +1122,7 @@ abstract class ElectrumWalletBase
|
|||
coin.isFrozen = coinInfo.isFrozen;
|
||||
coin.isSending = coinInfo.isSending;
|
||||
coin.note = coinInfo.note;
|
||||
coin.bitcoinAddressRecord.balance += coinInfo.value;
|
||||
} else {
|
||||
_addCoinInfo(coin);
|
||||
}
|
||||
|
@ -1081,6 +1132,55 @@ abstract class ElectrumWalletBase
|
|||
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
|
||||
Future<void> _addCoinInfo(BitcoinUnspent coin) async {
|
||||
final newInfo = UnspentCoinsInfo(
|
||||
|
@ -1093,6 +1193,7 @@ abstract class ElectrumWalletBase
|
|||
value: coin.value,
|
||||
vout: coin.vout,
|
||||
isChange: coin.isChange,
|
||||
isSilentPayment: coin is BitcoinSilentPaymentsUnspent,
|
||||
);
|
||||
|
||||
await unspentCoinsInfo.add(newInfo);
|
||||
|
@ -1309,8 +1410,8 @@ abstract class ElectrumWalletBase
|
|||
|
||||
time = status["block_time"] as int?;
|
||||
final height = status["block_height"] as int? ?? 0;
|
||||
confirmations =
|
||||
height > 0 ? (await electrumClient.getCurrentBlockChainTip())! - height + 1 : 0;
|
||||
final tip = await currentChainTip;
|
||||
if (tip > 0) confirmations = height > 0 ? tip - height + 1 : 0;
|
||||
} else {
|
||||
final verboseTransaction = await electrumClient.getTransactionRaw(hash: hash);
|
||||
|
||||
|
@ -1335,18 +1436,15 @@ abstract class ElectrumWalletBase
|
|||
}
|
||||
|
||||
Future<ElectrumTransactionInfo?> fetchTransactionInfo(
|
||||
{required String hash,
|
||||
required int height,
|
||||
required Set<String> myAddresses,
|
||||
bool? retryOnFailure}) async {
|
||||
{required String hash, required int height, bool? retryOnFailure}) async {
|
||||
try {
|
||||
return ElectrumTransactionInfo.fromElectrumBundle(
|
||||
await getTransactionExpanded(hash: hash), walletInfo.type, network,
|
||||
addresses: myAddresses, height: height);
|
||||
addresses: addressesSet, height: height);
|
||||
} catch (e) {
|
||||
if (e is FormatException && retryOnFailure == true) {
|
||||
await Future.delayed(const Duration(seconds: 2));
|
||||
return fetchTransactionInfo(hash: hash, height: height, myAddresses: myAddresses);
|
||||
return fetchTransactionInfo(hash: hash, height: height);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -1356,11 +1454,15 @@ abstract class ElectrumWalletBase
|
|||
Future<Map<String, ElectrumTransactionInfo>> fetchTransactions() async {
|
||||
try {
|
||||
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(
|
||||
(type) => fetchTransactionsForAddressType(addressesSet, historiesWithDetails, type)));
|
||||
if (type == WalletType.bitcoin) {
|
||||
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;
|
||||
} catch (e) {
|
||||
|
@ -1370,7 +1472,6 @@ abstract class ElectrumWalletBase
|
|||
}
|
||||
|
||||
Future<void> fetchTransactionsForAddressType(
|
||||
Set<String> addressesSet,
|
||||
Map<String, ElectrumTransactionInfo> historiesWithDetails,
|
||||
BitcoinAddressType type,
|
||||
) async {
|
||||
|
@ -1379,39 +1480,50 @@ abstract class ElectrumWalletBase
|
|||
final receiveAddresses = addressesByType.where((addr) => addr.isHidden == false);
|
||||
|
||||
await Future.wait(addressesByType.map((addressRecord) async {
|
||||
final history = await _fetchAddressHistory(addressRecord, addressesSet, currentChainTip!);
|
||||
final history = await _fetchAddressHistory(addressRecord, await currentChainTip);
|
||||
|
||||
if (history.isNotEmpty) {
|
||||
addressRecord.txCount = history.length;
|
||||
historiesWithDetails.addAll(history);
|
||||
|
||||
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) {
|
||||
// The last address by gap limit is used, discover new addresses for the same address type
|
||||
if (isUsedAddressUnderGap) {
|
||||
final prevLength = walletAddresses.allAddresses.length;
|
||||
|
||||
// Discover new addresses for the same address type until the gap limit is respected
|
||||
await walletAddresses.discoverAddresses(
|
||||
matchedAddresses.toList(),
|
||||
addressRecord.isHidden,
|
||||
(address, addressesSet) => _fetchAddressHistory(address, addressesSet, currentChainTip!)
|
||||
.then((history) => history.isNotEmpty ? address.address : null),
|
||||
(address) async {
|
||||
await _subscribeForUpdates();
|
||||
return _fetchAddressHistory(address, await currentChainTip)
|
||||
.then((history) => history.isNotEmpty ? address.address : null);
|
||||
},
|
||||
type: type,
|
||||
);
|
||||
|
||||
// Continue until the last address by this address type is not used yet
|
||||
await fetchTransactionsForAddressType(addressesSet, historiesWithDetails, type);
|
||||
final newLength = walletAddresses.allAddresses.length;
|
||||
|
||||
if (newLength > prevLength) {
|
||||
await fetchTransactionsForAddressType(historiesWithDetails, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
Future<Map<String, ElectrumTransactionInfo>> _fetchAddressHistory(
|
||||
BitcoinAddressRecord addressRecord, Set<String> addressesSet, int currentHeight) async {
|
||||
BitcoinAddressRecord addressRecord, int? currentHeight) async {
|
||||
try {
|
||||
final Map<String, ElectrumTransactionInfo> historiesWithDetails = {};
|
||||
|
||||
final history = await electrumClient
|
||||
.getHistory(addressRecord.scriptHash ?? addressRecord.updateScriptHash(network));
|
||||
final history = await electrumClient.getHistory(addressRecord.getScriptHash(network));
|
||||
|
||||
if (history.isNotEmpty) {
|
||||
addressRecord.setAsUsed();
|
||||
|
@ -1425,14 +1537,13 @@ abstract class ElectrumWalletBase
|
|||
if (height > 0) {
|
||||
storedTx.height = height;
|
||||
// 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;
|
||||
}
|
||||
|
||||
historiesWithDetails[txid] = storedTx;
|
||||
} else {
|
||||
final tx = await fetchTransactionInfo(
|
||||
hash: txid, height: height, myAddresses: addressesSet, retryOnFailure: true);
|
||||
final tx = await fetchTransactionInfo(hash: txid, height: height, retryOnFailure: true);
|
||||
|
||||
if (tx != null) {
|
||||
historiesWithDetails[txid] = tx;
|
||||
|
@ -1462,9 +1573,9 @@ abstract class ElectrumWalletBase
|
|||
return;
|
||||
}
|
||||
|
||||
transactionHistory.transactions.values.forEach((tx) {
|
||||
if (tx.unspents != null && currentChainTip != null) {
|
||||
tx.confirmations = currentChainTip! - tx.height + 1;
|
||||
transactionHistory.transactions.values.forEach((tx) async {
|
||||
if (tx.unspents != null && tx.unspents!.isNotEmpty && tx.height > 0) {
|
||||
tx.confirmations = await currentChainTip - tx.height + 1;
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -1479,33 +1590,24 @@ abstract class ElectrumWalletBase
|
|||
}
|
||||
}
|
||||
|
||||
void _subscribeForUpdates() async {
|
||||
scriptHashes.forEach((sh) async {
|
||||
Future<void> _subscribeForUpdates() 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();
|
||||
_scripthashesUpdateSubject[sh] = electrumClient.scripthashUpdate(sh);
|
||||
_scripthashesUpdateSubject[sh] = await electrumClient.scripthashUpdate(sh);
|
||||
_scripthashesUpdateSubject[sh]?.listen((event) async {
|
||||
try {
|
||||
await updateUnspent();
|
||||
await updateBalance();
|
||||
await updateTransactions();
|
||||
} catch (e, s) {
|
||||
print(e.toString());
|
||||
_onError?.call(FlutterErrorDetails(
|
||||
exception: e,
|
||||
stack: s,
|
||||
library: this.runtimeType.toString(),
|
||||
));
|
||||
}
|
||||
});
|
||||
});
|
||||
await updateUnspents(address);
|
||||
|
||||
await _chainTipUpdateSubject?.close();
|
||||
_chainTipUpdateSubject = electrumClient.chainTipUpdate();
|
||||
_chainTipUpdateSubject?.listen((_) async {
|
||||
try {
|
||||
final currentHeight = await electrumClient.getCurrentBlockChainTip();
|
||||
if (currentHeight != null) walletInfo.restoreHeight = currentHeight;
|
||||
_setListeners(walletInfo.restoreHeight, chainTip: currentHeight);
|
||||
final newBalance = await _fetchBalance(sh);
|
||||
balance[currency]?.confirmed += newBalance.confirmed;
|
||||
balance[currency]?.unconfirmed += newBalance.unconfirmed;
|
||||
|
||||
await _fetchAddressHistory(address, await currentChainTip);
|
||||
} catch (e, s) {
|
||||
print(e.toString());
|
||||
_onError?.call(FlutterErrorDetails(
|
||||
|
@ -1515,6 +1617,7 @@ abstract class ElectrumWalletBase
|
|||
));
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
Future<ElectrumBalance> _fetchBalances() async {
|
||||
|
@ -1534,17 +1637,15 @@ abstract class ElectrumWalletBase
|
|||
if (hasSilentPaymentsScanning) {
|
||||
// Add values from unspent coins that are not fetched by the address list
|
||||
// i.e. scanned silent payments
|
||||
unspentCoinsInfo.values.forEach((info) {
|
||||
unspentCoins.forEach((element) {
|
||||
if (element.hash == info.hash &&
|
||||
element.bitcoinAddressRecord.address == info.address &&
|
||||
element.value == info.value) {
|
||||
if (info.isFrozen) totalFrozen += element.value;
|
||||
if (element.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) {
|
||||
totalConfirmed += element.value;
|
||||
}
|
||||
transactionHistory.transactions.values.forEach((tx) {
|
||||
if (tx.unspents != null) {
|
||||
tx.unspents!.forEach((unspent) {
|
||||
if (unspent.bitcoinAddressRecord is BitcoinSilentPaymentAddressRecord) {
|
||||
if (unspent.isFrozen) totalFrozen += unspent.value;
|
||||
totalConfirmed += unspent.value;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1567,6 +1668,13 @@ abstract class ElectrumWalletBase
|
|||
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 {
|
||||
balance[currency] = await _fetchBalances();
|
||||
await save();
|
||||
|
@ -1597,10 +1705,19 @@ abstract class ElectrumWalletBase
|
|||
}
|
||||
|
||||
Future<void> _setInitialHeight() async {
|
||||
currentChainTip = await electrumClient.getCurrentBlockChainTip();
|
||||
if (currentChainTip != null && walletInfo.restoreHeight == 0) {
|
||||
walletInfo.restoreHeight = currentChainTip!;
|
||||
if (_chainTipUpdateSubject != null) return;
|
||||
|
||||
_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) {
|
||||
|
@ -1679,8 +1796,6 @@ class SyncResponse {
|
|||
}
|
||||
|
||||
Future<void> startRefresh(ScanData scanData) async {
|
||||
var cachedBlockchainHeight = scanData.chainTip;
|
||||
|
||||
Future<ElectrumClient> getElectrumConnection() async {
|
||||
final electrumClient = scanData.electrumClient;
|
||||
if (!electrumClient.isConnected) {
|
||||
|
@ -1690,11 +1805,6 @@ Future<void> startRefresh(ScanData scanData) async {
|
|||
return electrumClient;
|
||||
}
|
||||
|
||||
Future<int> getUpdatedNodeHeight() async {
|
||||
final electrumClient = await getElectrumConnection();
|
||||
return await electrumClient.getCurrentBlockChainTip() ?? cachedBlockchainHeight;
|
||||
}
|
||||
|
||||
var lastKnownBlockHeight = 0;
|
||||
var initialSyncHeight = 0;
|
||||
|
||||
|
@ -1714,60 +1824,54 @@ Future<void> startRefresh(ScanData scanData) async {
|
|||
return;
|
||||
}
|
||||
|
||||
// Run this until no more blocks left to scan txs. At first this was recursive
|
||||
// i.e. re-calling the startRefresh function but this was easier for the above values to retain
|
||||
// their initial values
|
||||
while (true) {
|
||||
BehaviorSubject<Object>? tweaksSubscription = null;
|
||||
|
||||
lastKnownBlockHeight = syncHeight;
|
||||
|
||||
SyncingSyncStatus syncingStatus;
|
||||
if (scanData.isSingleScan) {
|
||||
syncingStatus = SyncingSyncStatus(1, 0);
|
||||
} else {
|
||||
syncingStatus = SyncingSyncStatus.fromHeightValues(
|
||||
await getUpdatedNodeHeight(), initialSyncHeight, syncHeight);
|
||||
syncingStatus =
|
||||
SyncingSyncStatus.fromHeightValues(scanData.chainTip, initialSyncHeight, syncHeight);
|
||||
}
|
||||
|
||||
scanData.sendPort.send(SyncResponse(syncHeight, syncingStatus));
|
||||
|
||||
if (syncingStatus.blocksLeft <= 0 || (scanData.isSingleScan && scanData.height != syncHeight)) {
|
||||
scanData.sendPort.send(SyncResponse(await getUpdatedNodeHeight(), SyncedSyncStatus()));
|
||||
scanData.sendPort.send(SyncResponse(scanData.chainTip, SyncedSyncStatus()));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final electrumClient = await getElectrumConnection();
|
||||
|
||||
// TODO: hardcoded values, if timed out decrease amount of blocks per request?
|
||||
final scanningBlockCount =
|
||||
scanData.isSingleScan ? 1 : (scanData.network == BitcoinNetwork.testnet ? 25 : 10);
|
||||
|
||||
Map<String, dynamic>? tweaks;
|
||||
if (tweaksSubscription == null) {
|
||||
try {
|
||||
tweaks = await electrumClient.getTweaks(height: syncHeight, count: scanningBlockCount);
|
||||
} catch (e) {
|
||||
if (e is RequestFailedTimeoutException) {
|
||||
return scanData.sendPort.send(
|
||||
SyncResponse(syncHeight, TimedOutSyncStatus()),
|
||||
);
|
||||
}
|
||||
}
|
||||
tweaksSubscription = await electrumClient.tweaksSubscribe(height: syncHeight);
|
||||
|
||||
if (tweaks == null) {
|
||||
return scanData.sendPort.send(
|
||||
SyncResponse(syncHeight, UnsupportedSyncStatus()),
|
||||
);
|
||||
}
|
||||
tweaksSubscription?.listen((t) {
|
||||
final tweaks = t as Map<String, dynamic>;
|
||||
|
||||
if (tweaks.isEmpty) {
|
||||
syncHeight += scanningBlockCount;
|
||||
continue;
|
||||
syncHeight += 1;
|
||||
scanData.sendPort.send(
|
||||
SyncResponse(
|
||||
syncHeight,
|
||||
SyncingSyncStatus.fromHeightValues(
|
||||
currentChainTip,
|
||||
initialSyncHeight,
|
||||
syncHeight,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
final blockHeights = tweaks.keys;
|
||||
for (var i = 0; i < blockHeights.length; i++) {
|
||||
final blockHeight = tweaks.keys.first.toString();
|
||||
|
||||
try {
|
||||
final blockHeight = blockHeights.elementAt(i).toString();
|
||||
final blockTweaks = tweaks[blockHeight] as Map<String, dynamic>;
|
||||
|
||||
for (var j = 0; j < blockTweaks.keys.length; j++) {
|
||||
|
@ -1777,6 +1881,7 @@ Future<void> startRefresh(ScanData scanData) async {
|
|||
final tweak = details["tweak"].toString();
|
||||
|
||||
try {
|
||||
// scanOutputs called from rust here
|
||||
final addToWallet = scanOutputs(
|
||||
outputPubkeys.values.map((o) => o[0].toString()).toList(),
|
||||
tweak,
|
||||
|
@ -1794,8 +1899,22 @@ Future<void> startRefresh(ScanData scanData) async {
|
|||
continue;
|
||||
}
|
||||
|
||||
addToWallet.forEach((label, value) async {
|
||||
(value as Map<String, dynamic>).forEach((output, tweak) async {
|
||||
// placeholder ElectrumTransactionInfo object to update values based on new scanned unspent(s)
|
||||
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 receivingOutputAddress = ECPublic.fromHex(output)
|
||||
|
@ -1816,8 +1935,8 @@ Future<void> startRefresh(ScanData scanData) async {
|
|||
int? amount;
|
||||
int? pos;
|
||||
outputPubkeys.entries.firstWhere((k) {
|
||||
final matches = k.value[0] == output;
|
||||
if (matches) {
|
||||
final isMatchingOutput = k.value[0] == output;
|
||||
if (isMatchingOutput) {
|
||||
amount = int.parse(k.value[1].toString());
|
||||
pos = int.parse(k.key.toString());
|
||||
return true;
|
||||
|
@ -1825,61 +1944,62 @@ Future<void> startRefresh(ScanData scanData) async {
|
|||
return false;
|
||||
});
|
||||
|
||||
final json = <String, dynamic>{
|
||||
'address_record': receivedAddressRecord.toJSON(),
|
||||
'tx_hash': txid,
|
||||
'value': amount!,
|
||||
'tx_pos': pos!,
|
||||
'silent_payment_tweak': t_k,
|
||||
};
|
||||
|
||||
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 unspent = BitcoinSilentPaymentsUnspent(
|
||||
receivedAddressRecord,
|
||||
txid,
|
||||
amount!,
|
||||
pos!,
|
||||
silentPaymentTweak: t_k,
|
||||
silentPaymentLabel: label == "None" ? null : label,
|
||||
);
|
||||
|
||||
final txInfo = ElectrumTransactionInfo(
|
||||
WalletType.bitcoin,
|
||||
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],
|
||||
);
|
||||
txInfo.unspents!.add(unspent);
|
||||
txInfo.amount += unspent.value;
|
||||
});
|
||||
});
|
||||
|
||||
scanData.sendPort.send({txInfo.id: txInfo});
|
||||
});
|
||||
});
|
||||
} catch (_) {}
|
||||
}
|
||||
} catch (e, s) {
|
||||
print([e, s]);
|
||||
} catch (_) {}
|
||||
|
||||
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
|
||||
syncHeight += 1;
|
||||
scanData.sendPort.send(SyncResponse(syncHeight,
|
||||
SyncingSyncStatus.fromHeightValues(currentChainTip, initialSyncHeight, syncHeight)));
|
||||
return;
|
||||
});
|
||||
} catch (e) {
|
||||
if (e is RequestFailedTimeoutException) {
|
||||
return scanData.sendPort.send(
|
||||
SyncResponse(syncHeight, TimedOutSyncStatus()),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tweaksSubscription == null) {
|
||||
return scanData.sendPort.send(
|
||||
SyncResponse(syncHeight, UnsupportedSyncStatus()),
|
||||
);
|
||||
}
|
||||
} catch (e, stacktrace) {
|
||||
print(stacktrace);
|
||||
print(e.toString());
|
||||
|
||||
scanData.sendPort.send(SyncResponse(syncHeight, NotConnectedSyncStatus()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
b_spend: ECPrivate.fromHex(masterHd.derivePath(SPEND_PATH).privKey!),
|
||||
hrp: network == BitcoinNetwork.testnet ? 'tsp' : 'sp');
|
||||
|
||||
if (silentAddresses.length == 0)
|
||||
if (silentAddresses.length == 0) {
|
||||
silentAddresses.add(BitcoinSilentPaymentAddressRecord(
|
||||
silentAddress.toString(),
|
||||
index: 0,
|
||||
|
@ -67,6 +67,16 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
network: network,
|
||||
type: SilentPaymentsAddresType.p2sp,
|
||||
));
|
||||
silentAddresses.add(BitcoinSilentPaymentAddressRecord(
|
||||
silentAddress!.toLabeledSilentPaymentAddress(0).toString(),
|
||||
index: 0,
|
||||
isHidden: true,
|
||||
name: "",
|
||||
silentPaymentTweak: BytesUtils.toHexString(silentAddress!.generateLabel(0)),
|
||||
network: network,
|
||||
type: SilentPaymentsAddresType.p2sp,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
updateAddressesByMatch();
|
||||
|
@ -446,7 +456,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
|
||||
@action
|
||||
Future<void> discoverAddresses(List<BitcoinAddressRecord> addressList, bool isHidden,
|
||||
Future<String?> Function(BitcoinAddressRecord, Set<String>) getAddressHistory,
|
||||
Future<String?> Function(BitcoinAddressRecord) getAddressHistory,
|
||||
{BitcoinAddressType type = SegwitAddresType.p2wpkh}) async {
|
||||
if (!isHidden) {
|
||||
_validateSideHdAddresses(addressList.toList());
|
||||
|
@ -456,8 +466,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store {
|
|||
startIndex: addressList.length, isHidden: isHidden, type: type);
|
||||
addAddresses(newAddresses);
|
||||
|
||||
final addressesWithHistory = await Future.wait(newAddresses
|
||||
.map((addr) => getAddressHistory(addr, _addresses.map((e) => e.address).toSet())));
|
||||
final addressesWithHistory = await Future.wait(newAddresses.map(getAddressHistory));
|
||||
final isLastAddressUsed = addressesWithHistory.last == addressList.last.address;
|
||||
|
||||
if (isLastAddressUsed) {
|
||||
|
|
|
@ -2,7 +2,7 @@ import 'package:cw_core/crypto_currency.dart';
|
|||
import 'package:cw_core/exceptions.dart';
|
||||
|
||||
class BitcoinTransactionWrongBalanceException extends TransactionWrongBalanceException {
|
||||
BitcoinTransactionWrongBalanceException() : super(CryptoCurrency.btc);
|
||||
BitcoinTransactionWrongBalanceException({super.amount}) : super(CryptoCurrency.btc);
|
||||
}
|
||||
|
||||
class BitcoinTransactionNoInputsException extends TransactionNoInputsException {}
|
||||
|
@ -25,3 +25,7 @@ class BitcoinTransactionCommitFailedDustOutputSendAll
|
|||
extends TransactionCommitFailedDustOutputSendAll {}
|
||||
|
||||
class BitcoinTransactionCommitFailedVoutNegative extends TransactionCommitFailedVoutNegative {}
|
||||
|
||||
class BitcoinTransactionCommitFailedBIP68Final extends TransactionCommitFailedBIP68Final {}
|
||||
|
||||
class BitcoinTransactionSilentPaymentsNotSupported extends TransactionInputNotSupported {}
|
||||
|
|
|
@ -73,6 +73,10 @@ class PendingBitcoinTransaction with PendingTransaction {
|
|||
if (error.contains("bad-txns-vout-negative")) {
|
||||
throw BitcoinTransactionCommitFailedVoutNegative();
|
||||
}
|
||||
|
||||
if (error.contains("non-BIP68-final")) {
|
||||
throw BitcoinTransactionCommitFailedBIP68Final();
|
||||
}
|
||||
}
|
||||
throw BitcoinTransactionCommitFailed();
|
||||
}
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import 'package:cw_core/crypto_currency.dart';
|
||||
|
||||
class TransactionWrongBalanceException implements Exception {
|
||||
TransactionWrongBalanceException(this.currency);
|
||||
TransactionWrongBalanceException(this.currency, {this.amount});
|
||||
|
||||
final CryptoCurrency currency;
|
||||
final int? amount;
|
||||
}
|
||||
|
||||
class TransactionNoInputsException implements Exception {}
|
||||
|
@ -28,3 +29,7 @@ class TransactionCommitFailedDustOutput implements Exception {}
|
|||
class TransactionCommitFailedDustOutputSendAll implements Exception {}
|
||||
|
||||
class TransactionCommitFailedVoutNegative implements Exception {}
|
||||
|
||||
class TransactionCommitFailedBIP68Final implements Exception {}
|
||||
|
||||
class TransactionInputNotSupported implements Exception {}
|
||||
|
|
|
@ -31,6 +31,11 @@ class SyncedSyncStatus extends SyncStatus {
|
|||
double progress() => 1.0;
|
||||
}
|
||||
|
||||
class SyncronizingSyncStatus extends SyncStatus {
|
||||
@override
|
||||
double progress() => 0.0;
|
||||
}
|
||||
|
||||
class NotConnectedSyncStatus extends SyncStatus {
|
||||
const NotConnectedSyncStatus();
|
||||
|
||||
|
@ -43,10 +48,7 @@ class AttemptingSyncStatus extends SyncStatus {
|
|||
double progress() => 0.0;
|
||||
}
|
||||
|
||||
class FailedSyncStatus extends SyncStatus {
|
||||
@override
|
||||
double progress() => 1.0;
|
||||
}
|
||||
class FailedSyncStatus extends NotConnectedSyncStatus {}
|
||||
|
||||
class ConnectingSyncStatus extends SyncStatus {
|
||||
@override
|
||||
|
@ -58,21 +60,14 @@ class ConnectedSyncStatus extends SyncStatus {
|
|||
double progress() => 0.0;
|
||||
}
|
||||
|
||||
class UnsupportedSyncStatus extends SyncStatus {
|
||||
@override
|
||||
double progress() => 1.0;
|
||||
}
|
||||
class UnsupportedSyncStatus extends NotConnectedSyncStatus {}
|
||||
|
||||
class TimedOutSyncStatus extends SyncStatus {
|
||||
@override
|
||||
double progress() => 1.0;
|
||||
class TimedOutSyncStatus extends NotConnectedSyncStatus {
|
||||
@override
|
||||
String toString() => 'Timed out';
|
||||
}
|
||||
|
||||
class LostConnectionSyncStatus extends SyncStatus {
|
||||
@override
|
||||
double progress() => 1.0;
|
||||
class LostConnectionSyncStatus extends NotConnectedSyncStatus {
|
||||
@override
|
||||
String toString() => 'Reconnecting';
|
||||
}
|
||||
|
|
|
@ -16,7 +16,8 @@ class UnspentCoinsInfo extends HiveObject {
|
|||
required this.value,
|
||||
this.keyImage = null,
|
||||
this.isChange = false,
|
||||
this.accountIndex = 0
|
||||
this.accountIndex = 0,
|
||||
this.isSilentPayment = false,
|
||||
});
|
||||
|
||||
static const typeId = UNSPENT_COINS_INFO_TYPE_ID;
|
||||
|
@ -56,6 +57,9 @@ class UnspentCoinsInfo extends HiveObject {
|
|||
@HiveField(10, defaultValue: 0)
|
||||
int accountIndex;
|
||||
|
||||
@HiveField(11, defaultValue: false)
|
||||
bool? isSilentPayment;
|
||||
|
||||
String get note => noteRaw ?? '';
|
||||
|
||||
set note(String value) => noteRaw = value;
|
||||
|
|
|
@ -187,7 +187,7 @@ class CWBitcoin extends Bitcoin {
|
|||
|
||||
Future<void> updateUnspents(Object wallet) async {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
await bitcoinWallet.updateUnspent();
|
||||
await bitcoinWallet.updateAllUnspents();
|
||||
}
|
||||
|
||||
WalletService createBitcoinWalletService(
|
||||
|
@ -386,4 +386,10 @@ class CWBitcoin extends Bitcoin {
|
|||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
bitcoinWallet.walletAddresses.deleteSilentPaymentAddress(address);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> updateFeeRates(Object wallet) async {
|
||||
final bitcoinWallet = wallet as ElectrumWallet;
|
||||
await bitcoinWallet.updateFeeRates();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,5 +44,9 @@ String syncStatusTitle(SyncStatus syncStatus) {
|
|||
return S.current.sync_status_timed_out;
|
||||
}
|
||||
|
||||
if (syncStatus is SyncronizingSyncStatus) {
|
||||
return S.current.sync_status_syncronizing;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
|
|
@ -818,14 +818,16 @@ Future<void> checkCurrentNodes(
|
|||
}
|
||||
|
||||
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 sharedPreferences.setInt(
|
||||
PreferencesKey.currentBitcoinElectrumSererIdKey, cakeWalletElectrum.key as int);
|
||||
}
|
||||
|
||||
if (currentLitecoinElectrumServer == null) {
|
||||
final cakeWalletElectrum = Node(uri: cakeWalletLitecoinElectrumUri, type: WalletType.litecoin);
|
||||
final cakeWalletElectrum =
|
||||
Node(uri: cakeWalletLitecoinElectrumUri, type: WalletType.litecoin, useSSL: true);
|
||||
await nodeSource.add(cakeWalletElectrum);
|
||||
await sharedPreferences.setInt(
|
||||
PreferencesKey.currentLitecoinElectrumSererIdKey, cakeWalletElectrum.key as int);
|
||||
|
@ -860,7 +862,8 @@ Future<void> checkCurrentNodes(
|
|||
}
|
||||
|
||||
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 sharedPreferences.setInt(PreferencesKey.currentBitcoinCashNodeIdKey, node.key as int);
|
||||
}
|
||||
|
@ -888,7 +891,8 @@ Future<void> resetBitcoinElectrumServer(
|
|||
.firstWhereOrNull((node) => node.uriRaw.toString() == cakeWalletBitcoinElectrumUri);
|
||||
|
||||
if (cakeWalletNode == null) {
|
||||
cakeWalletNode = Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin);
|
||||
cakeWalletNode =
|
||||
Node(uri: cakeWalletBitcoinElectrumUri, type: WalletType.bitcoin, useSSL: true);
|
||||
await nodeSource.add(cakeWalletNode);
|
||||
}
|
||||
|
||||
|
|
|
@ -146,7 +146,7 @@ class AddressCell extends StatelessWidget {
|
|||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
Text(
|
||||
'${S.of(context).balance}: $txCount',
|
||||
'${S.of(context).balance}: $balance',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/core/auth_service.dart';
|
||||
import 'package:cake_wallet/entities/contact_record.dart';
|
||||
import 'package:cake_wallet/entities/fiat_currency.dart';
|
||||
|
@ -400,6 +401,10 @@ class SendPage extends BasePage {
|
|||
return;
|
||||
}
|
||||
|
||||
if (sendViewModel.isElectrumWallet) {
|
||||
bitcoin!.updateFeeRates(sendViewModel.wallet);
|
||||
}
|
||||
|
||||
reaction((_) => sendViewModel.state, (ExecutionState state) {
|
||||
if (state is FailureState) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
|
@ -456,10 +461,12 @@ class SendPage extends BasePage {
|
|||
sendViewModel.selectedCryptoCurrency.toString());
|
||||
|
||||
final waitMessage = sendViewModel.walletType == WalletType.solana
|
||||
? '. ${S.of(_dialogContext).waitFewSecondForTxUpdate}' : '';
|
||||
? '. ${S.of(_dialogContext).waitFewSecondForTxUpdate}'
|
||||
: '';
|
||||
|
||||
final newContactMessage = newContactAddress != null
|
||||
? '\n${S.of(_dialogContext).add_contact_to_address_book}' : '';
|
||||
? '\n${S.of(_dialogContext).add_contact_to_address_book}'
|
||||
: '';
|
||||
|
||||
final alertContent =
|
||||
"$successMessage$waitMessage$newContactMessage";
|
||||
|
|
|
@ -57,6 +57,7 @@ class UnspentCoinsListFormState extends State<UnspentCoinsListForm> {
|
|||
isSending: item.isSending,
|
||||
isFrozen: item.isFrozen,
|
||||
isChange: item.isChange,
|
||||
isSilentPayment: item.isSilentPayment,
|
||||
onCheckBoxTap: item.isFrozen
|
||||
? null
|
||||
: () async {
|
||||
|
|
|
@ -12,6 +12,7 @@ class UnspentCoinsListItem extends StatelessWidget {
|
|||
required this.isSending,
|
||||
required this.isFrozen,
|
||||
required this.isChange,
|
||||
required this.isSilentPayment,
|
||||
this.onCheckBoxTap,
|
||||
});
|
||||
|
||||
|
@ -21,18 +22,16 @@ class UnspentCoinsListItem extends StatelessWidget {
|
|||
final bool isSending;
|
||||
final bool isFrozen;
|
||||
final bool isChange;
|
||||
final bool isSilentPayment;
|
||||
final Function()? onCheckBoxTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final unselectedItemColor = Theme.of(context).cardColor;
|
||||
final selectedItemColor = Theme.of(context).primaryColor;
|
||||
final itemColor = isSending
|
||||
? selectedItemColor
|
||||
: unselectedItemColor;
|
||||
final amountColor = isSending
|
||||
? Colors.white
|
||||
: Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor;
|
||||
final itemColor = isSending ? selectedItemColor : unselectedItemColor;
|
||||
final amountColor =
|
||||
isSending ? Colors.white : Theme.of(context).extension<CakeTextTheme>()!.buttonTextColor;
|
||||
final addressColor = isSending
|
||||
? Colors.white.withOpacity(0.5)
|
||||
: Theme.of(context).extension<CakeTextTheme>()!.buttonSecondaryTextColor;
|
||||
|
@ -121,6 +120,23 @@ class UnspentCoinsListItem extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
),
|
||||
if (isSilentPayment)
|
||||
Container(
|
||||
height: 17,
|
||||
padding: EdgeInsets.only(left: 6, right: 6),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(8.5)),
|
||||
color: Colors.white),
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
S.of(context).silent_payments,
|
||||
style: TextStyle(
|
||||
color: itemColor,
|
||||
fontSize: 7,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -567,9 +567,15 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
|||
if (error is TransactionCommitFailedVoutNegative) {
|
||||
return S.current.tx_rejected_vout_negative;
|
||||
}
|
||||
if (error is TransactionCommitFailedBIP68Final) {
|
||||
return S.current.tx_rejected_bip68_final;
|
||||
}
|
||||
if (error is TransactionNoDustOnChangeException) {
|
||||
return S.current.tx_commit_exception_no_dust_on_change(error.min, error.max);
|
||||
}
|
||||
if (error is TransactionInputNotSupported) {
|
||||
return S.current.tx_invalid_input;
|
||||
}
|
||||
}
|
||||
|
||||
return errorMessage;
|
||||
|
|
|
@ -15,7 +15,8 @@ abstract class UnspentCoinsItemBase with Store {
|
|||
required this.isChange,
|
||||
required this.amountRaw,
|
||||
required this.vout,
|
||||
required this.keyImage
|
||||
required this.keyImage,
|
||||
required this.isSilentPayment,
|
||||
});
|
||||
|
||||
@observable
|
||||
|
@ -47,4 +48,7 @@ abstract class UnspentCoinsItemBase with Store {
|
|||
|
||||
@observable
|
||||
String? keyImage;
|
||||
|
||||
@observable
|
||||
bool isSilentPayment;
|
||||
}
|
||||
|
|
|
@ -86,11 +86,14 @@ abstract class UnspentCoinsListViewModelBase with Store {
|
|||
@action
|
||||
void _updateUnspentCoinsInfo() {
|
||||
_items.clear();
|
||||
_items.addAll(_getUnspents().map((elem) {
|
||||
|
||||
List<UnspentCoinsItem> unspents = [];
|
||||
_getUnspents().forEach((elem) {
|
||||
try {
|
||||
final info =
|
||||
getUnspentCoinInfo(elem.hash, elem.address, elem.value, elem.vout, elem.keyImage);
|
||||
|
||||
return UnspentCoinsItem(
|
||||
unspents.add(UnspentCoinsItem(
|
||||
address: elem.address,
|
||||
amount: '${formatAmountToString(elem.value)} ${wallet.currency.title}',
|
||||
hash: elem.hash,
|
||||
|
@ -101,7 +104,14 @@ abstract class UnspentCoinsListViewModelBase with Store {
|
|||
vout: elem.vout,
|
||||
keyImage: elem.keyImage,
|
||||
isChange: elem.isChange,
|
||||
);
|
||||
}));
|
||||
isSilentPayment: info.isSilentPayment ?? false,
|
||||
));
|
||||
} catch (e, s) {
|
||||
print(s);
|
||||
print(e.toString());
|
||||
}
|
||||
});
|
||||
|
||||
_items.addAll(unspents);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -316,8 +316,7 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
|||
if (isElectrumWallet) {
|
||||
if (bitcoin!.hasSelectedSilentPayments(wallet)) {
|
||||
final addressItems = bitcoin!.getSilentPaymentAddresses(wallet).map((address) {
|
||||
// Silent Payments index 0 is change per BIP
|
||||
final isPrimary = address.index == 1;
|
||||
final isPrimary = address.index == 0;
|
||||
|
||||
return WalletAddressListItem(
|
||||
id: address.index,
|
||||
|
@ -335,11 +334,9 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
|||
|
||||
final receivedAddressItems =
|
||||
bitcoin!.getSilentPaymentReceivedAddresses(wallet).map((address) {
|
||||
final isPrimary = address.index == 0;
|
||||
|
||||
return WalletAddressListItem(
|
||||
id: address.index,
|
||||
isPrimary: isPrimary,
|
||||
isPrimary: false,
|
||||
name: address.name,
|
||||
address: address.address,
|
||||
txCount: address.txCount,
|
||||
|
|
|
@ -745,8 +745,10 @@
|
|||
"trusted": "موثوق به",
|
||||
"tx_commit_exception_no_dust_on_change": "يتم رفض المعاملة مع هذا المبلغ. باستخدام هذه العملات المعدنية ، يمكنك إرسال ${min} دون تغيير أو ${max} الذي يعيد التغيير.",
|
||||
"tx_commit_failed": "فشل ارتكاب المعاملة. يرجى الاتصال بالدعم.",
|
||||
"tx_invalid_input": "أنت تستخدم نوع الإدخال الخاطئ لهذا النوع من الدفع",
|
||||
"tx_no_dust_exception": "يتم رفض المعاملة عن طريق إرسال مبلغ صغير جدًا. يرجى محاولة زيادة المبلغ.",
|
||||
"tx_not_enough_inputs_exception": "لا يكفي المدخلات المتاحة. الرجاء تحديد المزيد تحت التحكم في العملة",
|
||||
"tx_rejected_bip68_final": "تحتوي المعاملة على مدخلات غير مؤكدة وفشلت في استبدال الرسوم.",
|
||||
"tx_rejected_dust_change": "المعاملة التي يتم رفضها بموجب قواعد الشبكة ، ومبلغ التغيير المنخفض (الغبار). حاول إرسال كل أو تقليل المبلغ.",
|
||||
"tx_rejected_dust_output": "المعاملة التي يتم رفضها بموجب قواعد الشبكة ، وكمية الإخراج المنخفض (الغبار). يرجى زيادة المبلغ.",
|
||||
"tx_rejected_dust_output_send_all": "المعاملة التي يتم رفضها بموجب قواعد الشبكة ، وكمية الإخراج المنخفض (الغبار). يرجى التحقق من رصيد العملات المعدنية المحددة تحت التحكم في العملة.",
|
||||
|
|
|
@ -745,8 +745,10 @@
|
|||
"trusted": "Надежден",
|
||||
"tx_commit_exception_no_dust_on_change": "Сделката се отхвърля с тази сума. С тези монети можете да изпратите ${min} без промяна или ${max}, която връща промяна.",
|
||||
"tx_commit_failed": "Компетацията на транзакцията не успя. Моля, свържете се с поддръжката.",
|
||||
"tx_invalid_input": "Използвате грешен тип вход за този тип плащане",
|
||||
"tx_no_dust_exception": "Сделката се отхвърля чрез изпращане на сума твърде малка. Моля, опитайте да увеличите сумата.",
|
||||
"tx_not_enough_inputs_exception": "Няма достатъчно налични входове. Моля, изберете повече под контрол на монети",
|
||||
"tx_rejected_bip68_final": "Сделката има непотвърдени входове и не успя да се замени с такса.",
|
||||
"tx_rejected_dust_change": "Транзакция, отхвърлена от мрежови правила, ниска сума на промяна (прах). Опитайте да изпратите всички или да намалите сумата.",
|
||||
"tx_rejected_dust_output": "Транзакция, отхвърлена от мрежови правила, ниска стойност на изхода (прах). Моля, увеличете сумата.",
|
||||
"tx_rejected_dust_output_send_all": "Транзакция, отхвърлена от мрежови правила, ниска стойност на изхода (прах). Моля, проверете баланса на монетите, избрани под контрол на монети.",
|
||||
|
|
|
@ -745,8 +745,10 @@
|
|||
"trusted": "Důvěřovat",
|
||||
"tx_commit_exception_no_dust_on_change": "Transakce je zamítnuta s touto částkou. S těmito mincemi můžete odeslat ${min} bez změny nebo ${max}, které se vrátí změna.",
|
||||
"tx_commit_failed": "Transakce COMPORT selhala. Kontaktujte prosím podporu.",
|
||||
"tx_invalid_input": "Pro tento typ platby používáte nesprávný typ vstupu",
|
||||
"tx_no_dust_exception": "Transakce je zamítnuta odesláním příliš malé. Zkuste prosím zvýšit částku.",
|
||||
"tx_not_enough_inputs_exception": "Není k dispozici dostatek vstupů. Vyberte prosím více pod kontrolou mincí",
|
||||
"tx_rejected_bip68_final": "Transakce má nepotvrzené vstupy a nepodařilo se nahradit poplatkem.",
|
||||
"tx_rejected_dust_change": "Transakce zamítnuta podle síťových pravidel, množství nízké změny (prach). Zkuste odeslat vše nebo snížit částku.",
|
||||
"tx_rejected_dust_output": "Transakce zamítnuta síťovými pravidly, nízkým množstvím výstupu (prach). Zvyšte prosím částku.",
|
||||
"tx_rejected_dust_output_send_all": "Transakce zamítnuta síťovými pravidly, nízkým množstvím výstupu (prach). Zkontrolujte prosím zůstatek mincí vybraných pod kontrolou mincí.",
|
||||
|
|
|
@ -746,8 +746,10 @@
|
|||
"trusted": "Vertrauenswürdige",
|
||||
"tx_commit_exception_no_dust_on_change": "Die Transaktion wird diesen Betrag abgelehnt. Mit diesen Münzen können Sie ${min} ohne Veränderung oder ${max} senden, die Änderungen zurückgeben.",
|
||||
"tx_commit_failed": "Transaktionsausschüsse ist fehlgeschlagen. Bitte wenden Sie sich an Support.",
|
||||
"tx_invalid_input": "Sie verwenden den falschen Eingangstyp für diese Art von Zahlung",
|
||||
"tx_no_dust_exception": "Die Transaktion wird abgelehnt, indem eine Menge zu klein gesendet wird. Bitte versuchen Sie, die Menge zu erhöhen.",
|
||||
"tx_not_enough_inputs_exception": "Nicht genügend Eingänge verfügbar. Bitte wählen Sie mehr unter Münzkontrolle aus",
|
||||
"tx_rejected_bip68_final": "Die Transaktion hat unbestätigte Inputs und konnte nicht durch Gebühr ersetzt werden.",
|
||||
"tx_rejected_dust_change": "Transaktion abgelehnt durch Netzwerkregeln, niedriger Änderungsbetrag (Staub). Versuchen Sie, alle zu senden oder die Menge zu reduzieren.",
|
||||
"tx_rejected_dust_output": "Transaktion durch Netzwerkregeln, niedriger Ausgangsmenge (Staub) abgelehnt. Bitte erhöhen Sie den Betrag.",
|
||||
"tx_rejected_dust_output_send_all": "Transaktion durch Netzwerkregeln, niedriger Ausgangsmenge (Staub) abgelehnt. Bitte überprüfen Sie den Gleichgewicht der unter Münzkontrolle ausgewählten Münzen.",
|
||||
|
|
|
@ -745,8 +745,10 @@
|
|||
"trusted": "Trusted",
|
||||
"tx_commit_exception_no_dust_on_change": "The transaction is rejected with this amount. With these coins you can send ${min} without change or ${max} that returns change.",
|
||||
"tx_commit_failed": "Transaction commit failed. Please contact support.",
|
||||
"tx_invalid_input": "You are using the wrong input type for this type of payment",
|
||||
"tx_no_dust_exception": "The transaction is rejected by sending an amount too small. Please try increasing the amount.",
|
||||
"tx_not_enough_inputs_exception": "Not enough inputs available. Please select more under Coin Control",
|
||||
"tx_rejected_bip68_final": "Transaction has unconfirmed inputs and failed to replace by fee.",
|
||||
"tx_rejected_dust_change": "Transaction rejected by network rules, low change amount (dust). Try sending ALL or reducing the amount.",
|
||||
"tx_rejected_dust_output": "Transaction rejected by network rules, low output amount (dust). Please increase the amount.",
|
||||
"tx_rejected_dust_output_send_all": "Transaction rejected by network rules, low output amount (dust). Please check the balance of coins selected under Coin Control.",
|
||||
|
|
|
@ -746,8 +746,10 @@
|
|||
"trusted": "de confianza",
|
||||
"tx_commit_exception_no_dust_on_change": "La transacción se rechaza con esta cantidad. Con estas monedas puede enviar ${min} sin cambios o ${max} que devuelve el cambio.",
|
||||
"tx_commit_failed": "La confirmación de transacción falló. Póngase en contacto con el soporte.",
|
||||
"tx_invalid_input": "Está utilizando el tipo de entrada incorrecto para este tipo de pago",
|
||||
"tx_no_dust_exception": "La transacción se rechaza enviando una cantidad demasiado pequeña. Intente aumentar la cantidad.",
|
||||
"tx_not_enough_inputs_exception": "No hay suficientes entradas disponibles. Seleccione más bajo control de monedas",
|
||||
"tx_rejected_bip68_final": "La transacción tiene entradas no confirmadas y no ha podido reemplazar por tarifa.",
|
||||
"tx_rejected_dust_change": "Transacción rechazada por reglas de red, bajo cambio de cambio (polvo). Intente enviar todo o reducir la cantidad.",
|
||||
"tx_rejected_dust_output": "Transacción rechazada por reglas de red, baja cantidad de salida (polvo). Aumente la cantidad.",
|
||||
"tx_rejected_dust_output_send_all": "Transacción rechazada por reglas de red, baja cantidad de salida (polvo). Verifique el saldo de monedas seleccionadas bajo control de monedas.",
|
||||
|
|
|
@ -745,8 +745,10 @@
|
|||
"trusted": "de confiance",
|
||||
"tx_commit_exception_no_dust_on_change": "La transaction est rejetée avec ce montant. Avec ces pièces, vous pouvez envoyer ${min} sans changement ou ${max} qui renvoie le changement.",
|
||||
"tx_commit_failed": "La validation de la transaction a échoué. Veuillez contacter l'assistance.",
|
||||
"tx_invalid_input": "Vous utilisez le mauvais type d'entrée pour ce type de paiement",
|
||||
"tx_no_dust_exception": "La transaction est rejetée en envoyant un montant trop faible. Veuillez essayer d'augmenter le montant.",
|
||||
"tx_not_enough_inputs_exception": "Pas assez d'entrées disponibles. Veuillez sélectionner plus sous Control Control",
|
||||
"tx_rejected_bip68_final": "La transaction a des entrées non confirmées et n'a pas réussi à remplacer par les frais.",
|
||||
"tx_rejected_dust_change": "Transaction rejetée par les règles du réseau, montant de faible variation (poussière). Essayez d'envoyer tout ou de réduire le montant.",
|
||||
"tx_rejected_dust_output": "Transaction rejetée par les règles du réseau, faible quantité de sortie (poussière). Veuillez augmenter le montant.",
|
||||
"tx_rejected_dust_output_send_all": "Transaction rejetée par les règles du réseau, faible quantité de sortie (poussière). Veuillez vérifier le solde des pièces sélectionnées sous le contrôle des pièces de monnaie.",
|
||||
|
|
|
@ -747,8 +747,10 @@
|
|||
"trusted": "Amintacce",
|
||||
"tx_commit_exception_no_dust_on_change": "An ƙi ma'amala da wannan adadin. Tare da waɗannan tsabar kudi Zaka iya aika ${min}, ba tare da canji ba ko ${max} wanda ya dawo canzawa.",
|
||||
"tx_commit_failed": "Ma'amala ya kasa. Da fatan za a tuntuɓi goyan baya.",
|
||||
"tx_invalid_input": "Kuna amfani da nau'in shigar da ba daidai ba don wannan nau'in biyan kuɗi",
|
||||
"tx_no_dust_exception": "An ƙi ma'amala ta hanyar aika adadin ƙarami. Da fatan za a gwada ƙara adadin.",
|
||||
"tx_not_enough_inputs_exception": "Bai isa ba hanyoyin da ake samu. Da fatan za selectiari a karkashin Kwarewar Coin",
|
||||
"tx_rejected_bip68_final": "Ma'amala tana da abubuwan da basu dace ba kuma sun kasa maye gurbin ta.",
|
||||
"tx_rejected_dust_change": "Ma'amala ta ƙi ta dokokin cibiyar sadarwa, ƙarancin canji (ƙura). Gwada aikawa da duka ko rage adadin.",
|
||||
"tx_rejected_dust_output": "Ma'adar da aka ƙi ta dokokin cibiyar sadarwa, ƙananan fitarwa (ƙura). Da fatan za a ƙara adadin.",
|
||||
"tx_rejected_dust_output_send_all": "Ma'adar da aka ƙi ta dokokin cibiyar sadarwa, ƙananan fitarwa (ƙura). Da fatan za a duba daidaiton tsabar kudi a ƙarƙashin ikon tsabar kudin.",
|
||||
|
|
|
@ -747,8 +747,10 @@
|
|||
"trusted": "भरोसा",
|
||||
"tx_commit_exception_no_dust_on_change": "लेनदेन को इस राशि से खारिज कर दिया जाता है। इन सिक्कों के साथ आप चेंज या ${min} के बिना ${max} को भेज सकते हैं जो परिवर्तन लौटाता है।",
|
||||
"tx_commit_failed": "लेन -देन प्रतिबद्ध विफल। कृपया संपर्क समर्थन करें।",
|
||||
"tx_invalid_input": "आप इस प्रकार के भुगतान के लिए गलत इनपुट प्रकार का उपयोग कर रहे हैं",
|
||||
"tx_no_dust_exception": "लेनदेन को बहुत छोटी राशि भेजकर अस्वीकार कर दिया जाता है। कृपया राशि बढ़ाने का प्रयास करें।",
|
||||
"tx_not_enough_inputs_exception": "पर्याप्त इनपुट उपलब्ध नहीं है। कृपया सिक्का नियंत्रण के तहत अधिक चुनें",
|
||||
"tx_rejected_bip68_final": "लेन -देन में अपुष्ट इनपुट हैं और शुल्क द्वारा प्रतिस्थापित करने में विफल रहे हैं।",
|
||||
"tx_rejected_dust_change": "नेटवर्क नियमों, कम परिवर्तन राशि (धूल) द्वारा खारिज किए गए लेनदेन। सभी भेजने या राशि को कम करने का प्रयास करें।",
|
||||
"tx_rejected_dust_output": "नेटवर्क नियमों, कम आउटपुट राशि (धूल) द्वारा खारिज किए गए लेनदेन। कृपया राशि बढ़ाएं।",
|
||||
"tx_rejected_dust_output_send_all": "नेटवर्क नियमों, कम आउटपुट राशि (धूल) द्वारा खारिज किए गए लेनदेन। कृपया सिक्का नियंत्रण के तहत चुने गए सिक्कों के संतुलन की जाँच करें।",
|
||||
|
|
|
@ -745,8 +745,10 @@
|
|||
"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_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_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_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.",
|
||||
|
|
|
@ -748,8 +748,10 @@
|
|||
"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_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_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_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.",
|
||||
|
|
|
@ -747,8 +747,10 @@
|
|||
"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_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_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_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.",
|
||||
|
|
|
@ -746,8 +746,10 @@
|
|||
"trusted": "信頼できる",
|
||||
"tx_commit_exception_no_dust_on_change": "この金額ではトランザクションは拒否されます。 これらのコインを使用すると、おつりなしの ${min} またはおつりを返す ${max} を送信できます。",
|
||||
"tx_commit_failed": "トランザクションコミットは失敗しました。サポートに連絡してください。",
|
||||
"tx_invalid_input": "このタイプの支払いに間違った入力タイプを使用しています",
|
||||
"tx_no_dust_exception": "トランザクションは、小さすぎる金額を送信することにより拒否されます。量を増やしてみてください。",
|
||||
"tx_not_enough_inputs_exception": "利用可能な入力が十分ではありません。コイン制御下でもっと選択してください",
|
||||
"tx_rejected_bip68_final": "トランザクションには未確認の入力があり、料金で交換できませんでした。",
|
||||
"tx_rejected_dust_change": "ネットワークルール、低い変更量(ほこり)によって拒否されたトランザクション。すべてを送信するか、金額を減らしてみてください。",
|
||||
"tx_rejected_dust_output": "ネットワークルール、低出力量(ダスト)によって拒否されたトランザクション。金額を増やしてください。",
|
||||
"tx_rejected_dust_output_send_all": "ネットワークルール、低出力量(ダスト)によって拒否されたトランザクション。コイン管理下で選択されたコインのバランスを確認してください。",
|
||||
|
|
|
@ -746,8 +746,10 @@
|
|||
"trusted": "신뢰할 수 있는",
|
||||
"tx_commit_exception_no_dust_on_change": "이 금액으로 거래가 거부되었습니다. 이 코인을 사용하면 거스름돈 없이 ${min}를 보내거나 거스름돈을 반환하는 ${max}를 보낼 수 있습니다.",
|
||||
"tx_commit_failed": "거래 커밋이 실패했습니다. 지원에 연락하십시오.",
|
||||
"tx_invalid_input": "이 유형의 지불에 잘못 입력 유형을 사용하고 있습니다.",
|
||||
"tx_no_dust_exception": "너무 작은 금액을 보내면 거래가 거부됩니다. 금액을 늘리십시오.",
|
||||
"tx_not_enough_inputs_exception": "사용 가능한 입력이 충분하지 않습니다. 코인 컨트롤에서 더 많은 것을 선택하십시오",
|
||||
"tx_rejected_bip68_final": "거래는 확인되지 않은 입력을 받았으며 수수료로 교체하지 못했습니다.",
|
||||
"tx_rejected_dust_change": "네트워크 규칙, 낮은 변경 금액 (먼지)에 의해 거부 된 거래. 전부를 보내거나 금액을 줄이십시오.",
|
||||
"tx_rejected_dust_output": "네트워크 규칙, 낮은 출력 금액 (먼지)에 의해 거부 된 거래. 금액을 늘리십시오.",
|
||||
"tx_rejected_dust_output_send_all": "네트워크 규칙, 낮은 출력 금액 (먼지)에 의해 거부 된 거래. 동전 제어에서 선택한 동전의 균형을 확인하십시오.",
|
||||
|
|
|
@ -745,8 +745,10 @@
|
|||
"trusted": "ယုံတယ်။",
|
||||
"tx_commit_exception_no_dust_on_change": "အဆိုပါငွေပေးငွေယူကဒီပမာဏနှင့်အတူပယ်ချခံရသည်။ ဤဒင်္ဂါးပြားများနှင့်အတူပြောင်းလဲမှုကိုပြန်လည်ပြောင်းလဲခြင်းသို့မဟုတ် ${min} မပါဘဲ ${max} ပေးပို့နိုင်သည်။",
|
||||
"tx_commit_failed": "ငွေပေးငွေယူကျူးလွန်မှုပျက်ကွက်။ ကျေးဇူးပြုပြီးပံ့ပိုးမှုဆက်သွယ်ပါ။",
|
||||
"tx_invalid_input": "သင်သည်ဤငွေပေးချေမှုအမျိုးအစားအတွက်မှားယွင်းသော input type ကိုအသုံးပြုနေသည်",
|
||||
"tx_no_dust_exception": "ငွေပမာဏကိုသေးငယ်လွန်းသောငွေပမာဏကိုပေးပို့ခြင်းဖြင့်ပယ်ဖျက်ခြင်းကိုငြင်းပယ်သည်။ ကျေးဇူးပြုပြီးငွေပမာဏကိုတိုးမြှင့်ကြိုးစားပါ။",
|
||||
"tx_not_enough_inputs_exception": "အလုံအလောက်သွင်းအားစုများမလုံလောက်။ ကျေးဇူးပြုပြီးဒင်္ဂါးပြားထိန်းချုပ်မှုအောက်တွင်ပိုမိုရွေးချယ်ပါ",
|
||||
"tx_rejected_bip68_final": "ငွေပေးငွေယူသည်အတည်မပြုရသေးသောသွင်းအားစုများရှိပြီးအခကြေးငွေဖြင့်အစားထိုးရန်ပျက်ကွက်ခဲ့သည်။",
|
||||
"tx_rejected_dust_change": "Network စည်းမျဉ်းစည်းကမ်းများဖြင့်ပယ်ဖျက်ခြင်းသည် Network စည်းမျဉ်းစည်းကမ်းများဖြင့်ငြင်းပယ်ခြင်း, အားလုံးပေးပို့ခြင်းသို့မဟုတ်ငွေပမာဏကိုလျှော့ချကြိုးစားပါ။",
|
||||
"tx_rejected_dust_output": "Network စည်းမျဉ်းစည်းကမ်းများဖြင့် ပယ်ချ. ငွေပေးချေမှုသည် output output (ဖုန်မှုန့်) ဖြင့်ပယ်ချခဲ့သည်။ ကျေးဇူးပြုပြီးငွေပမာဏကိုတိုးမြှင့်ပေးပါ။",
|
||||
"tx_rejected_dust_output_send_all": "Network စည်းမျဉ်းစည်းကမ်းများဖြင့် ပယ်ချ. ငွေပေးချေမှုသည် output output (ဖုန်မှုန့်) ဖြင့်ပယ်ချခဲ့သည်။ ဒင်္ဂါးပြားထိန်းချုပ်မှုအောက်တွင်ရွေးချယ်ထားသောဒင်္ဂါးများ၏လက်ကျန်ငွေကိုစစ်ဆေးပါ။",
|
||||
|
|
|
@ -745,8 +745,10 @@
|
|||
"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_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_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_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.",
|
||||
|
|
|
@ -745,8 +745,10 @@
|
|||
"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_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_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_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.",
|
||||
|
|
|
@ -747,8 +747,10 @@
|
|||
"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_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_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_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.",
|
||||
|
@ -760,7 +762,7 @@
|
|||
"unconfirmed": "Saldo não confirmado",
|
||||
"understand": "Entendo",
|
||||
"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_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.",
|
||||
|
|
|
@ -746,8 +746,10 @@
|
|||
"trusted": "доверенный",
|
||||
"tx_commit_exception_no_dust_on_change": "Транзакция отклоняется с этой суммой. С этими монетами вы можете отправлять ${min} без изменения или ${max}, которые возвращают изменение.",
|
||||
"tx_commit_failed": "Комплект транзакции не удался. Пожалуйста, свяжитесь с поддержкой.",
|
||||
"tx_invalid_input": "Вы используете неправильный тип ввода для этого типа оплаты",
|
||||
"tx_no_dust_exception": "Транзакция отклоняется путем отправки слишком маленькой суммы. Пожалуйста, попробуйте увеличить сумму.",
|
||||
"tx_not_enough_inputs_exception": "Недостаточно входов доступны. Пожалуйста, выберите больше под контролем монет",
|
||||
"tx_rejected_bip68_final": "Транзакция имеет неподтвержденные входные данные и не смогли заменить на плату.",
|
||||
"tx_rejected_dust_change": "Транзакция отклоняется в соответствии с правилами сети, низкой суммой изменений (пыль). Попробуйте отправить все или уменьшить сумму.",
|
||||
"tx_rejected_dust_output": "Транзакция отклоняется в соответствии с правилами сети, низкой выходной суммой (пыль). Пожалуйста, увеличьте сумму.",
|
||||
"tx_rejected_dust_output_send_all": "Транзакция отклоняется в соответствии с правилами сети, низкой выходной суммой (пыль). Пожалуйста, проверьте баланс монет, выбранных под контролем монет.",
|
||||
|
|
|
@ -745,8 +745,10 @@
|
|||
"trusted": "มั่นคง",
|
||||
"tx_commit_exception_no_dust_on_change": "ธุรกรรมถูกปฏิเสธด้วยจำนวนเงินนี้ ด้วยเหรียญเหล่านี้คุณสามารถส่ง ${min} โดยไม่ต้องเปลี่ยนแปลงหรือ ${max} ที่ส่งคืนการเปลี่ยนแปลง",
|
||||
"tx_commit_failed": "การทำธุรกรรมล้มเหลว กรุณาติดต่อฝ่ายสนับสนุน",
|
||||
"tx_invalid_input": "คุณกำลังใช้ประเภทอินพุตที่ไม่ถูกต้องสำหรับการชำระเงินประเภทนี้",
|
||||
"tx_no_dust_exception": "การทำธุรกรรมถูกปฏิเสธโดยการส่งจำนวนน้อยเกินไป โปรดลองเพิ่มจำนวนเงิน",
|
||||
"tx_not_enough_inputs_exception": "มีอินพุตไม่เพียงพอ โปรดเลือกเพิ่มเติมภายใต้การควบคุมเหรียญ",
|
||||
"tx_rejected_bip68_final": "การทำธุรกรรมมีอินพุตที่ไม่ได้รับการยืนยันและไม่สามารถแทนที่ด้วยค่าธรรมเนียม",
|
||||
"tx_rejected_dust_change": "ธุรกรรมถูกปฏิเสธโดยกฎเครือข่ายจำนวนการเปลี่ยนแปลงต่ำ (ฝุ่น) ลองส่งทั้งหมดหรือลดจำนวนเงิน",
|
||||
"tx_rejected_dust_output": "การทำธุรกรรมถูกปฏิเสธโดยกฎเครือข่ายจำนวนเอาต์พุตต่ำ (ฝุ่น) โปรดเพิ่มจำนวนเงิน",
|
||||
"tx_rejected_dust_output_send_all": "การทำธุรกรรมถูกปฏิเสธโดยกฎเครือข่ายจำนวนเอาต์พุตต่ำ (ฝุ่น) โปรดตรวจสอบยอดคงเหลือของเหรียญที่เลือกภายใต้การควบคุมเหรียญ",
|
||||
|
|
|
@ -745,8 +745,10 @@
|
|||
"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_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_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_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.",
|
||||
|
|
|
@ -745,8 +745,10 @@
|
|||
"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_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_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_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.",
|
||||
|
|
|
@ -746,8 +746,10 @@
|
|||
"trusted": "довіряють",
|
||||
"tx_commit_exception_no_dust_on_change": "Транзакція відхилена цією сумою. За допомогою цих монет ви можете надіслати ${min} без змін або ${max}, що повертає зміни.",
|
||||
"tx_commit_failed": "Транзакційна комісія не вдалося. Будь ласка, зв'яжіться з підтримкою.",
|
||||
"tx_invalid_input": "Ви використовуєте неправильний тип введення для цього типу оплати",
|
||||
"tx_no_dust_exception": "Угода відхиляється, відправивши суму занадто мала. Будь ласка, спробуйте збільшити суму.",
|
||||
"tx_not_enough_inputs_exception": "Недостатньо доступних входів. Виберіть більше під контролем монети",
|
||||
"tx_rejected_bip68_final": "Трансакція має непідтверджені входи і не замінила плату.",
|
||||
"tx_rejected_dust_change": "Транзакція відхилена за допомогою мережевих правил, низька кількість змін (пил). Спробуйте надіслати все або зменшити суму.",
|
||||
"tx_rejected_dust_output": "Транзакція відхилена за допомогою мережевих правил, низька кількість вихідної кількості (пил). Будь ласка, збільшуйте суму.",
|
||||
"tx_rejected_dust_output_send_all": "Транзакція відхилена за допомогою мережевих правил, низька кількість вихідної кількості (пил). Будь ласка, перевірте баланс монет, вибраних під контролем монет.",
|
||||
|
|
|
@ -747,8 +747,10 @@
|
|||
"trusted": "قابل اعتماد",
|
||||
"tx_commit_exception_no_dust_on_change": "اس رقم سے لین دین کو مسترد کردیا گیا ہے۔ ان سککوں کے ذریعہ آپ بغیر کسی تبدیلی کے ${min} یا ${max} بھیج سکتے ہیں جو لوٹتے ہیں۔",
|
||||
"tx_commit_failed": "ٹرانزیکشن کمٹ ناکام ہوگیا۔ براہ کرم سپورٹ سے رابطہ کریں۔",
|
||||
"tx_invalid_input": "آپ اس قسم کی ادائیگی کے لئے غلط ان پٹ کی قسم استعمال کررہے ہیں",
|
||||
"tx_no_dust_exception": "لین دین کو بہت چھوٹی رقم بھیج کر مسترد کردیا جاتا ہے۔ براہ کرم رقم میں اضافہ کرنے کی کوشش کریں۔",
|
||||
"tx_not_enough_inputs_exception": "کافی ان پٹ دستیاب نہیں ہے۔ براہ کرم سکے کے کنٹرول میں مزید منتخب کریں",
|
||||
"tx_rejected_bip68_final": "لین دین میں غیر مصدقہ آدانوں کی ہے اور وہ فیس کے ذریعہ تبدیل کرنے میں ناکام رہا ہے۔",
|
||||
"tx_rejected_dust_change": "نیٹ ورک کے قواعد ، کم تبدیلی کی رقم (دھول) کے ذریعہ لین دین کو مسترد کردیا گیا۔ سب کو بھیجنے یا رقم کو کم کرنے کی کوشش کریں۔",
|
||||
"tx_rejected_dust_output": "لین دین کو نیٹ ورک کے قواعد ، کم آؤٹ پٹ رقم (دھول) کے ذریعہ مسترد کردیا گیا۔ براہ کرم رقم میں اضافہ کریں۔",
|
||||
"tx_rejected_dust_output_send_all": "لین دین کو نیٹ ورک کے قواعد ، کم آؤٹ پٹ رقم (دھول) کے ذریعہ مسترد کردیا گیا۔ براہ کرم سکے کے کنٹرول میں منتخب کردہ سکے کا توازن چیک کریں۔",
|
||||
|
|
|
@ -746,8 +746,10 @@
|
|||
"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_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_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_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.",
|
||||
|
|
|
@ -745,8 +745,10 @@
|
|||
"trusted": "值得信赖",
|
||||
"tx_commit_exception_no_dust_on_change": "交易被此金额拒绝。使用这些硬币,您可以发送${min}无需更改或返回${max}的变化。",
|
||||
"tx_commit_failed": "交易承诺失败。请联系支持。",
|
||||
"tx_invalid_input": "您正在使用错误的输入类型进行此类付款",
|
||||
"tx_no_dust_exception": "通过发送太小的金额来拒绝交易。请尝试增加金额。",
|
||||
"tx_not_enough_inputs_exception": "没有足够的输入。请在硬币控制下选择更多",
|
||||
"tx_rejected_bip68_final": "交易未确认投入,未能取代费用。",
|
||||
"tx_rejected_dust_change": "交易被网络规则拒绝,较低的变化数量(灰尘)。尝试发送全部或减少金额。",
|
||||
"tx_rejected_dust_output": "交易被网络规则,低输出量(灰尘)拒绝。请增加金额。",
|
||||
"tx_rejected_dust_output_send_all": "交易被网络规则,低输出量(灰尘)拒绝。请检查在硬币控制下选择的硬币的余额。",
|
||||
|
|
|
@ -173,6 +173,7 @@ abstract class Bitcoin {
|
|||
Future<void> rescan(Object wallet, {required int height, bool? doSingleScan});
|
||||
bool getNodeIsCakeElectrs(Object wallet);
|
||||
void deleteSilentPaymentAddress(Object wallet, String address);
|
||||
Future<void> updateFeeRates(Object wallet);
|
||||
}
|
||||
""";
|
||||
|
||||
|
|
Loading…
Reference in a new issue