Bitcoin ABC 0.32.12
P2P Digital Currency
sendcoinsdialog.cpp
Go to the documentation of this file.
1// Copyright (c) 2011-2016 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)
6#include <config/bitcoin-config.h>
7#endif
8
9#include <qt/forms/ui_sendcoinsdialog.h>
10#include <qt/sendcoinsdialog.h>
11
12#include <chainparams.h>
13#include <interfaces/node.h>
14#include <key_io.h>
15#include <node/ui_interface.h>
17#include <qt/bitcoinunits.h>
18#include <qt/clientmodel.h>
20#include <qt/guiutil.h>
21#include <qt/optionsmodel.h>
22#include <qt/platformstyle.h>
23#include <qt/sendcoinsentry.h>
24#include <txmempool.h>
25#include <wallet/coincontrol.h>
26#include <wallet/fees.h>
27#include <wallet/wallet.h>
28
29#include <validation.h>
30
31#include <array>
32#include <fstream>
33#include <memory>
34
35#include <QScrollBar>
36#include <QSettings>
37#include <QTextDocument>
38
40
42 WalletModel *_model, QWidget *parent)
43 : QDialog(parent), ui(new Ui::SendCoinsDialog), clientModel(nullptr),
44 model(_model), m_coin_control(new CCoinControl),
45 fNewRecipientAllowed(true), fFeeMinimized(true),
46 platformStyle(_platformStyle) {
47 ui->setupUi(this);
48
49 if (!_platformStyle->getImagesOnButtons()) {
50 ui->addButton->setIcon(QIcon());
51 ui->clearButton->setIcon(QIcon());
52 ui->sendButton->setIcon(QIcon());
53 } else {
54 ui->addButton->setIcon(_platformStyle->SingleColorIcon(":/icons/add"));
55 ui->clearButton->setIcon(
56 _platformStyle->SingleColorIcon(":/icons/remove"));
57 ui->sendButton->setIcon(
58 _platformStyle->SingleColorIcon(":/icons/send"));
59 }
60
61 GUIUtil::setupAddressWidget(ui->lineEditCoinControlChange, this);
62
63 addEntry();
64
65 connect(ui->addButton, &QPushButton::clicked, this,
67 connect(ui->clearButton, &QPushButton::clicked, this,
69
70 // Coin Control
71 connect(ui->pushButtonCoinControl, &QPushButton::clicked, this,
73 connect(ui->checkBoxCoinControlChange, &QCheckBox::stateChanged, this,
75 connect(ui->lineEditCoinControlChange, &QValidatedLineEdit::textEdited,
77
78 // Coin Control: clipboard actions
79 QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this);
80 QAction *clipboardAmountAction = new QAction(tr("Copy amount"), this);
81 QAction *clipboardFeeAction = new QAction(tr("Copy fee"), this);
82 QAction *clipboardAfterFeeAction = new QAction(tr("Copy after fee"), this);
83 QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this);
84 QAction *clipboardLowOutputAction = new QAction(tr("Copy dust"), this);
85 QAction *clipboardChangeAction = new QAction(tr("Copy change"), this);
86 connect(clipboardQuantityAction, &QAction::triggered, this,
88 connect(clipboardAmountAction, &QAction::triggered, this,
90 connect(clipboardFeeAction, &QAction::triggered, this,
92 connect(clipboardAfterFeeAction, &QAction::triggered, this,
94 connect(clipboardBytesAction, &QAction::triggered, this,
96 connect(clipboardLowOutputAction, &QAction::triggered, this,
98 connect(clipboardChangeAction, &QAction::triggered, this,
100 ui->labelCoinControlQuantity->addAction(clipboardQuantityAction);
101 ui->labelCoinControlAmount->addAction(clipboardAmountAction);
102 ui->labelCoinControlFee->addAction(clipboardFeeAction);
103 ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction);
104 ui->labelCoinControlBytes->addAction(clipboardBytesAction);
105 ui->labelCoinControlLowOutput->addAction(clipboardLowOutputAction);
106 ui->labelCoinControlChange->addAction(clipboardChangeAction);
107
108 // init transaction fee section
109 QSettings settings;
110 if (!settings.contains("fFeeSectionMinimized")) {
111 settings.setValue("fFeeSectionMinimized", true);
112 }
113 // compatibility
114 if (!settings.contains("nFeeRadio") &&
115 settings.contains("nTransactionFee") &&
116 settings.value("nTransactionFee").toLongLong() > 0) {
117 // custom
118 settings.setValue("nFeeRadio", 1);
119 }
120 if (!settings.contains("nFeeRadio")) {
121 // recommended
122 settings.setValue("nFeeRadio", 0);
123 }
124 if (!settings.contains("nTransactionFee")) {
125 settings.setValue("nTransactionFee",
126 qint64(DEFAULT_PAY_TX_FEE / SATOSHI));
127 }
128 ui->groupFee->setId(ui->radioSmartFee, 0);
129 ui->groupFee->setId(ui->radioCustomFee, 1);
130 ui->groupFee
131 ->button(
132 std::max<int>(0, std::min(1, settings.value("nFeeRadio").toInt())))
133 ->setChecked(true);
134 ui->customFee->SetAllowEmpty(false);
135 ui->customFee->setValue(
136 int64_t(settings.value("nTransactionFee").toLongLong()) * SATOSHI);
137 minimizeFeeSection(settings.value("fFeeSectionMinimized").toBool());
138
139 // Set the model properly.
141}
142
144 this->clientModel = _clientModel;
145
146 if (_clientModel) {
147 connect(_clientModel, &ClientModel::numBlocksChanged, this,
149 }
150}
151
153 this->model = _model;
154
155 if (_model && _model->getOptionsModel()) {
156 for (int i = 0; i < ui->entries->count(); ++i) {
157 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry *>(
158 ui->entries->itemAt(i)->widget());
159 if (entry) {
160 entry->setModel(_model);
161 }
162 }
163
164 interfaces::WalletBalances balances = _model->wallet().getBalances();
165 setBalance(balances);
166 connect(_model, &WalletModel::balanceChanged, this,
171
172 // Coin Control
175 connect(_model->getOptionsModel(),
178 ui->frameCoinControl->setVisible(
181
182 // fee section
183#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
184 const auto buttonClickedEvent =
185 QOverload<int>::of(&QButtonGroup::idClicked);
186#else
187 /* QOverload in introduced from Qt 5.7, but we support down to 5.5.1 */
188 const auto buttonClickedEvent =
189 static_cast<void (QButtonGroup::*)(int)>(
190 &QButtonGroup::buttonClicked);
191#endif
192 connect(ui->groupFee, buttonClickedEvent, this,
194 connect(ui->groupFee, buttonClickedEvent, this,
196 connect(ui->customFee, &BitcoinAmountField::valueChanged, this,
198 Amount requiredFee = model->wallet().getRequiredFee(1000);
199 ui->customFee->SetMinValue(requiredFee);
200 if (ui->customFee->value() < requiredFee) {
201 ui->customFee->setValue(requiredFee);
202 }
203 ui->customFee->setSingleStep(requiredFee);
206
208 ui->sendButton->setText(tr("Cr&eate Unsigned"));
209 ui->sendButton->setToolTip(
210 tr("Creates a Partially Signed Bitcoin Transaction (PSBT) for "
211 "use with e.g. an offline %1 wallet, or a PSBT-compatible "
212 "hardware wallet.")
213 .arg(PACKAGE_NAME));
214 }
215 }
216}
217
219 QSettings settings;
220 settings.setValue("fFeeSectionMinimized", fFeeMinimized);
221 settings.setValue("nFeeRadio", ui->groupFee->checkedId());
222 settings.setValue("nTransactionFee",
223 qint64(ui->customFee->value() / SATOSHI));
224
225 delete ui;
226}
227
228bool SendCoinsDialog::PrepareSendText(QString &question_string,
229 QString &informative_text,
230 QString &detailed_text) {
231 QList<SendCoinsRecipient> recipients;
232 bool valid = true;
233
234 for (int i = 0; i < ui->entries->count(); ++i) {
235 SendCoinsEntry *entry =
236 qobject_cast<SendCoinsEntry *>(ui->entries->itemAt(i)->widget());
237 if (entry) {
238 if (entry->validate(model->node())) {
239 recipients.append(entry->getValue());
240 } else if (valid) {
241 ui->scrollArea->ensureWidgetVisible(entry);
242 valid = false;
243 }
244 }
245 }
246
247 if (!valid || recipients.isEmpty()) {
248 return false;
249 }
250
251 fNewRecipientAllowed = false;
253 if (!ctx.isValid()) {
254 // Unlock wallet was cancelled
256 return false;
257 }
258
259 // prepare transaction for getting txFee earlier
261 std::make_unique<WalletModelTransaction>(recipients);
262 WalletModel::SendCoinsReturn prepareStatus;
263
265
266 prepareStatus =
268
269 // process prepareStatus and on error generate message shown to user
270 processSendCoinsReturn(prepareStatus,
273 m_current_transaction->getTransactionFee()));
274
275 if (prepareStatus.status != WalletModel::OK) {
277 return false;
278 }
279
280 Amount txFee = m_current_transaction->getTransactionFee();
281 QStringList formatted;
282 for (const SendCoinsRecipient &rcp :
283 m_current_transaction->getRecipients()) {
284 // generate amount string with wallet name in case of multiwallet
285 QString amount = BitcoinUnits::formatWithUnit(
286 model->getOptionsModel()->getDisplayUnit(), rcp.amount);
287 if (model->isMultiwallet()) {
288 amount.append(
289 tr(" from wallet '%1'")
291 }
292 // generate address string
293 QString address = rcp.address;
294
295 QString recipientElement;
296
297#ifdef ENABLE_BIP70
298 // normal payment
299 if (!rcp.paymentRequest.IsInitialized())
300#endif
301 {
302 if (rcp.label.length() > 0) {
303 // label with address
304 recipientElement.append(
305 tr("%1 to '%2'")
306 .arg(amount, GUIUtil::HtmlEscape(rcp.label)));
307 recipientElement.append(QString(" (%1)").arg(address));
308 } else {
309 // just address
310 recipientElement.append(tr("%1 to %2").arg(amount, address));
311 }
312 }
313#ifdef ENABLE_BIP70
314 // authenticated payment request
315 else if (!rcp.authenticatedMerchant.isEmpty()) {
316 recipientElement.append(
317 tr("%1 to '%2'").arg(amount, rcp.authenticatedMerchant));
318 } else {
319 // unauthenticated payment request
320 recipientElement.append(tr("%1 to %2").arg(amount, address));
321 }
322#endif
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(
336 tr("Please, review your transaction proposal. This will produce a "
337 "Partially Signed Bitcoin Transaction (PSBT) which you can save "
338 "or copy and then sign with e.g. an offline %1 wallet, or a "
339 "PSBT-compatible hardware wallet.")
340 .arg(PACKAGE_NAME));
341 } else {
342 question_string.append(tr("Please, review your transaction."));
343 }
344 question_string.append("</span>%1");
345
346 if (txFee > Amount::zero()) {
347 // append fee string if a fee is required
348 question_string.append("<hr /><b>");
349 question_string.append(tr("Transaction fee"));
350 question_string.append("</b>");
351
352 // append transaction size
353 question_string.append(
354 " (" +
355 QString::number(
356 (double)m_current_transaction->getTransactionSize() / 1000) +
357 " kB): ");
358
359 // append transaction fee value
360 question_string.append(
361 "<span style='color:#aa0000; font-weight:bold;'>");
362 question_string.append(BitcoinUnits::formatHtmlWithUnit(
363 model->getOptionsModel()->getDisplayUnit(), txFee));
364 question_string.append("</span><br />");
365 }
366
367 // add total amount in all subdivision units
368 question_string.append("<hr />");
369 Amount totalAmount =
370 m_current_transaction->getTotalTransactionAmount() + txFee;
371 QStringList alternativeUnits;
373 if (u != model->getOptionsModel()->getDisplayUnit()) {
374 alternativeUnits.append(
375 BitcoinUnits::formatHtmlWithUnit(u, totalAmount));
376 }
377 }
378 question_string.append(
379 QString("<b>%1</b>: <b>%2</b>")
380 .arg(tr("Total Amount"))
382 model->getOptionsModel()->getDisplayUnit(), totalAmount)));
383 question_string.append(
384 QString("<br /><span style='font-size:10pt; "
385 "font-weight:normal;'>(=%1)</span>")
386 .arg(alternativeUnits.join(" " + tr("or") + " ")));
387
388 if (formatted.size() > 1) {
389 question_string = question_string.arg("");
390 informative_text =
391 tr("To review recipient list click \"Show Details...\"");
392 detailed_text = formatted.join("\n\n");
393 } else {
394 question_string = question_string.arg("<br /><br />" + formatted.at(0));
395 }
396
397 return true;
398}
399
401 if (!model || !model->getOptionsModel()) {
402 return;
403 }
404
405 QString question_string, informative_text, detailed_text;
406 if (!PrepareSendText(question_string, informative_text, detailed_text)) {
407 return;
408 }
410
411 const QString confirmation = model->wallet().privateKeysDisabled()
412 ? tr("Confirm transaction proposal")
413 : tr("Confirm send coins");
414 const QString confirmButtonText = model->wallet().privateKeysDisabled()
415 ? tr("Create Unsigned")
416 : tr("Send");
417 auto confirmationDialog = new SendConfirmationDialog(
418 confirmation, question_string, informative_text, detailed_text,
419 SEND_CONFIRM_DELAY, confirmButtonText, this);
420 confirmationDialog->setAttribute(Qt::WA_DeleteOnClose);
421 // TODO: Replace QDialog::exec() with safer QDialog::show().
422 const auto retval =
423 static_cast<QMessageBox::StandardButton>(confirmationDialog->exec());
424
425 if (retval != QMessageBox::Yes) {
427 return;
428 }
429
430 bool send_failure = false;
435 bool complete = false;
436 std::optional<PSBTError> err = model->wallet().fillPSBT(
437 SigHashType().withForkId(), false /* sign */,
438 true /* bip32derivs */, psbtx, complete);
439 assert(!complete);
440 assert(!err);
441 // Serialize the PSBT
442 DataStream ssTx{};
443 ssTx << psbtx;
444 GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str());
445 QMessageBox msgBox;
446 msgBox.setText("Unsigned Transaction");
447 msgBox.setInformativeText(
448 "The PSBT has been copied to the clipboard. You can also save it.");
449 msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard);
450 msgBox.setDefaultButton(QMessageBox::Discard);
451 switch (msgBox.exec()) {
452 case QMessageBox::Save: {
453 QString selectedFilter;
454 QString fileNameSuggestion = "";
455 bool first = true;
456 for (const SendCoinsRecipient &rcp :
457 m_current_transaction->getRecipients()) {
458 if (!first) {
459 fileNameSuggestion.append(" - ");
460 }
461 QString labelOrAddress =
462 rcp.label.isEmpty() ? rcp.address : rcp.label;
463 QString amount = BitcoinUnits::formatWithUnit(
464 model->getOptionsModel()->getDisplayUnit(), rcp.amount);
465 fileNameSuggestion.append(labelOrAddress + "-" + amount);
466 first = false;
467 }
468 fileNameSuggestion.append(".psbt");
469 QString filename = GUIUtil::getSaveFileName(
470 this, tr("Save Transaction Data"), fileNameSuggestion,
471 tr("Partially Signed Transaction (Binary) (*.psbt)"),
472 &selectedFilter);
473 if (filename.isEmpty()) {
474 return;
475 }
476 std::ofstream out{filename.toLocal8Bit().data(),
477 std::ofstream::out | std::ofstream::binary};
478 out << ssTx.str();
479 out.close();
480 Q_EMIT message(tr("PSBT saved"), "PSBT saved to disk",
482 break;
483 }
484 case QMessageBox::Discard:
485 break;
486 default:
487 assert(false);
488 }
489 } else {
490 // now send the prepared transaction
493 // process sendStatus and on error generate message shown to user
494 processSendCoinsReturn(sendStatus);
495
496 if (sendStatus.status == WalletModel::OK) {
497 Q_EMIT coinsSent(m_current_transaction->getWtx()->GetId());
498 } else {
499 send_failure = true;
500 }
501 }
502 if (!send_failure) {
503 accept();
504 m_coin_control->UnSelectAll();
506 }
508 m_current_transaction.reset();
509}
510
512 m_current_transaction.reset();
513
514 // Clear coin control settings
515 m_coin_control->UnSelectAll();
516 ui->checkBoxCoinControlChange->setChecked(false);
517 ui->lineEditCoinControlChange->clear();
519
520 // Remove entries until only one left
521 while (ui->entries->count()) {
522 ui->entries->takeAt(0)->widget()->deleteLater();
523 }
524 addEntry();
525
527}
528
530 clear();
531}
532
534 clear();
535}
536
539 ui->entries->addWidget(entry);
540 connect(entry, &SendCoinsEntry::removeEntry, this,
542 connect(entry, &SendCoinsEntry::useAvailableBalance, this,
544 connect(entry, &SendCoinsEntry::payAmountChanged, this,
548
549 // Focus the field, so that entry can start immediately
550 entry->clear();
551 entry->setFocus();
552 ui->scrollAreaWidgetContents->resize(
553 ui->scrollAreaWidgetContents->sizeHint());
554 qApp->processEvents();
555 QScrollBar *bar = ui->scrollArea->verticalScrollBar();
556 if (bar) {
557 bar->setSliderPosition(bar->maximum());
558 }
559
561 return entry;
562}
563
565 setupTabChain(nullptr);
567}
568
570 entry->hide();
571
572 // If the last entry is about to be removed add an empty one
573 if (ui->entries->count() == 1) {
574 addEntry();
575 }
576
577 entry->deleteLater();
578
580}
581
582QWidget *SendCoinsDialog::setupTabChain(QWidget *prev) {
583 for (int i = 0; i < ui->entries->count(); ++i) {
584 SendCoinsEntry *entry =
585 qobject_cast<SendCoinsEntry *>(ui->entries->itemAt(i)->widget());
586 if (entry) {
587 prev = entry->setupTabChain(prev);
588 }
589 }
590 QWidget::setTabOrder(prev, ui->sendButton);
591 QWidget::setTabOrder(ui->sendButton, ui->clearButton);
592 QWidget::setTabOrder(ui->clearButton, ui->addButton);
593 return ui->addButton;
594}
595
596void SendCoinsDialog::setAddress(const QString &address) {
597 SendCoinsEntry *entry = nullptr;
598 // Replace the first entry if it is still unused
599 if (ui->entries->count() == 1) {
600 SendCoinsEntry *first =
601 qobject_cast<SendCoinsEntry *>(ui->entries->itemAt(0)->widget());
602 if (first->isClear()) {
603 entry = first;
604 }
605 }
606 if (!entry) {
607 entry = addEntry();
608 }
609
610 entry->setAddress(address);
611}
612
615 return;
616 }
617
618 SendCoinsEntry *entry = nullptr;
619 // Replace the first entry if it is still unused
620 if (ui->entries->count() == 1) {
621 SendCoinsEntry *first =
622 qobject_cast<SendCoinsEntry *>(ui->entries->itemAt(0)->widget());
623 if (first->isClear()) {
624 entry = first;
625 }
626 }
627 if (!entry) {
628 entry = addEntry();
629 }
630
631 entry->setValue(rv);
633}
634
636 // Just paste the entry, all pre-checks are done in paymentserver.cpp.
637 pasteEntry(rv);
638 return true;
639}
640
642 if (model && model->getOptionsModel()) {
643 Amount balance = balances.balance;
645 balance = balances.watch_only_balance;
646 ui->labelBalanceName->setText(tr("Watch-only balance:"));
647 }
648 ui->labelBalance->setText(BitcoinUnits::formatWithUnit(
650 }
651}
652
655 ui->customFee->setDisplayUnit(model->getOptionsModel()->getDisplayUnit());
657}
658
660 const WalletModel::SendCoinsReturn &sendCoinsReturn,
661 const QString &msgArg) {
662 QPair<QString, CClientUIInterface::MessageBoxFlags> msgParams;
663 // Default to a warning message, override if error message is needed
664 msgParams.second = CClientUIInterface::MSG_WARNING;
665
666 // This comment is specific to SendCoinsDialog usage of
667 // WalletModel::SendCoinsReturn.
668 // All status values are used only in WalletModel::prepareTransaction()
669 switch (sendCoinsReturn.status) {
671 msgParams.first =
672 tr("The recipient address is not valid. Please recheck.");
673 break;
675 msgParams.first = tr("The amount to pay must be larger than 0.");
676 break;
678 msgParams.first = tr("The amount exceeds your balance.");
679 break;
681 msgParams.first = tr("The total exceeds your balance when the %1 "
682 "transaction fee is included.")
683 .arg(msgArg);
684 break;
686 msgParams.first = tr("Duplicate address found: addresses should "
687 "only be used once each.");
688 break;
690 msgParams.first = tr("Transaction creation failed!");
691 msgParams.second = CClientUIInterface::MSG_ERROR;
692 break;
694 msgParams.first =
695 tr("A fee higher than %1 is considered an absurdly high fee.")
699 break;
701 msgParams.first = tr("Payment request expired.");
702 msgParams.second = CClientUIInterface::MSG_ERROR;
703 break;
704 // included to prevent a compiler warning.
705 case WalletModel::OK:
706 default:
707 return;
708 }
709
710 Q_EMIT message(tr("Send Coins"), msgParams.first, msgParams.second);
711}
712
714 ui->labelFeeMinimized->setVisible(fMinimize);
715 ui->buttonChooseFee->setVisible(fMinimize);
716 ui->buttonMinimizeFee->setVisible(!fMinimize);
717 ui->frameFeeSelection->setVisible(!fMinimize);
718 ui->horizontalLayoutSmartFee->setContentsMargins(0, (fMinimize ? 0 : 6), 0,
719 0);
720 fFeeMinimized = fMinimize;
721}
722
724 minimizeFeeSection(false);
725}
726
729 minimizeFeeSection(true);
730}
731
733 // Include watch-only for wallets without private key
734 m_coin_control->fAllowWatchOnly = model->wallet().privateKeysDisabled();
735
736 // Calculate available amount to send.
738 for (int i = 0; i < ui->entries->count(); ++i) {
739 SendCoinsEntry *e =
740 qobject_cast<SendCoinsEntry *>(ui->entries->itemAt(i)->widget());
741 if (e && !e->isHidden() && e != entry) {
742 amount -= e->getValue().amount;
743 }
744 }
745
746 if (amount > Amount::zero()) {
748 entry->setAmount(amount);
749 } else {
750 entry->setAmount(Amount::zero());
751 }
752}
753
755 ui->labelSmartFee->setEnabled(ui->radioSmartFee->isChecked());
756 ui->labelSmartFee2->setEnabled(ui->radioSmartFee->isChecked());
757 ui->labelFeeEstimation->setEnabled(ui->radioSmartFee->isChecked());
758 ui->labelCustomFeeWarning->setEnabled(ui->radioCustomFee->isChecked());
759 ui->labelCustomPerKilobyte->setEnabled(ui->radioCustomFee->isChecked());
760 ui->customFee->setEnabled(ui->radioCustomFee->isChecked());
761}
762
764 if (!model || !model->getOptionsModel()) {
765 return;
766 }
767
768 if (ui->radioSmartFee->isChecked()) {
769 ui->labelFeeMinimized->setText(ui->labelSmartFee->text());
770 } else {
771 ui->labelFeeMinimized->setText(
774 ui->customFee->value()) +
775 "/kB");
776 }
777}
778
780 if (ui->radioCustomFee->isChecked()) {
781 ctrl.m_feerate = CFeeRate(ui->customFee->value());
782 } else {
783 ctrl.m_feerate.reset();
784 }
785 // Include watch-only for wallets without private key
787}
788
790 const QDateTime &blockDate,
791 double nVerificationProgress,
792 SyncType synctype,
793 SynchronizationState sync_state) {
794 if (sync_state == SynchronizationState::POST_INIT) {
796 }
797}
798
800 if (!model || !model->getOptionsModel()) {
801 return;
802 }
803
805 // Explicitly use only fee estimation rate for smart fee labels
806 m_coin_control->m_feerate.reset();
808
809 ui->labelSmartFee->setText(
811 feeRate.GetFeePerK()) +
812 "/kB");
813 // not enough data => minfee
814 if (feeRate <= CFeeRate(Amount::zero())) {
815 // (Smart fee not initialized yet. This usually takes a few blocks...)
816 ui->labelSmartFee2->show();
817 ui->labelFeeEstimation->setText("");
818 } else {
819 ui->labelSmartFee2->hide();
820 ui->labelFeeEstimation->setText(
821 tr("Estimated to begin confirmation by next block."));
822 }
823
825}
826
827// Coin Control: copy label "Quantity" to clipboard
829 GUIUtil::setClipboard(ui->labelCoinControlQuantity->text());
830}
831
832// Coin Control: copy label "Amount" to clipboard
834 GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left(
835 ui->labelCoinControlAmount->text().indexOf(" ")));
836}
837
838// Coin Control: copy label "Fee" to clipboard
841 ui->labelCoinControlFee->text()
842 .left(ui->labelCoinControlFee->text().indexOf(" "))
843 .replace(ASYMP_UTF8, ""));
844}
845
846// Coin Control: copy label "After fee" to clipboard
849 ui->labelCoinControlAfterFee->text()
850 .left(ui->labelCoinControlAfterFee->text().indexOf(" "))
851 .replace(ASYMP_UTF8, ""));
852}
853
854// Coin Control: copy label "Bytes" to clipboard
857 ui->labelCoinControlBytes->text().replace(ASYMP_UTF8, ""));
858}
859
860// Coin Control: copy label "Dust" to clipboard
862 GUIUtil::setClipboard(ui->labelCoinControlLowOutput->text());
863}
864
865// Coin Control: copy label "Change" to clipboard
868 ui->labelCoinControlChange->text()
869 .left(ui->labelCoinControlChange->text().indexOf(" "))
870 .replace(ASYMP_UTF8, ""));
871}
872
873// Coin Control: settings menu - coin control enabled/disabled by user
875 ui->frameCoinControl->setVisible(checked);
876
877 // coin control features disabled
878 if (!checked && model) {
879 m_coin_control->SetNull();
880 }
881
883}
884
885// Coin Control: button inputs -> show actual coin control dialog
888 connect(dlg, &QDialog::finished, this,
891}
892
893// Coin Control: checkbox custom change address
895 if (state == Qt::Unchecked) {
896 m_coin_control->destChange = CNoDestination();
897 ui->labelCoinControlChangeLabel->clear();
898 } else {
899 // use this to re-validate an already entered address
900 coinControlChangeEdited(ui->lineEditCoinControlChange->text());
901 }
902
903 ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked));
904}
905
906// Coin Control: custom change address changed
908 if (model && model->getAddressTableModel()) {
909 // Default to no change address until verified
910 m_coin_control->destChange = CNoDestination();
911 ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:red;}");
912
913 const CTxDestination dest =
914 DecodeDestination(text.toStdString(), model->getChainParams());
915
916 if (text.isEmpty()) {
917 // Nothing entered
918 ui->labelCoinControlChangeLabel->setText("");
919 } else if (!IsValidDestination(dest)) {
920 // Invalid address
921 ui->labelCoinControlChangeLabel->setText(
922 tr("Warning: Invalid Bitcoin address"));
923 } else {
924 // Valid address
925 if (!model->wallet().isSpendable(dest)) {
926 ui->labelCoinControlChangeLabel->setText(
927 tr("Warning: Unknown change address"));
928
929 // confirmation dialog
930 QMessageBox::StandardButton btnRetVal = QMessageBox::question(
931 this, tr("Confirm custom change address"),
932 tr("The address you selected for change is not part of "
933 "this wallet. Any or all funds in your wallet may be "
934 "sent to this address. Are you sure?"),
935 QMessageBox::Yes | QMessageBox::Cancel,
936 QMessageBox::Cancel);
937
938 if (btnRetVal == QMessageBox::Yes) {
939 m_coin_control->destChange = dest;
940 } else {
941 ui->lineEditCoinControlChange->setText("");
942 ui->labelCoinControlChangeLabel->setStyleSheet(
943 "QLabel{color:black;}");
944 ui->labelCoinControlChangeLabel->setText("");
945 }
946 } else {
947 // Known change address
948 ui->labelCoinControlChangeLabel->setStyleSheet(
949 "QLabel{color:black;}");
950
951 // Query label
952 QString associatedLabel =
954 if (!associatedLabel.isEmpty()) {
955 ui->labelCoinControlChangeLabel->setText(associatedLabel);
956 } else {
957 ui->labelCoinControlChangeLabel->setText(tr("(no label)"));
958 }
959
960 m_coin_control->destChange = dest;
961 }
962 }
963 }
964}
965
966// Coin Control: update labels
968 if (!model || !model->getOptionsModel()) {
969 return;
970 }
971
973
974 // set pay amounts
977 for (int i = 0; i < ui->entries->count(); ++i) {
978 SendCoinsEntry *entry =
979 qobject_cast<SendCoinsEntry *>(ui->entries->itemAt(i)->widget());
980 if (entry && !entry->isHidden()) {
981 SendCoinsRecipient rcp = entry->getValue();
983 if (rcp.fSubtractFeeFromAmount) {
985 }
986 }
987 }
988
989 if (m_coin_control->HasSelected()) {
990 // actual coin control calculation
992
993 // show coin control stats
994 ui->labelCoinControlAutomaticallySelected->hide();
995 ui->widgetCoinControl->show();
996 } else {
997 // hide coin control stats
998 ui->labelCoinControlAutomaticallySelected->show();
999 ui->widgetCoinControl->hide();
1000 ui->labelCoinControlInsuffFunds->hide();
1001 }
1002}
1003
1005 const QString &title, const QString &text, const QString &informative_text,
1006 const QString &detailed_text, int _secDelay,
1007 const QString &_confirmButtonText, QWidget *parent)
1008 : QMessageBox(parent), secDelay(_secDelay),
1009 confirmButtonText(_confirmButtonText) {
1010 setIcon(QMessageBox::Question);
1011 // On macOS, the window title is ignored (as required by the macOS
1012 // Guidelines).
1013 setWindowTitle(title);
1014 setText(text);
1015 setInformativeText(informative_text);
1016 setDetailedText(detailed_text);
1017 setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel);
1018 setDefaultButton(QMessageBox::Cancel);
1019 yesButton = button(QMessageBox::Yes);
1021 connect(&countDownTimer, &QTimer::timeout, this,
1023}
1024
1027 countDownTimer.start(1000);
1028 return QMessageBox::exec();
1029}
1030
1032 secDelay--;
1034
1035 if (secDelay <= 0) {
1036 countDownTimer.stop();
1037 }
1038}
1039
1041 if (secDelay > 0) {
1042 yesButton->setEnabled(false);
1043 yesButton->setText(confirmButtonText + " (" +
1044 QString::number(secDelay) + ")");
1045 } else {
1046 yesButton->setEnabled(true);
1047 yesButton->setText(confirmButtonText);
1048 }
1049}
static constexpr Amount SATOSHI
Definition: amount.h:148
QString labelForAddress(const QString &address) const
Look up label for address in address book, if not found return empty string.
static QString formatWithUnit(int unit, const Amount 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
Currency units Please add only sensible ones.
Definition: bitcoinunits.h:42
static QString formatHtmlWithUnit(int unit, const Amount amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD)
Format as HTML string (with unit)
@ MSG_INFORMATION
Predefined combinations for certain default usage cases.
Definition: ui_interface.h:72
Coin Control Features.
Definition: coincontrol.h:21
bool fAllowWatchOnly
Includes watch only addresses which are solvable.
Definition: coincontrol.h:34
std::optional< CFeeRate > m_feerate
Override the wallet's m_pay_tx_fee if set.
Definition: coincontrol.h:38
Fee rate in satoshis per kilobyte: Amount / kB.
Definition: feerate.h:21
Amount GetFeePerK() const
Return the fee in satoshis for a size of 1000 bytes.
Definition: feerate.h:54
A mutable version of CTransaction.
Definition: transaction.h:274
Model for Bitcoin network client.
Definition: clientmodel.h:43
void numBlocksChanged(int count, const QDateTime &blockDate, double nVerificationProgress, SyncType header, SynchronizationState sync_state)
static QList< Amount > payAmounts
static void updateLabels(CCoinControl &m_coin_control, WalletModel *, QDialog *)
static bool fSubtractFeeFromAmount
Double ended buffer combining vector and stream-like interfaces.
Definition: streams.h:118
int getDisplayUnit() const
Definition: optionsmodel.h:97
bool getCoinControlFeatures() const
Definition: optionsmodel.h:100
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:20
Dialog for sending bitcoins.
void useAvailableBalance(SendCoinsEntry *entry)
WalletModel * model
ClientModel * clientModel
void coinControlChangeEdited(const QString &)
void coinControlChangeChecked(int)
void coinControlClipboardFee()
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 updateNumberOfBlocks(int count, const QDateTime &blockDate, double nVerificationProgress, SyncType synctype, SynchronizationState sync_state)
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 setModel(WalletModel *model)
void coinControlClipboardLowOutput()
bool handlePaymentRequest(const SendCoinsRecipient &recipient)
SendCoinsDialog(const PlatformStyle *platformStyle, WalletModel *model, QWidget *parent=nullptr)
void setBalance(const interfaces::WalletBalances &balances)
void coinControlClipboardAmount()
void updateCoinControlState(CCoinControl &ctrl)
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)
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 setAmount(const Amount amount)
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()
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()
SendConfirmationDialog(const QString &title, const QString &text, const QString &informative_text="", const QString &detailed_text="", int secDelay=SEND_CONFIRM_DELAY, const QString &confirmText="Send", QWidget *parent=nullptr)
QAbstractButton * yesButton
Signature hash type wrapper class.
Definition: sighashtype.h:37
Interface to Bitcoin wallet from Qt view code.
Definition: walletmodel.h:47
interfaces::Node & node() const
Definition: walletmodel.h:149
SendCoinsReturn sendCoins(WalletModelTransaction &transaction)
const CChainParams & getChainParams() const
AddressTableModel * getAddressTableModel()
OptionsModel * getOptionsModel()
SendCoinsReturn prepareTransaction(WalletModelTransaction &transaction, const CCoinControl &coinControl)
interfaces::Wallet & wallet() const
Definition: walletmodel.h:150
bool isMultiwallet()
UnlockContext requestUnlock()
void balanceChanged(const interfaces::WalletBalances &balances)
QString getWalletName() const
@ AmountWithFeeExceedsBalance
Definition: walletmodel.h:63
@ TransactionCreationFailed
Definition: walletmodel.h:66
@ AmountExceedsBalance
Definition: walletmodel.h:62
@ DuplicateAddress
Definition: walletmodel.h:64
@ PaymentRequestExpired
Definition: walletmodel.h:68
virtual Amount getAvailableBalance(const CCoinControl &coin_control)=0
Get available balance.
virtual std::optional< common::PSBTError > fillPSBT(SigHashType sighash_type, bool sign, bool bip32derivs, PartiallySignedTransaction &psbtx, bool &complete) const =0
Fill PSBT.
virtual bool isSpendable(const CTxDestination &dest)=0
Return whether wallet has private key.
virtual WalletBalances getBalances()=0
Get balances.
virtual bool privateKeysDisabled()=0
virtual Amount getMinimumFee(unsigned int tx_bytes, const CCoinControl &coin_control)=0
Get minimum fee.
virtual Amount getDefaultMaxTxFee()=0
Get max tx fee.
virtual Amount getRequiredFee(unsigned int tx_bytes)=0
Get required fee.
SyncType
Definition: clientmodel.h:40
#define ASYMP_UTF8
static Amount balance
CTxDestination DecodeDestination(const std::string &addr, const CChainParams &params)
Definition: key_io.cpp:174
secp256k1_context * ctx
Definition: bench_impl.h:13
QString HtmlEscape(const QString &str, bool fMultiLine)
Definition: guiutil.cpp:256
void ShowModalDialogAsynchronously(QDialog *dialog)
Shows a QDialog instance asynchronously, and deletes it on close.
Definition: guiutil.cpp:1000
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:310
void setupAddressWidget(QValidatedLineEdit *widget, QWidget *parent)
Definition: guiutil.cpp:135
void setClipboard(const QString &str)
Definition: guiutil.cpp:782
PSBTError
Definition: types.h:17
#define SEND_CONFIRM_DELAY
bool IsValidDestination(const CTxDestination &dest)
Check whether a CTxDestination is a CNoDestination.
Definition: standard.cpp:260
std::variant< CNoDestination, PKHash, ScriptHash > CTxDestination
A txout script template with a specific destination.
Definition: standard.h:85
Definition: amount.h:21
static constexpr Amount zero() noexcept
Definition: amount.h:34
A version of CTransaction with the PSBT format.
Definition: psbt.h:334
Collection of wallet balances.
Definition: wallet.h:355
static int count
std::string EncodeBase64(Span< const uint8_t > input)
assert(!tx.IsCoinBase())
SynchronizationState
Current sync state passed to tip changed callbacks.
Definition: validation.h:118
constexpr Amount DEFAULT_PAY_TX_FEE
-paytxfee default
Definition: wallet.h:101