Bitcoin Core 22.99.0
P2P Digital Currency
sendcoinsdialog.cpp
Go to the documentation of this file.
1// Copyright (c) 2011-2020 The Bitcoin Core developers
2// Distributed under the MIT software license, see the accompanying
3// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5#if defined(HAVE_CONFIG_H)
7#endif
8
11
13#include <qt/bitcoinunits.h>
14#include <qt/clientmodel.h>
16#include <qt/guiutil.h>
17#include <qt/optionsmodel.h>
18#include <qt/platformstyle.h>
19#include <qt/sendcoinsentry.h>
20
21#include <chainparams.h>
22#include <interfaces/node.h>
23#include <key_io.h>
24#include <node/ui_interface.h>
25#include <policy/fees.h>
26#include <txmempool.h>
27#include <wallet/coincontrol.h>
28#include <wallet/fees.h>
29#include <wallet/wallet.h>
30
31#include <validation.h>
32
33#include <QFontMetrics>
34#include <QScrollBar>
35#include <QSettings>
36#include <QTextDocument>
37
38static constexpr std::array confTargets{2, 4, 6, 12, 24, 48, 144, 504, 1008};
39int getConfTargetForIndex(int index) {
40 if (index+1 > static_cast<int>(confTargets.size())) {
41 return confTargets.back();
42 }
43 if (index < 0) {
44 return confTargets[0];
45 }
46 return confTargets[index];
47}
48int getIndexForConfTarget(int target) {
49 for (unsigned int i = 0; i < confTargets.size(); i++) {
50 if (confTargets[i] >= target) {
51 return i;
52 }
53 }
54 return confTargets.size() - 1;
55}
56
57SendCoinsDialog::SendCoinsDialog(const PlatformStyle *_platformStyle, QWidget *parent) :
58 QDialog(parent, GUIUtil::dialog_flags),
59 ui(new Ui::SendCoinsDialog),
60 clientModel(nullptr),
61 model(nullptr),
62 m_coin_control(new CCoinControl),
63 fNewRecipientAllowed(true),
64 fFeeMinimized(true),
65 platformStyle(_platformStyle)
66{
67 ui->setupUi(this);
68
69 if (!_platformStyle->getImagesOnButtons()) {
70 ui->addButton->setIcon(QIcon());
71 ui->clearButton->setIcon(QIcon());
72 ui->sendButton->setIcon(QIcon());
73 } else {
74 ui->addButton->setIcon(_platformStyle->SingleColorIcon(":/icons/add"));
75 ui->clearButton->setIcon(_platformStyle->SingleColorIcon(":/icons/remove"));
76 ui->sendButton->setIcon(_platformStyle->SingleColorIcon(":/icons/send"));
77 }
78
80
81 addEntry();
82
83 connect(ui->addButton, &QPushButton::clicked, this, &SendCoinsDialog::addEntry);
84 connect(ui->clearButton, &QPushButton::clicked, this, &SendCoinsDialog::clear);
85
86 // Coin Control
87 connect(ui->pushButtonCoinControl, &QPushButton::clicked, this, &SendCoinsDialog::coinControlButtonClicked);
88 connect(ui->checkBoxCoinControlChange, &QCheckBox::stateChanged, this, &SendCoinsDialog::coinControlChangeChecked);
89 connect(ui->lineEditCoinControlChange, &QValidatedLineEdit::textEdited, this, &SendCoinsDialog::coinControlChangeEdited);
90
91 // Coin Control: clipboard actions
92 QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this);
93 QAction *clipboardAmountAction = new QAction(tr("Copy amount"), this);
94 QAction *clipboardFeeAction = new QAction(tr("Copy fee"), this);
95 QAction *clipboardAfterFeeAction = new QAction(tr("Copy after fee"), this);
96 QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this);
97 QAction *clipboardLowOutputAction = new QAction(tr("Copy dust"), this);
98 QAction *clipboardChangeAction = new QAction(tr("Copy change"), this);
99 connect(clipboardQuantityAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardQuantity);
100 connect(clipboardAmountAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardAmount);
101 connect(clipboardFeeAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardFee);
102 connect(clipboardAfterFeeAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardAfterFee);
103 connect(clipboardBytesAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardBytes);
104 connect(clipboardLowOutputAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardLowOutput);
105 connect(clipboardChangeAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardChange);
106 ui->labelCoinControlQuantity->addAction(clipboardQuantityAction);
107 ui->labelCoinControlAmount->addAction(clipboardAmountAction);
108 ui->labelCoinControlFee->addAction(clipboardFeeAction);
109 ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction);
110 ui->labelCoinControlBytes->addAction(clipboardBytesAction);
111 ui->labelCoinControlLowOutput->addAction(clipboardLowOutputAction);
112 ui->labelCoinControlChange->addAction(clipboardChangeAction);
113
114 // init transaction fee section
115 QSettings settings;
116 if (!settings.contains("fFeeSectionMinimized"))
117 settings.setValue("fFeeSectionMinimized", true);
118 if (!settings.contains("nFeeRadio") && settings.contains("nTransactionFee") && settings.value("nTransactionFee").toLongLong() > 0) // compatibility
119 settings.setValue("nFeeRadio", 1); // custom
120 if (!settings.contains("nFeeRadio"))
121 settings.setValue("nFeeRadio", 0); // recommended
122 if (!settings.contains("nSmartFeeSliderPosition"))
123 settings.setValue("nSmartFeeSliderPosition", 0);
124 if (!settings.contains("nTransactionFee"))
125 settings.setValue("nTransactionFee", (qint64)DEFAULT_PAY_TX_FEE);
126 ui->groupFee->setId(ui->radioSmartFee, 0);
127 ui->groupFee->setId(ui->radioCustomFee, 1);
128 ui->groupFee->button((int)std::max(0, std::min(1, settings.value("nFeeRadio").toInt())))->setChecked(true);
129 ui->customFee->SetAllowEmpty(false);
130 ui->customFee->setValue(settings.value("nTransactionFee").toLongLong());
131 minimizeFeeSection(settings.value("fFeeSectionMinimized").toBool());
132
134}
135
137{
138 this->clientModel = _clientModel;
139
140 if (_clientModel) {
142 }
143}
144
146{
147 this->model = _model;
148
149 if(_model && _model->getOptionsModel())
150 {
151 for(int i = 0; i < ui->entries->count(); ++i)
152 {
153 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
154 if(entry)
155 {
156 entry->setModel(_model);
157 }
158 }
159
160 interfaces::WalletBalances balances = _model->wallet().getBalances();
161 setBalance(balances);
165
166 // Coin Control
171
172 // fee section
173 for (const int n : confTargets) {
174 ui->confTargetSelector->addItem(tr("%1 (%2 blocks)").arg(GUIUtil::formatNiceTimeOffset(n*Params().GetConsensus().nPowTargetSpacing)).arg(n));
175 }
176 connect(ui->confTargetSelector, qOverload<int>(&QComboBox::currentIndexChanged), this, &SendCoinsDialog::updateSmartFeeLabel);
177 connect(ui->confTargetSelector, qOverload<int>(&QComboBox::currentIndexChanged), this, &SendCoinsDialog::coinControlUpdateLabels);
178
179#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
180 connect(ui->groupFee, &QButtonGroup::idClicked, this, &SendCoinsDialog::updateFeeSectionControls);
181 connect(ui->groupFee, &QButtonGroup::idClicked, this, &SendCoinsDialog::coinControlUpdateLabels);
182#else
183 connect(ui->groupFee, qOverload<int>(&QButtonGroup::buttonClicked), this, &SendCoinsDialog::updateFeeSectionControls);
184 connect(ui->groupFee, qOverload<int>(&QButtonGroup::buttonClicked), this, &SendCoinsDialog::coinControlUpdateLabels);
185#endif
186
188 connect(ui->optInRBF, &QCheckBox::stateChanged, this, &SendCoinsDialog::updateSmartFeeLabel);
189 connect(ui->optInRBF, &QCheckBox::stateChanged, this, &SendCoinsDialog::coinControlUpdateLabels);
190 CAmount requiredFee = model->wallet().getRequiredFee(1000);
191 ui->customFee->SetMinValue(requiredFee);
192 if (ui->customFee->value() < requiredFee) {
193 ui->customFee->setValue(requiredFee);
194 }
195 ui->customFee->setSingleStep(requiredFee);
198
199 // set default rbf checkbox state
200 ui->optInRBF->setCheckState(Qt::Checked);
201
202 if (model->wallet().hasExternalSigner()) {
203 //: "device" usually means a hardware wallet.
204 ui->sendButton->setText(tr("Sign on device"));
205 if (gArgs.GetArg("-signer", "") != "") {
206 ui->sendButton->setEnabled(true);
207 ui->sendButton->setToolTip(tr("Connect your hardware wallet first."));
208 } else {
209 ui->sendButton->setEnabled(false);
210 //: "External signer" means using devices such as hardware wallets.
211 ui->sendButton->setToolTip(tr("Set external signer script path in Options -> Wallet"));
212 }
213 } else if (model->wallet().privateKeysDisabled()) {
214 ui->sendButton->setText(tr("Cr&eate Unsigned"));
215 ui->sendButton->setToolTip(tr("Creates a Partially Signed Bitcoin Transaction (PSBT) for use with e.g. an offline %1 wallet, or a PSBT-compatible hardware wallet.").arg(PACKAGE_NAME));
216 }
217
218 // set the smartfee-sliders default value (wallets default conf.target or last stored value)
219 QSettings settings;
220 if (settings.value("nSmartFeeSliderPosition").toInt() != 0) {
221 // migrate nSmartFeeSliderPosition to nConfTarget
222 // nConfTarget is available since 0.15 (replaced nSmartFeeSliderPosition)
223 int nConfirmTarget = 25 - settings.value("nSmartFeeSliderPosition").toInt(); // 25 == old slider range
224 settings.setValue("nConfTarget", nConfirmTarget);
225 settings.remove("nSmartFeeSliderPosition");
226 }
227 if (settings.value("nConfTarget").toInt() == 0)
229 else
230 ui->confTargetSelector->setCurrentIndex(getIndexForConfTarget(settings.value("nConfTarget").toInt()));
231 }
232}
233
235{
236 QSettings settings;
237 settings.setValue("fFeeSectionMinimized", fFeeMinimized);
238 settings.setValue("nFeeRadio", ui->groupFee->checkedId());
239 settings.setValue("nConfTarget", getConfTargetForIndex(ui->confTargetSelector->currentIndex()));
240 settings.setValue("nTransactionFee", (qint64)ui->customFee->value());
241
242 delete ui;
243}
244
245bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informative_text, QString& detailed_text)
246{
247 QList<SendCoinsRecipient> recipients;
248 bool valid = true;
249
250 for(int i = 0; i < ui->entries->count(); ++i)
251 {
252 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
253 if(entry)
254 {
255 if(entry->validate(model->node()))
256 {
257 recipients.append(entry->getValue());
258 }
259 else if (valid)
260 {
261 ui->scrollArea->ensureWidgetVisible(entry);
262 valid = false;
263 }
264 }
265 }
266
267 if(!valid || recipients.isEmpty())
268 {
269 return false;
270 }
271
272 fNewRecipientAllowed = false;
274 if(!ctx.isValid())
275 {
276 // Unlock wallet was cancelled
278 return false;
279 }
280
281 // prepare transaction for getting txFee earlier
282 m_current_transaction = std::make_unique<WalletModelTransaction>(recipients);
283 WalletModel::SendCoinsReturn prepareStatus;
284
286
288
289 // process prepareStatus and on error generate message shown to user
290 processSendCoinsReturn(prepareStatus,
292
293 if(prepareStatus.status != WalletModel::OK) {
295 return false;
296 }
297
298 CAmount txFee = m_current_transaction->getTransactionFee();
299 QStringList formatted;
300 for (const SendCoinsRecipient &rcp : m_current_transaction->getRecipients())
301 {
302 // generate amount string with wallet name in case of multiwallet
303 QString amount = BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
304 if (model->isMultiwallet()) {
305 amount.append(tr(" from wallet '%1'").arg(GUIUtil::HtmlEscape(model->getWalletName())));
306 }
307
308 // generate address string
309 QString address = rcp.address;
310
311 QString recipientElement;
312
313 {
314 if(rcp.label.length() > 0) // label with address
315 {
316 recipientElement.append(tr("%1 to '%2'").arg(amount, GUIUtil::HtmlEscape(rcp.label)));
317 recipientElement.append(QString(" (%1)").arg(address));
318 }
319 else // just address
320 {
321 recipientElement.append(tr("%1 to %2").arg(amount, address));
322 }
323 }
324 formatted.append(recipientElement);
325 }
326
328 question_string.append(tr("Do you want to draft this transaction?"));
329 } else {
330 question_string.append(tr("Are you sure you want to send?"));
331 }
332
333 question_string.append("<br /><span style='font-size:10pt;'>");
335 question_string.append(tr("Please, review your transaction proposal. This will produce a Partially Signed Bitcoin Transaction (PSBT) which you can save or copy and then sign with e.g. an offline %1 wallet, or a PSBT-compatible hardware wallet.").arg(PACKAGE_NAME));
336 } else {
337 question_string.append(tr("Please, review your transaction."));
338 }
339 question_string.append("</span>%1");
340
341 if(txFee > 0)
342 {
343 // append fee string if a fee is required
344 question_string.append("<hr /><b>");
345 question_string.append(tr("Transaction fee"));
346 question_string.append("</b>");
347
348 // append transaction size
349 question_string.append(" (" + QString::number((double)m_current_transaction->getTransactionSize() / 1000) + " kB): ");
350
351 // append transaction fee value
352 question_string.append("<span style='color:#aa0000; font-weight:bold;'>");
353 question_string.append(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), txFee));
354 question_string.append("</span><br />");
355
356 // append RBF message according to transaction's signalling
357 question_string.append("<span style='font-size:10pt; font-weight:normal;'>");
358 if (ui->optInRBF->isChecked()) {
359 question_string.append(tr("You can increase the fee later (signals Replace-By-Fee, BIP-125)."));
360 } else {
361 question_string.append(tr("Not signalling Replace-By-Fee, BIP-125."));
362 }
363 question_string.append("</span>");
364 }
365
366 // add total amount in all subdivision units
367 question_string.append("<hr />");
368 CAmount totalAmount = m_current_transaction->getTotalTransactionAmount() + txFee;
369 QStringList alternativeUnits;
371 {
373 alternativeUnits.append(BitcoinUnits::formatHtmlWithUnit(u, totalAmount));
374 }
375 question_string.append(QString("<b>%1</b>: <b>%2</b>").arg(tr("Total Amount"))
377 question_string.append(QString("<br /><span style='font-size:10pt; font-weight:normal;'>(=%1)</span>")
378 .arg(alternativeUnits.join(" " + tr("or") + " ")));
379
380 if (formatted.size() > 1) {
381 question_string = question_string.arg("");
382 informative_text = tr("To review recipient list click \"Show Details…\"");
383 detailed_text = formatted.join("\n\n");
384 } else {
385 question_string = question_string.arg("<br /><br />" + formatted.at(0));
386 }
387
388 return true;
389}
390
391void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
392{
393 if(!model || !model->getOptionsModel())
394 return;
395
396 QString question_string, informative_text, detailed_text;
397 if (!PrepareSendText(question_string, informative_text, detailed_text)) return;
399
400 const QString confirmation = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner() ? tr("Confirm transaction proposal") : tr("Confirm send coins");
401 const QString confirmButtonText = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner() ? tr("Create Unsigned") : tr("Sign and send");
402 auto confirmationDialog = new SendConfirmationDialog(confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, confirmButtonText, this);
403 confirmationDialog->setAttribute(Qt::WA_DeleteOnClose);
404 // TODO: Replace QDialog::exec() with safer QDialog::show().
405 const auto retval = static_cast<QMessageBox::StandardButton>(confirmationDialog->exec());
406
407 if(retval != QMessageBox::Yes)
408 {
410 return;
411 }
412
413 bool send_failure = false;
417 bool complete = false;
418 // Always fill without signing first. This prevents an external signer
419 // from being called prematurely and is not expensive.
420 TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, nullptr, psbtx, complete);
421 assert(!complete);
423 if (model->wallet().hasExternalSigner()) {
424 try {
425 err = model->wallet().fillPSBT(SIGHASH_ALL, true /* sign */, true /* bip32derivs */, nullptr, psbtx, complete);
426 } catch (const std::runtime_error& e) {
427 QMessageBox::critical(nullptr, tr("Sign failed"), e.what());
428 send_failure = true;
429 return;
430 }
432 //: "External signer" means using devices such as hardware wallets.
433 QMessageBox::critical(nullptr, tr("External signer not found"), "External signer not found");
434 send_failure = true;
435 return;
436 }
438 //: "External signer" means using devices such as hardware wallets.
439 QMessageBox::critical(nullptr, tr("External signer failure"), "External signer failure");
440 send_failure = true;
441 return;
442 }
443 if (err != TransactionError::OK) {
444 tfm::format(std::cerr, "Failed to sign PSBT");
446 send_failure = true;
447 return;
448 }
449 // fillPSBT does not always properly finalize
450 complete = FinalizeAndExtractPSBT(psbtx, mtx);
451 }
452
453 // Broadcast transaction if complete (even with an external signer this
454 // is not always the case, e.g. in a multisig wallet).
455 if (complete) {
456 const CTransactionRef tx = MakeTransactionRef(mtx);
457 m_current_transaction->setWtx(tx);
459 // process sendStatus and on error generate message shown to user
460 processSendCoinsReturn(sendStatus);
461
462 if (sendStatus.status == WalletModel::OK) {
463 Q_EMIT coinsSent(m_current_transaction->getWtx()->GetHash());
464 } else {
465 send_failure = true;
466 }
467 return;
468 }
469
470 // Copy PSBT to clipboard and offer to save
471 assert(!complete);
472 // Serialize the PSBT
474 ssTx << psbtx;
475 GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str());
476 QMessageBox msgBox;
477 msgBox.setText("Unsigned Transaction");
478 msgBox.setInformativeText("The PSBT has been copied to the clipboard. You can also save it.");
479 msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard);
480 msgBox.setDefaultButton(QMessageBox::Discard);
481 switch (msgBox.exec()) {
482 case QMessageBox::Save: {
483 QString selectedFilter;
484 QString fileNameSuggestion = "";
485 bool first = true;
486 for (const SendCoinsRecipient &rcp : m_current_transaction->getRecipients()) {
487 if (!first) {
488 fileNameSuggestion.append(" - ");
489 }
490 QString labelOrAddress = rcp.label.isEmpty() ? rcp.address : rcp.label;
491 QString amount = BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
492 fileNameSuggestion.append(labelOrAddress + "-" + amount);
493 first = false;
494 }
495 fileNameSuggestion.append(".psbt");
496 QString filename = GUIUtil::getSaveFileName(this,
497 tr("Save Transaction Data"), fileNameSuggestion,
498 //: Expanded name of the binary PSBT file format. See: BIP 174.
499 tr("Partially Signed Transaction (Binary)") + QLatin1String(" (*.psbt)"), &selectedFilter);
500 if (filename.isEmpty()) {
501 return;
502 }
503 std::ofstream out(filename.toLocal8Bit().data(), std::ofstream::out | std::ofstream::binary);
504 out << ssTx.str();
505 out.close();
506 Q_EMIT message(tr("PSBT saved"), "PSBT saved to disk", CClientUIInterface::MSG_INFORMATION);
507 break;
508 }
509 case QMessageBox::Discard:
510 break;
511 default:
512 assert(false);
513 } // msgBox.exec()
514 } else {
515 // now send the prepared transaction
517 // process sendStatus and on error generate message shown to user
518 processSendCoinsReturn(sendStatus);
519
520 if (sendStatus.status == WalletModel::OK) {
521 Q_EMIT coinsSent(m_current_transaction->getWtx()->GetHash());
522 } else {
523 send_failure = true;
524 }
525 }
526 if (!send_failure) {
527 accept();
528 m_coin_control->UnSelectAll();
530 }
532 m_current_transaction.reset();
533}
534
536{
537 m_current_transaction.reset();
538
539 // Clear coin control settings
540 m_coin_control->UnSelectAll();
541 ui->checkBoxCoinControlChange->setChecked(false);
544
545 // Remove entries until only one left
546 while(ui->entries->count())
547 {
548 ui->entries->takeAt(0)->widget()->deleteLater();
549 }
550 addEntry();
551
553}
554
556{
557 clear();
558}
559
561{
562 clear();
563}
564
566{
567 SendCoinsEntry *entry = new SendCoinsEntry(platformStyle, this);
568 entry->setModel(model);
569 ui->entries->addWidget(entry);
574
575 // Focus the field, so that entry can start immediately
576 entry->clear();
577 entry->setFocus();
579 qApp->processEvents();
580 QScrollBar* bar = ui->scrollArea->verticalScrollBar();
581 if(bar)
582 bar->setSliderPosition(bar->maximum());
583
585 return entry;
586}
587
589{
590 setupTabChain(nullptr);
592}
593
595{
596 entry->hide();
597
598 // If the last entry is about to be removed add an empty one
599 if (ui->entries->count() == 1)
600 addEntry();
601
602 entry->deleteLater();
603
605}
606
607QWidget *SendCoinsDialog::setupTabChain(QWidget *prev)
608{
609 for(int i = 0; i < ui->entries->count(); ++i)
610 {
611 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
612 if(entry)
613 {
614 prev = entry->setupTabChain(prev);
615 }
616 }
617 QWidget::setTabOrder(prev, ui->sendButton);
618 QWidget::setTabOrder(ui->sendButton, ui->clearButton);
619 QWidget::setTabOrder(ui->clearButton, ui->addButton);
620 return ui->addButton;
621}
622
623void SendCoinsDialog::setAddress(const QString &address)
624{
625 SendCoinsEntry *entry = nullptr;
626 // Replace the first entry if it is still unused
627 if(ui->entries->count() == 1)
628 {
629 SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
630 if(first->isClear())
631 {
632 entry = first;
633 }
634 }
635 if(!entry)
636 {
637 entry = addEntry();
638 }
639
640 entry->setAddress(address);
641}
642
644{
646 return;
647
648 SendCoinsEntry *entry = nullptr;
649 // Replace the first entry if it is still unused
650 if(ui->entries->count() == 1)
651 {
652 SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
653 if(first->isClear())
654 {
655 entry = first;
656 }
657 }
658 if(!entry)
659 {
660 entry = addEntry();
661 }
662
663 entry->setValue(rv);
665}
666
668{
669 // Just paste the entry, all pre-checks
670 // are done in paymentserver.cpp.
671 pasteEntry(rv);
672 return true;
673}
674
676{
677 if(model && model->getOptionsModel())
678 {
679 CAmount balance = balances.balance;
680 if (model->wallet().hasExternalSigner()) {
681 ui->labelBalanceName->setText(tr("External balance:"));
682 } else if (model->wallet().privateKeysDisabled()) {
683 balance = balances.watch_only_balance;
684 ui->labelBalanceName->setText(tr("Watch-only balance:"));
685 }
687 }
688}
689
691{
695}
696
697void SendCoinsDialog::processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg)
698{
699 QPair<QString, CClientUIInterface::MessageBoxFlags> msgParams;
700 // Default to a warning message, override if error message is needed
701 msgParams.second = CClientUIInterface::MSG_WARNING;
702
703 // This comment is specific to SendCoinsDialog usage of WalletModel::SendCoinsReturn.
704 // All status values are used only in WalletModel::prepareTransaction()
705 switch(sendCoinsReturn.status)
706 {
708 msgParams.first = tr("The recipient address is not valid. Please recheck.");
709 break;
711 msgParams.first = tr("The amount to pay must be larger than 0.");
712 break;
714 msgParams.first = tr("The amount exceeds your balance.");
715 break;
717 msgParams.first = tr("The total exceeds your balance when the %1 transaction fee is included.").arg(msgArg);
718 break;
720 msgParams.first = tr("Duplicate address found: addresses should only be used once each.");
721 break;
723 msgParams.first = tr("Transaction creation failed!");
724 msgParams.second = CClientUIInterface::MSG_ERROR;
725 break;
727 msgParams.first = tr("A fee higher than %1 is considered an absurdly high fee.").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), model->wallet().getDefaultMaxTxFee()));
728 break;
730 msgParams.first = tr("Payment request expired.");
731 msgParams.second = CClientUIInterface::MSG_ERROR;
732 break;
733 // included to prevent a compiler warning.
734 case WalletModel::OK:
735 default:
736 return;
737 }
738
739 Q_EMIT message(tr("Send Coins"), msgParams.first, msgParams.second);
740}
741
743{
744 ui->labelFeeMinimized->setVisible(fMinimize);
745 ui->buttonChooseFee ->setVisible(fMinimize);
746 ui->buttonMinimizeFee->setVisible(!fMinimize);
747 ui->frameFeeSelection->setVisible(!fMinimize);
748 ui->horizontalLayoutSmartFee->setContentsMargins(0, (fMinimize ? 0 : 6), 0, 0);
749 fFeeMinimized = fMinimize;
750}
751
753{
754 minimizeFeeSection(false);
755}
756
758{
760 minimizeFeeSection(true);
761}
762
764{
765 // Include watch-only for wallets without private key
767
768 // Calculate available amount to send.
770 for (int i = 0; i < ui->entries->count(); ++i) {
771 SendCoinsEntry* e = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
772 if (e && !e->isHidden() && e != entry) {
773 amount -= e->getValue().amount;
774 }
775 }
776
777 if (amount > 0) {
779 entry->setAmount(amount);
780 } else {
781 entry->setAmount(0);
782 }
783}
784
786{
787 ui->confTargetSelector ->setEnabled(ui->radioSmartFee->isChecked());
788 ui->labelSmartFee ->setEnabled(ui->radioSmartFee->isChecked());
789 ui->labelSmartFee2 ->setEnabled(ui->radioSmartFee->isChecked());
790 ui->labelSmartFee3 ->setEnabled(ui->radioSmartFee->isChecked());
791 ui->labelFeeEstimation ->setEnabled(ui->radioSmartFee->isChecked());
792 ui->labelCustomFeeWarning ->setEnabled(ui->radioCustomFee->isChecked());
793 ui->labelCustomPerKilobyte ->setEnabled(ui->radioCustomFee->isChecked());
794 ui->customFee ->setEnabled(ui->radioCustomFee->isChecked());
795}
796
798{
799 if(!model || !model->getOptionsModel())
800 return;
801
802 if (ui->radioSmartFee->isChecked())
803 ui->labelFeeMinimized->setText(ui->labelSmartFee->text());
804 else {
806 }
807}
808
810{
811 if (ui->radioCustomFee->isChecked()) {
812 m_coin_control->m_feerate = CFeeRate(ui->customFee->value());
813 } else {
814 m_coin_control->m_feerate.reset();
815 }
816 // Avoid using global defaults when sending money from the GUI
817 // Either custom fee will be used or if not selected, the confirmation target from dropdown box
818 m_coin_control->m_confirm_target = getConfTargetForIndex(ui->confTargetSelector->currentIndex());
819 m_coin_control->m_signal_bip125_rbf = ui->optInRBF->isChecked();
820 // Include watch-only for wallets without private key
822}
823
824void SendCoinsDialog::updateNumberOfBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, bool headers, SynchronizationState sync_state) {
825 if (sync_state == SynchronizationState::POST_INIT) {
827 }
828}
829
831{
832 if(!model || !model->getOptionsModel())
833 return;
835 m_coin_control->m_feerate.reset(); // Explicitly use only fee estimation rate for smart fee labels
836 int returned_target;
837 FeeReason reason;
838 CFeeRate feeRate = CFeeRate(model->wallet().getMinimumFee(1000, *m_coin_control, &returned_target, &reason));
839
841
842 if (reason == FeeReason::FALLBACK) {
843 ui->labelSmartFee2->show(); // (Smart fee not initialized yet. This usually takes a few blocks...)
844 ui->labelFeeEstimation->setText("");
845 ui->fallbackFeeWarningLabel->setVisible(true);
846 int lightness = ui->fallbackFeeWarningLabel->palette().color(QPalette::WindowText).lightness();
847 QColor warning_colour(255 - (lightness / 5), 176 - (lightness / 3), 48 - (lightness / 14));
848 ui->fallbackFeeWarningLabel->setStyleSheet("QLabel { color: " + warning_colour.name() + "; }");
849 ui->fallbackFeeWarningLabel->setIndent(GUIUtil::TextWidth(QFontMetrics(ui->fallbackFeeWarningLabel->font()), "x"));
850 }
851 else
852 {
853 ui->labelSmartFee2->hide();
854 ui->labelFeeEstimation->setText(tr("Estimated to begin confirmation within %n block(s).", "", returned_target));
855 ui->fallbackFeeWarningLabel->setVisible(false);
856 }
857
859}
860
861// Coin Control: copy label "Quantity" to clipboard
863{
865}
866
867// Coin Control: copy label "Amount" to clipboard
869{
870 GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left(ui->labelCoinControlAmount->text().indexOf(" ")));
871}
872
873// Coin Control: copy label "Fee" to clipboard
875{
876 GUIUtil::setClipboard(ui->labelCoinControlFee->text().left(ui->labelCoinControlFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
877}
878
879// Coin Control: copy label "After fee" to clipboard
881{
882 GUIUtil::setClipboard(ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
883}
884
885// Coin Control: copy label "Bytes" to clipboard
887{
889}
890
891// Coin Control: copy label "Dust" to clipboard
893{
895}
896
897// Coin Control: copy label "Change" to clipboard
899{
900 GUIUtil::setClipboard(ui->labelCoinControlChange->text().left(ui->labelCoinControlChange->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
901}
902
903// Coin Control: settings menu - coin control enabled/disabled by user
905{
906 ui->frameCoinControl->setVisible(checked);
907
908 if (!checked && model) { // coin control features disabled
909 m_coin_control = std::make_unique<CCoinControl>();
910 }
911
913}
914
915// Coin Control: button inputs -> show actual coin control dialog
917{
919 connect(dlg, &QDialog::finished, this, &SendCoinsDialog::coinControlUpdateLabels);
921}
922
923// Coin Control: checkbox custom change address
925{
926 if (state == Qt::Unchecked)
927 {
928 m_coin_control->destChange = CNoDestination();
930 }
931 else
932 // use this to re-validate an already entered address
934
935 ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked));
936}
937
938// Coin Control: custom change address changed
940{
942 {
943 // Default to no change address until verified
944 m_coin_control->destChange = CNoDestination();
945 ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:red;}");
946
947 const CTxDestination dest = DecodeDestination(text.toStdString());
948
949 if (text.isEmpty()) // Nothing entered
950 {
951 ui->labelCoinControlChangeLabel->setText("");
952 }
953 else if (!IsValidDestination(dest)) // Invalid address
954 {
955 ui->labelCoinControlChangeLabel->setText(tr("Warning: Invalid Bitcoin address"));
956 }
957 else // Valid address
958 {
959 if (!model->wallet().isSpendable(dest)) {
960 ui->labelCoinControlChangeLabel->setText(tr("Warning: Unknown change address"));
961
962 // confirmation dialog
963 QMessageBox::StandardButton btnRetVal = QMessageBox::question(this, tr("Confirm custom change address"), tr("The address you selected for change is not part of this wallet. Any or all funds in your wallet may be sent to this address. Are you sure?"),
964 QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel);
965
966 if(btnRetVal == QMessageBox::Yes)
967 m_coin_control->destChange = dest;
968 else
969 {
970 ui->lineEditCoinControlChange->setText("");
971 ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
972 ui->labelCoinControlChangeLabel->setText("");
973 }
974 }
975 else // Known change address
976 {
977 ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
978
979 // Query label
980 QString associatedLabel = model->getAddressTableModel()->labelForAddress(text);
981 if (!associatedLabel.isEmpty())
982 ui->labelCoinControlChangeLabel->setText(associatedLabel);
983 else
984 ui->labelCoinControlChangeLabel->setText(tr("(no label)"));
985
986 m_coin_control->destChange = dest;
987 }
988 }
989 }
990}
991
992// Coin Control: update labels
994{
995 if (!model || !model->getOptionsModel())
996 return;
997
999
1000 // set pay amounts
1003
1004 for(int i = 0; i < ui->entries->count(); ++i)
1005 {
1006 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
1007 if(entry && !entry->isHidden())
1008 {
1009 SendCoinsRecipient rcp = entry->getValue();
1011 if (rcp.fSubtractFeeFromAmount)
1013 }
1014 }
1015
1016 if (m_coin_control->HasSelected())
1017 {
1018 // actual coin control calculation
1020
1021 // show coin control stats
1023 ui->widgetCoinControl->show();
1024 }
1025 else
1026 {
1027 // hide coin control stats
1029 ui->widgetCoinControl->hide();
1031 }
1032}
1033
1034SendConfirmationDialog::SendConfirmationDialog(const QString& title, const QString& text, const QString& informative_text, const QString& detailed_text, int _secDelay, const QString& _confirmButtonText, QWidget* parent)
1035 : QMessageBox(parent), secDelay(_secDelay), confirmButtonText(_confirmButtonText)
1036{
1037 setIcon(QMessageBox::Question);
1038 setWindowTitle(title); // On macOS, the window title is ignored (as required by the macOS Guidelines).
1039 setText(text);
1040 setInformativeText(informative_text);
1041 setDetailedText(detailed_text);
1042 setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel);
1043 setDefaultButton(QMessageBox::Cancel);
1044 yesButton = button(QMessageBox::Yes);
1045 if (confirmButtonText.isEmpty()) {
1046 confirmButtonText = yesButton->text();
1047 }
1049 connect(&countDownTimer, &QTimer::timeout, this, &SendConfirmationDialog::countDown);
1050}
1051
1053{
1055 countDownTimer.start(1000);
1056 return QMessageBox::exec();
1057}
1058
1060{
1061 secDelay--;
1063
1064 if(secDelay <= 0)
1065 {
1066 countDownTimer.stop();
1067 }
1068}
1069
1071{
1072 if(secDelay > 0)
1073 {
1074 yesButton->setEnabled(false);
1075 yesButton->setText(confirmButtonText + " (" + QString::number(secDelay) + ")");
1076 }
1077 else
1078 {
1079 yesButton->setEnabled(true);
1080 yesButton->setText(confirmButtonText);
1081 }
1082}
int64_t CAmount
Amount in satoshis (Can be negative)
Definition: amount.h:12
#define PACKAGE_NAME
const CChainParams & Params()
Return the currently selected parameters.
QString labelForAddress(const QString &address) const
Look up label for address in address book, if not found return empty string.
std::string GetArg(const std::string &strArg, const std::string &strDefault) const
Return string argument or default value.
Definition: system.cpp:590
void setEnabled(bool fEnabled)
Enable/Disable.
void setSingleStep(const CAmount &step)
Set single step in satoshis.
void SetMinValue(const CAmount &value)
Set the minimum value in satoshis.
void setDisplayUnit(int unit)
Change unit used to display amount.
void setValue(const CAmount &value)
void SetAllowEmpty(bool allow)
If allow empty is set to false the field will be set to the minimum allowed value if left empty.
static QString formatHtmlWithUnit(int unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD)
Format as HTML string (with unit)
static QString formatWithUnit(int unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD)
Format as string (with unit)
static QList< Unit > availableUnits()
Get list of units, for drop-down box.
Unit
Bitcoin units.
Definition: bitcoinunits.h:42
@ MSG_INFORMATION
Predefined combinations for certain default usage cases.
Definition: ui_interface.h:66
Coin Control Features.
Definition: coincontrol.h:29
Double ended buffer combining vector and stream-like interfaces.
Definition: streams.h:205
std::string str() const
Definition: streams.h:242
Fee rate in satoshis per kilobyte: CAmount / kB.
Definition: feerate.h:30
CAmount GetFeePerK() const
Return the fee in satoshis for a size of 1000 bytes.
Definition: feerate.h:57
Model for Bitcoin network client.
Definition: clientmodel.h:48
void numBlocksChanged(int count, const QDateTime &blockDate, double nVerificationProgress, bool header, SynchronizationState sync_state)
static void updateLabels(CCoinControl &m_coin_control, WalletModel *, QDialog *)
static QList< CAmount > payAmounts
static bool fSubtractFeeFromAmount
int getDisplayUnit() const
Definition: optionsmodel.h:88
bool getCoinControlFeatures() const
Definition: optionsmodel.h:91
void coinControlFeaturesChanged(bool)
void displayUnitChanged(int unit)
QIcon SingleColorIcon(const QString &filename) const
Colorize an icon (given filename) with the icon color.
bool getImagesOnButtons() const
Definition: platformstyle.h:21
void setEnabled(bool enabled)
Dialog for sending bitcoins.
void useAvailableBalance(SendCoinsEntry *entry)
WalletModel * model
ClientModel * clientModel
void coinControlChangeEdited(const QString &)
void coinControlChangeChecked(int)
void coinControlClipboardFee()
void updateNumberOfBlocks(int count, const QDateTime &blockDate, double nVerificationProgress, bool headers, SynchronizationState sync_state)
Ui::SendCoinsDialog * ui
void on_buttonChooseFee_clicked()
void processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg=QString())
void setClientModel(ClientModel *clientModel)
void updateFeeSectionControls()
std::unique_ptr< CCoinControl > m_coin_control
SendCoinsEntry * addEntry()
void pasteEntry(const SendCoinsRecipient &rv)
void updateFeeMinimizedLabel()
void accept() override
const PlatformStyle * platformStyle
void coinControlClipboardQuantity()
void coinControlButtonClicked()
void coinControlClipboardAfterFee()
QWidget * setupTabChain(QWidget *prev)
Set up the tab chain manually, as Qt messes up the tab chain by default in some cases (issue https://...
bool PrepareSendText(QString &question_string, QString &informative_text, QString &detailed_text)
void sendButtonClicked(bool checked)
void setModel(WalletModel *model)
void coinControlClipboardLowOutput()
bool handlePaymentRequest(const SendCoinsRecipient &recipient)
void setBalance(const interfaces::WalletBalances &balances)
void coinControlClipboardAmount()
void setAddress(const QString &address)
void coinControlClipboardChange()
std::unique_ptr< WalletModelTransaction > m_current_transaction
void removeEntry(SendCoinsEntry *entry)
void reject() override
void coinControlClipboardBytes()
void message(const QString &title, const QString &message, unsigned int style)
SendCoinsDialog(const PlatformStyle *platformStyle, QWidget *parent=nullptr)
void coinsSent(const uint256 &txid)
void on_buttonMinimizeFee_clicked()
void coinControlUpdateLabels()
void coinControlFeatureChanged(bool)
void minimizeFeeSection(bool fMinimize)
A single entry in the dialog for sending bitcoins.
void setFocus()
void setAddress(const QString &address)
bool isClear()
Return whether the entry is still empty and unedited.
void subtractFeeFromAmountChanged()
void useAvailableBalance(SendCoinsEntry *entry)
void setValue(const SendCoinsRecipient &value)
void setModel(WalletModel *model)
void removeEntry(SendCoinsEntry *entry)
void payAmountChanged()
void setAmount(const CAmount &amount)
QWidget * setupTabChain(QWidget *prev)
Set up the tab chain manually, as Qt messes up the tab chain by default in some cases (issue https://...
void clear()
bool validate(interfaces::Node &node)
void checkSubtractFeeFromAmount()
SendCoinsRecipient getValue()
QAbstractButton * yesButton
SendConfirmationDialog(const QString &title, const QString &text, const QString &informative_text="", const QString &detailed_text="", int secDelay=SEND_CONFIRM_DELAY, const QString &confirmText="", QWidget *parent=nullptr)
QWidget * scrollAreaWidgetContents
QLabel * labelCoinControlQuantity
QHBoxLayout * horizontalLayoutSmartFee
QLabel * fallbackFeeWarningLabel
QLabel * labelCoinControlAutomaticallySelected
QValidatedLineEdit * lineEditCoinControlChange
QRadioButton * radioSmartFee
QLabel * labelCoinControlChangeLabel
QButtonGroup * groupFee
QPushButton * buttonMinimizeFee
QScrollArea * scrollArea
QLabel * labelCoinControlInsuffFunds
QCheckBox * checkBoxCoinControlChange
QRadioButton * radioCustomFee
QPushButton * pushButtonCoinControl
void setupUi(QDialog *SendCoinsDialog)
QComboBox * confTargetSelector
QPushButton * clearButton
QLabel * labelCoinControlLowOutput
BitcoinAmountField * customFee
QPushButton * sendButton
QLabel * labelCoinControlAfterFee
QPushButton * buttonChooseFee
Interface to Bitcoin wallet from Qt view code.
Definition: walletmodel.h:52
interfaces::Node & node() const
Definition: walletmodel.h:143
SendCoinsReturn sendCoins(WalletModelTransaction &transaction)
AddressTableModel * getAddressTableModel()
OptionsModel * getOptionsModel()
SendCoinsReturn prepareTransaction(WalletModelTransaction &transaction, const CCoinControl &coinControl)
interfaces::Wallet & wallet() const
Definition: walletmodel.h:144
bool isMultiwallet()
UnlockContext requestUnlock()
void balanceChanged(const interfaces::WalletBalances &balances)
QString getWalletName() const
@ AmountWithFeeExceedsBalance
Definition: walletmodel.h:65
@ TransactionCreationFailed
Definition: walletmodel.h:67
@ AmountExceedsBalance
Definition: walletmodel.h:64
@ DuplicateAddress
Definition: walletmodel.h:66
@ PaymentRequestExpired
Definition: walletmodel.h:69
virtual TransactionError fillPSBT(int sighash_type, bool sign, bool bip32derivs, size_t *n_signed, PartiallySignedTransaction &psbtx, bool &complete)=0
Fill PSBT.
virtual CAmount getAvailableBalance(const CCoinControl &coin_control)=0
Get available balance.
virtual CAmount getRequiredFee(unsigned int tx_bytes)=0
Get required fee.
virtual unsigned int getConfirmTarget()=0
Get tx confirm target.
virtual bool hasExternalSigner()=0
virtual CAmount getDefaultMaxTxFee()=0
Get max tx fee.
virtual bool isSpendable(const CTxDestination &dest)=0
Return whether wallet has private key.
virtual WalletBalances getBalances()=0
Get balances.
virtual CAmount getMinimumFee(unsigned int tx_bytes, const CCoinControl &coin_control, int *returned_target, FeeReason *reason)=0
Get minimum fee.
virtual bool privateKeysDisabled()=0
#define ASYMP_UTF8
TransactionError
Definition: error.h:22
@ SIGHASH_ALL
Definition: interpreter.h:27
CTxDestination DecodeDestination(const std::string &str, std::string &error_msg)
Definition: key_io.cpp:261
Utility functions used by the Bitcoin Qt UI.
Definition: bitcoingui.h:59
QString HtmlEscape(const QString &str, bool fMultiLine)
Definition: guiutil.cpp:233
QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedSuffixOut)
Get save filename, mimics QFileDialog::getSaveFileName, except that it appends a default suffix when ...
Definition: guiutil.cpp:286
QString formatNiceTimeOffset(qint64 secs)
Definition: guiutil.cpp:754
constexpr auto dialog_flags
Definition: guiutil.h:60
auto ExceptionSafeConnect(Sender sender, Signal signal, Receiver receiver, Slot method, Qt::ConnectionType type=Qt::AutoConnection)
A drop-in replacement of QObject::connect function (see: https://doc.qt.io/qt-5/qobject....
Definition: guiutil.h:392
void ShowModalDialogAndDeleteOnClose(QDialog *dialog)
Shows a QDialog instance asynchronously, and deletes it on close.
Definition: guiutil.cpp:980
int TextWidth(const QFontMetrics &fm, const QString &text)
Returns the distance in pixels appropriate for drawing a subsequent character after text.
Definition: guiutil.cpp:882
void setupAddressWidget(QValidatedLineEdit *widget, QWidget *parent)
Definition: guiutil.cpp:114
void setClipboard(const QString &str)
Definition: guiutil.cpp:645
fs::ofstream ofstream
Definition: fs.h:225
void format(std::ostream &out, const char *fmt, const Args &... args)
Format list of arguments to the stream according to given format string.
Definition: tinyformat.h:1062
FeeReason
Definition: fees.h:43
static CTransactionRef MakeTransactionRef(Tx &&txIn)
Definition: transaction.h:387
std::shared_ptr< const CTransaction > CTransactionRef
Definition: transaction.h:386
bool FinalizeAndExtractPSBT(PartiallySignedTransaction &psbtx, CMutableTransaction &result)
Finalizes a PSBT if possible, and extracts it to a CMutableTransaction if it could be finalized.
Definition: psbt.cpp:333
int getConfTargetForIndex(int index)
int getIndexForConfTarget(int target)
static constexpr std::array confTargets
#define SEND_CONFIRM_DELAY
@ SER_NETWORK
Definition: serialize.h:138
bool IsValidDestination(const CTxDestination &dest)
Check whether a CTxDestination is a CNoDestination.
Definition: standard.cpp:332
std::variant< CNoDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessV1Taproot, WitnessUnknown > CTxDestination
A txout script template with a specific destination.
Definition: standard.h:157
std::string EncodeBase64(Span< const unsigned char > input)
A mutable version of CTransaction.
Definition: transaction.h:345
A version of CTransaction with the PSBT format.
Definition: psbt.h:392
Collection of wallet balances.
Definition: wallet.h:356
static secp256k1_context * ctx
Definition: tests.c:42
static int count
Definition: tests.c:41
ArgsManager gArgs
Definition: system.cpp:85
assert(!tx.IsCoinBase())
SynchronizationState
Current sync state passed to tip changed callbacks.
Definition: validation.h:93
static const int PROTOCOL_VERSION
network protocol versioning
Definition: version.h:12
constexpr CAmount DEFAULT_PAY_TX_FEE
-paytxfee default
Definition: wallet.h:67