Bitcoin ABC 0.32.4
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 WalletModel *_model, QWidget *parent)
41 : QDialog(parent), ui(new Ui::SendCoinsDialog), clientModel(nullptr),
42 model(_model), m_coin_control(new CCoinControl),
43 fNewRecipientAllowed(true), fFeeMinimized(true),
44 platformStyle(_platformStyle) {
45 ui->setupUi(this);
46
47 if (!_platformStyle->getImagesOnButtons()) {
48 ui->addButton->setIcon(QIcon());
49 ui->clearButton->setIcon(QIcon());
50 ui->sendButton->setIcon(QIcon());
51 } else {
52 ui->addButton->setIcon(_platformStyle->SingleColorIcon(":/icons/add"));
53 ui->clearButton->setIcon(
54 _platformStyle->SingleColorIcon(":/icons/remove"));
55 ui->sendButton->setIcon(
56 _platformStyle->SingleColorIcon(":/icons/send"));
57 }
58
59 GUIUtil::setupAddressWidget(ui->lineEditCoinControlChange, this);
60
61 addEntry();
62
63 connect(ui->addButton, &QPushButton::clicked, this,
65 connect(ui->clearButton, &QPushButton::clicked, this,
67
68 // Coin Control
69 connect(ui->pushButtonCoinControl, &QPushButton::clicked, this,
71 connect(ui->checkBoxCoinControlChange, &QCheckBox::stateChanged, this,
73 connect(ui->lineEditCoinControlChange, &QValidatedLineEdit::textEdited,
75
76 // Coin Control: clipboard actions
77 QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this);
78 QAction *clipboardAmountAction = new QAction(tr("Copy amount"), this);
79 QAction *clipboardFeeAction = new QAction(tr("Copy fee"), this);
80 QAction *clipboardAfterFeeAction = new QAction(tr("Copy after fee"), this);
81 QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this);
82 QAction *clipboardLowOutputAction = new QAction(tr("Copy dust"), this);
83 QAction *clipboardChangeAction = new QAction(tr("Copy change"), this);
84 connect(clipboardQuantityAction, &QAction::triggered, this,
86 connect(clipboardAmountAction, &QAction::triggered, this,
88 connect(clipboardFeeAction, &QAction::triggered, this,
90 connect(clipboardAfterFeeAction, &QAction::triggered, this,
92 connect(clipboardBytesAction, &QAction::triggered, this,
94 connect(clipboardLowOutputAction, &QAction::triggered, this,
96 connect(clipboardChangeAction, &QAction::triggered, this,
98 ui->labelCoinControlQuantity->addAction(clipboardQuantityAction);
99 ui->labelCoinControlAmount->addAction(clipboardAmountAction);
100 ui->labelCoinControlFee->addAction(clipboardFeeAction);
101 ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction);
102 ui->labelCoinControlBytes->addAction(clipboardBytesAction);
103 ui->labelCoinControlLowOutput->addAction(clipboardLowOutputAction);
104 ui->labelCoinControlChange->addAction(clipboardChangeAction);
105
106 // init transaction fee section
107 QSettings settings;
108 if (!settings.contains("fFeeSectionMinimized")) {
109 settings.setValue("fFeeSectionMinimized", true);
110 }
111 // compatibility
112 if (!settings.contains("nFeeRadio") &&
113 settings.contains("nTransactionFee") &&
114 settings.value("nTransactionFee").toLongLong() > 0) {
115 // custom
116 settings.setValue("nFeeRadio", 1);
117 }
118 if (!settings.contains("nFeeRadio")) {
119 // recommended
120 settings.setValue("nFeeRadio", 0);
121 }
122 if (!settings.contains("nTransactionFee")) {
123 settings.setValue("nTransactionFee",
124 qint64(DEFAULT_PAY_TX_FEE / SATOSHI));
125 }
126 ui->groupFee->setId(ui->radioSmartFee, 0);
127 ui->groupFee->setId(ui->radioCustomFee, 1);
128 ui->groupFee
129 ->button(
130 std::max<int>(0, std::min(1, settings.value("nFeeRadio").toInt())))
131 ->setChecked(true);
132 ui->customFee->SetAllowEmpty(false);
133 ui->customFee->setValue(
134 int64_t(settings.value("nTransactionFee").toLongLong()) * SATOSHI);
135 minimizeFeeSection(settings.value("fFeeSectionMinimized").toBool());
136
137 // Set the model properly.
139}
140
142 this->clientModel = _clientModel;
143
144 if (_clientModel) {
145 connect(_clientModel, &ClientModel::numBlocksChanged, this,
147 }
148}
149
151 this->model = _model;
152
153 if (_model && _model->getOptionsModel()) {
154 for (int i = 0; i < ui->entries->count(); ++i) {
155 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry *>(
156 ui->entries->itemAt(i)->widget());
157 if (entry) {
158 entry->setModel(_model);
159 }
160 }
161
162 interfaces::WalletBalances balances = _model->wallet().getBalances();
163 setBalance(balances);
164 connect(_model, &WalletModel::balanceChanged, this,
169
170 // Coin Control
173 connect(_model->getOptionsModel(),
176 ui->frameCoinControl->setVisible(
179
180 // fee section
181#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
182 const auto buttonClickedEvent =
183 QOverload<int>::of(&QButtonGroup::idClicked);
184#else
185 /* QOverload in introduced from Qt 5.7, but we support down to 5.5.1 */
186 const auto buttonClickedEvent =
187 static_cast<void (QButtonGroup::*)(int)>(
188 &QButtonGroup::buttonClicked);
189#endif
190 connect(ui->groupFee, buttonClickedEvent, this,
192 connect(ui->groupFee, buttonClickedEvent, this,
194 connect(ui->customFee, &BitcoinAmountField::valueChanged, this,
196 Amount requiredFee = model->wallet().getRequiredFee(1000);
197 ui->customFee->SetMinValue(requiredFee);
198 if (ui->customFee->value() < requiredFee) {
199 ui->customFee->setValue(requiredFee);
200 }
201 ui->customFee->setSingleStep(requiredFee);
204
206 ui->sendButton->setText(tr("Cr&eate Unsigned"));
207 ui->sendButton->setToolTip(
208 tr("Creates a Partially Signed Bitcoin Transaction (PSBT) for "
209 "use with e.g. an offline %1 wallet, or a PSBT-compatible "
210 "hardware wallet.")
211 .arg(PACKAGE_NAME));
212 }
213 }
214}
215
217 QSettings settings;
218 settings.setValue("fFeeSectionMinimized", fFeeMinimized);
219 settings.setValue("nFeeRadio", ui->groupFee->checkedId());
220 settings.setValue("nTransactionFee",
221 qint64(ui->customFee->value() / SATOSHI));
222
223 delete ui;
224}
225
226bool SendCoinsDialog::PrepareSendText(QString &question_string,
227 QString &informative_text,
228 QString &detailed_text) {
229 QList<SendCoinsRecipient> recipients;
230 bool valid = true;
231
232 for (int i = 0; i < ui->entries->count(); ++i) {
233 SendCoinsEntry *entry =
234 qobject_cast<SendCoinsEntry *>(ui->entries->itemAt(i)->widget());
235 if (entry) {
236 if (entry->validate(model->node())) {
237 recipients.append(entry->getValue());
238 } else if (valid) {
239 ui->scrollArea->ensureWidgetVisible(entry);
240 valid = false;
241 }
242 }
243 }
244
245 if (!valid || recipients.isEmpty()) {
246 return false;
247 }
248
249 fNewRecipientAllowed = false;
251 if (!ctx.isValid()) {
252 // Unlock wallet was cancelled
254 return false;
255 }
256
257 // prepare transaction for getting txFee earlier
259 std::make_unique<WalletModelTransaction>(recipients);
260 WalletModel::SendCoinsReturn prepareStatus;
261
263
264 prepareStatus =
266
267 // process prepareStatus and on error generate message shown to user
268 processSendCoinsReturn(prepareStatus,
271 m_current_transaction->getTransactionFee()));
272
273 if (prepareStatus.status != WalletModel::OK) {
275 return false;
276 }
277
278 Amount txFee = m_current_transaction->getTransactionFee();
279 QStringList formatted;
280 for (const SendCoinsRecipient &rcp :
281 m_current_transaction->getRecipients()) {
282 // generate amount string with wallet name in case of multiwallet
283 QString amount = BitcoinUnits::formatWithUnit(
284 model->getOptionsModel()->getDisplayUnit(), rcp.amount);
285 if (model->isMultiwallet()) {
286 amount.append(
287 tr(" from wallet '%1'")
289 }
290 // generate address string
291 QString address = rcp.address;
292
293 QString recipientElement;
294
295#ifdef ENABLE_BIP70
296 // normal payment
297 if (!rcp.paymentRequest.IsInitialized())
298#endif
299 {
300 if (rcp.label.length() > 0) {
301 // label with address
302 recipientElement.append(
303 tr("%1 to '%2'")
304 .arg(amount, GUIUtil::HtmlEscape(rcp.label)));
305 recipientElement.append(QString(" (%1)").arg(address));
306 } else {
307 // just address
308 recipientElement.append(tr("%1 to %2").arg(amount, address));
309 }
310 }
311#ifdef ENABLE_BIP70
312 // authenticated payment request
313 else if (!rcp.authenticatedMerchant.isEmpty()) {
314 recipientElement.append(
315 tr("%1 to '%2'").arg(amount, rcp.authenticatedMerchant));
316 } else {
317 // unauthenticated payment request
318 recipientElement.append(tr("%1 to %2").arg(amount, address));
319 }
320#endif
321
322 formatted.append(recipientElement);
323 }
324
326 question_string.append(tr("Do you want to draft this transaction?"));
327 } else {
328 question_string.append(tr("Are you sure you want to send?"));
329 }
330
331 question_string.append("<br /><span style='font-size:10pt;'>");
333 question_string.append(
334 tr("Please, review your transaction proposal. This will produce a "
335 "Partially Signed Bitcoin Transaction (PSBT) which you can save "
336 "or copy and then sign with e.g. an offline %1 wallet, or a "
337 "PSBT-compatible hardware wallet.")
338 .arg(PACKAGE_NAME));
339 } else {
340 question_string.append(tr("Please, review your transaction."));
341 }
342 question_string.append("</span>%1");
343
344 if (txFee > Amount::zero()) {
345 // append fee string if a fee is required
346 question_string.append("<hr /><b>");
347 question_string.append(tr("Transaction fee"));
348 question_string.append("</b>");
349
350 // append transaction size
351 question_string.append(
352 " (" +
353 QString::number(
354 (double)m_current_transaction->getTransactionSize() / 1000) +
355 " kB): ");
356
357 // append transaction fee value
358 question_string.append(
359 "<span style='color:#aa0000; font-weight:bold;'>");
360 question_string.append(BitcoinUnits::formatHtmlWithUnit(
361 model->getOptionsModel()->getDisplayUnit(), txFee));
362 question_string.append("</span><br />");
363 }
364
365 // add total amount in all subdivision units
366 question_string.append("<hr />");
367 Amount totalAmount =
368 m_current_transaction->getTotalTransactionAmount() + txFee;
369 QStringList alternativeUnits;
371 if (u != model->getOptionsModel()->getDisplayUnit()) {
372 alternativeUnits.append(
373 BitcoinUnits::formatHtmlWithUnit(u, totalAmount));
374 }
375 }
376 question_string.append(
377 QString("<b>%1</b>: <b>%2</b>")
378 .arg(tr("Total Amount"))
380 model->getOptionsModel()->getDisplayUnit(), totalAmount)));
381 question_string.append(
382 QString("<br /><span style='font-size:10pt; "
383 "font-weight:normal;'>(=%1)</span>")
384 .arg(alternativeUnits.join(" " + tr("or") + " ")));
385
386 if (formatted.size() > 1) {
387 question_string = question_string.arg("");
388 informative_text =
389 tr("To review recipient list click \"Show Details...\"");
390 detailed_text = formatted.join("\n\n");
391 } else {
392 question_string = question_string.arg("<br /><br />" + formatted.at(0));
393 }
394
395 return true;
396}
397
399 if (!model || !model->getOptionsModel()) {
400 return;
401 }
402
403 QString question_string, informative_text, detailed_text;
404 if (!PrepareSendText(question_string, informative_text, detailed_text)) {
405 return;
406 }
408
409 const QString confirmation = model->wallet().privateKeysDisabled()
410 ? tr("Confirm transaction proposal")
411 : tr("Confirm send coins");
412 const QString confirmButtonText = model->wallet().privateKeysDisabled()
413 ? tr("Create Unsigned")
414 : tr("Send");
415 auto confirmationDialog = new SendConfirmationDialog(
416 confirmation, question_string, informative_text, detailed_text,
417 SEND_CONFIRM_DELAY, confirmButtonText, this);
418 confirmationDialog->setAttribute(Qt::WA_DeleteOnClose);
419 // TODO: Replace QDialog::exec() with safer QDialog::show().
420 const auto retval =
421 static_cast<QMessageBox::StandardButton>(confirmationDialog->exec());
422
423 if (retval != QMessageBox::Yes) {
425 return;
426 }
427
428 bool send_failure = false;
433 bool complete = false;
434 const TransactionError err = model->wallet().fillPSBT(
435 SigHashType().withForkId(), false /* sign */,
436 true /* bip32derivs */, psbtx, complete);
437 assert(!complete);
439 // Serialize the PSBT
441 ssTx << psbtx;
442 GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str());
443 QMessageBox msgBox;
444 msgBox.setText("Unsigned Transaction");
445 msgBox.setInformativeText(
446 "The PSBT has been copied to the clipboard. You can also save it.");
447 msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard);
448 msgBox.setDefaultButton(QMessageBox::Discard);
449 switch (msgBox.exec()) {
450 case QMessageBox::Save: {
451 QString selectedFilter;
452 QString fileNameSuggestion = "";
453 bool first = true;
454 for (const SendCoinsRecipient &rcp :
455 m_current_transaction->getRecipients()) {
456 if (!first) {
457 fileNameSuggestion.append(" - ");
458 }
459 QString labelOrAddress =
460 rcp.label.isEmpty() ? rcp.address : rcp.label;
461 QString amount = BitcoinUnits::formatWithUnit(
462 model->getOptionsModel()->getDisplayUnit(), rcp.amount);
463 fileNameSuggestion.append(labelOrAddress + "-" + amount);
464 first = false;
465 }
466 fileNameSuggestion.append(".psbt");
467 QString filename = GUIUtil::getSaveFileName(
468 this, tr("Save Transaction Data"), fileNameSuggestion,
469 tr("Partially Signed Transaction (Binary) (*.psbt)"),
470 &selectedFilter);
471 if (filename.isEmpty()) {
472 return;
473 }
474 std::ofstream out{filename.toLocal8Bit().data(),
475 std::ofstream::out | std::ofstream::binary};
476 out << ssTx.str();
477 out.close();
478 Q_EMIT message(tr("PSBT saved"), "PSBT saved to disk",
480 break;
481 }
482 case QMessageBox::Discard:
483 break;
484 default:
485 assert(false);
486 }
487 } else {
488 // now send the prepared transaction
491 // process sendStatus and on error generate message shown to user
492 processSendCoinsReturn(sendStatus);
493
494 if (sendStatus.status == WalletModel::OK) {
495 Q_EMIT coinsSent(m_current_transaction->getWtx()->GetId());
496 } else {
497 send_failure = true;
498 }
499 }
500 if (!send_failure) {
501 accept();
502 m_coin_control->UnSelectAll();
504 }
506 m_current_transaction.reset();
507}
508
510 m_current_transaction.reset();
511
512 // Clear coin control settings
513 m_coin_control->UnSelectAll();
514 ui->checkBoxCoinControlChange->setChecked(false);
515 ui->lineEditCoinControlChange->clear();
517
518 // Remove entries until only one left
519 while (ui->entries->count()) {
520 ui->entries->takeAt(0)->widget()->deleteLater();
521 }
522 addEntry();
523
525}
526
528 clear();
529}
530
532 clear();
533}
534
537 ui->entries->addWidget(entry);
538 connect(entry, &SendCoinsEntry::removeEntry, this,
540 connect(entry, &SendCoinsEntry::useAvailableBalance, this,
542 connect(entry, &SendCoinsEntry::payAmountChanged, this,
546
547 // Focus the field, so that entry can start immediately
548 entry->clear();
549 entry->setFocus();
550 ui->scrollAreaWidgetContents->resize(
551 ui->scrollAreaWidgetContents->sizeHint());
552 qApp->processEvents();
553 QScrollBar *bar = ui->scrollArea->verticalScrollBar();
554 if (bar) {
555 bar->setSliderPosition(bar->maximum());
556 }
557
559 return entry;
560}
561
563 setupTabChain(nullptr);
565}
566
568 entry->hide();
569
570 // If the last entry is about to be removed add an empty one
571 if (ui->entries->count() == 1) {
572 addEntry();
573 }
574
575 entry->deleteLater();
576
578}
579
580QWidget *SendCoinsDialog::setupTabChain(QWidget *prev) {
581 for (int i = 0; i < ui->entries->count(); ++i) {
582 SendCoinsEntry *entry =
583 qobject_cast<SendCoinsEntry *>(ui->entries->itemAt(i)->widget());
584 if (entry) {
585 prev = entry->setupTabChain(prev);
586 }
587 }
588 QWidget::setTabOrder(prev, ui->sendButton);
589 QWidget::setTabOrder(ui->sendButton, ui->clearButton);
590 QWidget::setTabOrder(ui->clearButton, ui->addButton);
591 return ui->addButton;
592}
593
594void SendCoinsDialog::setAddress(const QString &address) {
595 SendCoinsEntry *entry = nullptr;
596 // Replace the first entry if it is still unused
597 if (ui->entries->count() == 1) {
598 SendCoinsEntry *first =
599 qobject_cast<SendCoinsEntry *>(ui->entries->itemAt(0)->widget());
600 if (first->isClear()) {
601 entry = first;
602 }
603 }
604 if (!entry) {
605 entry = addEntry();
606 }
607
608 entry->setAddress(address);
609}
610
613 return;
614 }
615
616 SendCoinsEntry *entry = nullptr;
617 // Replace the first entry if it is still unused
618 if (ui->entries->count() == 1) {
619 SendCoinsEntry *first =
620 qobject_cast<SendCoinsEntry *>(ui->entries->itemAt(0)->widget());
621 if (first->isClear()) {
622 entry = first;
623 }
624 }
625 if (!entry) {
626 entry = addEntry();
627 }
628
629 entry->setValue(rv);
631}
632
634 // Just paste the entry, all pre-checks are done in paymentserver.cpp.
635 pasteEntry(rv);
636 return true;
637}
638
640 if (model && model->getOptionsModel()) {
641 Amount balance = balances.balance;
643 balance = balances.watch_only_balance;
644 ui->labelBalanceName->setText(tr("Watch-only balance:"));
645 }
646 ui->labelBalance->setText(BitcoinUnits::formatWithUnit(
648 }
649}
650
653 ui->customFee->setDisplayUnit(model->getOptionsModel()->getDisplayUnit());
655}
656
658 const WalletModel::SendCoinsReturn &sendCoinsReturn,
659 const QString &msgArg) {
660 QPair<QString, CClientUIInterface::MessageBoxFlags> msgParams;
661 // Default to a warning message, override if error message is needed
662 msgParams.second = CClientUIInterface::MSG_WARNING;
663
664 // This comment is specific to SendCoinsDialog usage of
665 // WalletModel::SendCoinsReturn.
666 // All status values are used only in WalletModel::prepareTransaction()
667 switch (sendCoinsReturn.status) {
669 msgParams.first =
670 tr("The recipient address is not valid. Please recheck.");
671 break;
673 msgParams.first = tr("The amount to pay must be larger than 0.");
674 break;
676 msgParams.first = tr("The amount exceeds your balance.");
677 break;
679 msgParams.first = tr("The total exceeds your balance when the %1 "
680 "transaction fee is included.")
681 .arg(msgArg);
682 break;
684 msgParams.first = tr("Duplicate address found: addresses should "
685 "only be used once each.");
686 break;
688 msgParams.first = tr("Transaction creation failed!");
689 msgParams.second = CClientUIInterface::MSG_ERROR;
690 break;
692 msgParams.first =
693 tr("A fee higher than %1 is considered an absurdly high fee.")
697 break;
699 msgParams.first = tr("Payment request expired.");
700 msgParams.second = CClientUIInterface::MSG_ERROR;
701 break;
702 // included to prevent a compiler warning.
703 case WalletModel::OK:
704 default:
705 return;
706 }
707
708 Q_EMIT message(tr("Send Coins"), msgParams.first, msgParams.second);
709}
710
712 ui->labelFeeMinimized->setVisible(fMinimize);
713 ui->buttonChooseFee->setVisible(fMinimize);
714 ui->buttonMinimizeFee->setVisible(!fMinimize);
715 ui->frameFeeSelection->setVisible(!fMinimize);
716 ui->horizontalLayoutSmartFee->setContentsMargins(0, (fMinimize ? 0 : 6), 0,
717 0);
718 fFeeMinimized = fMinimize;
719}
720
722 minimizeFeeSection(false);
723}
724
727 minimizeFeeSection(true);
728}
729
731 // Include watch-only for wallets without private key
732 m_coin_control->fAllowWatchOnly = model->wallet().privateKeysDisabled();
733
734 // Calculate available amount to send.
736 for (int i = 0; i < ui->entries->count(); ++i) {
737 SendCoinsEntry *e =
738 qobject_cast<SendCoinsEntry *>(ui->entries->itemAt(i)->widget());
739 if (e && !e->isHidden() && e != entry) {
740 amount -= e->getValue().amount;
741 }
742 }
743
744 if (amount > Amount::zero()) {
746 entry->setAmount(amount);
747 } else {
748 entry->setAmount(Amount::zero());
749 }
750}
751
753 ui->labelSmartFee->setEnabled(ui->radioSmartFee->isChecked());
754 ui->labelSmartFee2->setEnabled(ui->radioSmartFee->isChecked());
755 ui->labelFeeEstimation->setEnabled(ui->radioSmartFee->isChecked());
756 ui->labelCustomFeeWarning->setEnabled(ui->radioCustomFee->isChecked());
757 ui->labelCustomPerKilobyte->setEnabled(ui->radioCustomFee->isChecked());
758 ui->customFee->setEnabled(ui->radioCustomFee->isChecked());
759}
760
762 if (!model || !model->getOptionsModel()) {
763 return;
764 }
765
766 if (ui->radioSmartFee->isChecked()) {
767 ui->labelFeeMinimized->setText(ui->labelSmartFee->text());
768 } else {
769 ui->labelFeeMinimized->setText(
772 ui->customFee->value()) +
773 "/kB");
774 }
775}
776
778 if (ui->radioCustomFee->isChecked()) {
779 ctrl.m_feerate = CFeeRate(ui->customFee->value());
780 } else {
781 ctrl.m_feerate.reset();
782 }
783 // Include watch-only for wallets without private key
785}
786
788 const QDateTime &blockDate,
789 double nVerificationProgress,
790 SyncType synctype,
791 SynchronizationState sync_state) {
792 if (sync_state == SynchronizationState::POST_INIT) {
794 }
795}
796
798 if (!model || !model->getOptionsModel()) {
799 return;
800 }
801
803 // Explicitly use only fee estimation rate for smart fee labels
804 m_coin_control->m_feerate.reset();
806
807 ui->labelSmartFee->setText(
809 feeRate.GetFeePerK()) +
810 "/kB");
811 // not enough data => minfee
812 if (feeRate <= CFeeRate(Amount::zero())) {
813 // (Smart fee not initialized yet. This usually takes a few blocks...)
814 ui->labelSmartFee2->show();
815 ui->labelFeeEstimation->setText("");
816 } else {
817 ui->labelSmartFee2->hide();
818 ui->labelFeeEstimation->setText(
819 tr("Estimated to begin confirmation by next block."));
820 }
821
823}
824
825// Coin Control: copy label "Quantity" to clipboard
827 GUIUtil::setClipboard(ui->labelCoinControlQuantity->text());
828}
829
830// Coin Control: copy label "Amount" to clipboard
832 GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left(
833 ui->labelCoinControlAmount->text().indexOf(" ")));
834}
835
836// Coin Control: copy label "Fee" to clipboard
839 ui->labelCoinControlFee->text()
840 .left(ui->labelCoinControlFee->text().indexOf(" "))
841 .replace(ASYMP_UTF8, ""));
842}
843
844// Coin Control: copy label "After fee" to clipboard
847 ui->labelCoinControlAfterFee->text()
848 .left(ui->labelCoinControlAfterFee->text().indexOf(" "))
849 .replace(ASYMP_UTF8, ""));
850}
851
852// Coin Control: copy label "Bytes" to clipboard
855 ui->labelCoinControlBytes->text().replace(ASYMP_UTF8, ""));
856}
857
858// Coin Control: copy label "Dust" to clipboard
860 GUIUtil::setClipboard(ui->labelCoinControlLowOutput->text());
861}
862
863// Coin Control: copy label "Change" to clipboard
866 ui->labelCoinControlChange->text()
867 .left(ui->labelCoinControlChange->text().indexOf(" "))
868 .replace(ASYMP_UTF8, ""));
869}
870
871// Coin Control: settings menu - coin control enabled/disabled by user
873 ui->frameCoinControl->setVisible(checked);
874
875 // coin control features disabled
876 if (!checked && model) {
877 m_coin_control->SetNull();
878 }
879
881}
882
883// Coin Control: button inputs -> show actual coin control dialog
886 connect(dlg, &QDialog::finished, this,
889}
890
891// Coin Control: checkbox custom change address
893 if (state == Qt::Unchecked) {
894 m_coin_control->destChange = CNoDestination();
895 ui->labelCoinControlChangeLabel->clear();
896 } else {
897 // use this to re-validate an already entered address
898 coinControlChangeEdited(ui->lineEditCoinControlChange->text());
899 }
900
901 ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked));
902}
903
904// Coin Control: custom change address changed
906 if (model && model->getAddressTableModel()) {
907 // Default to no change address until verified
908 m_coin_control->destChange = CNoDestination();
909 ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:red;}");
910
911 const CTxDestination dest =
912 DecodeDestination(text.toStdString(), model->getChainParams());
913
914 if (text.isEmpty()) {
915 // Nothing entered
916 ui->labelCoinControlChangeLabel->setText("");
917 } else if (!IsValidDestination(dest)) {
918 // Invalid address
919 ui->labelCoinControlChangeLabel->setText(
920 tr("Warning: Invalid Bitcoin address"));
921 } else {
922 // Valid address
923 if (!model->wallet().isSpendable(dest)) {
924 ui->labelCoinControlChangeLabel->setText(
925 tr("Warning: Unknown change address"));
926
927 // confirmation dialog
928 QMessageBox::StandardButton btnRetVal = QMessageBox::question(
929 this, tr("Confirm custom change address"),
930 tr("The address you selected for change is not part of "
931 "this wallet. Any or all funds in your wallet may be "
932 "sent to this address. Are you sure?"),
933 QMessageBox::Yes | QMessageBox::Cancel,
934 QMessageBox::Cancel);
935
936 if (btnRetVal == QMessageBox::Yes) {
937 m_coin_control->destChange = dest;
938 } else {
939 ui->lineEditCoinControlChange->setText("");
940 ui->labelCoinControlChangeLabel->setStyleSheet(
941 "QLabel{color:black;}");
942 ui->labelCoinControlChangeLabel->setText("");
943 }
944 } else {
945 // Known change address
946 ui->labelCoinControlChangeLabel->setStyleSheet(
947 "QLabel{color:black;}");
948
949 // Query label
950 QString associatedLabel =
952 if (!associatedLabel.isEmpty()) {
953 ui->labelCoinControlChangeLabel->setText(associatedLabel);
954 } else {
955 ui->labelCoinControlChangeLabel->setText(tr("(no label)"));
956 }
957
958 m_coin_control->destChange = dest;
959 }
960 }
961 }
962}
963
964// Coin Control: update labels
966 if (!model || !model->getOptionsModel()) {
967 return;
968 }
969
971
972 // set pay amounts
975 for (int i = 0; i < ui->entries->count(); ++i) {
976 SendCoinsEntry *entry =
977 qobject_cast<SendCoinsEntry *>(ui->entries->itemAt(i)->widget());
978 if (entry && !entry->isHidden()) {
979 SendCoinsRecipient rcp = entry->getValue();
981 if (rcp.fSubtractFeeFromAmount) {
983 }
984 }
985 }
986
987 if (m_coin_control->HasSelected()) {
988 // actual coin control calculation
990
991 // show coin control stats
992 ui->labelCoinControlAutomaticallySelected->hide();
993 ui->widgetCoinControl->show();
994 } else {
995 // hide coin control stats
996 ui->labelCoinControlAutomaticallySelected->show();
997 ui->widgetCoinControl->hide();
998 ui->labelCoinControlInsuffFunds->hide();
999 }
1000}
1001
1003 const QString &title, const QString &text, const QString &informative_text,
1004 const QString &detailed_text, int _secDelay,
1005 const QString &_confirmButtonText, QWidget *parent)
1006 : QMessageBox(parent), secDelay(_secDelay),
1007 confirmButtonText(_confirmButtonText) {
1008 setIcon(QMessageBox::Question);
1009 // On macOS, the window title is ignored (as required by the macOS
1010 // Guidelines).
1011 setWindowTitle(title);
1012 setText(text);
1013 setInformativeText(informative_text);
1014 setDetailedText(detailed_text);
1015 setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel);
1016 setDefaultButton(QMessageBox::Cancel);
1017 yesButton = button(QMessageBox::Yes);
1019 connect(&countDownTimer, &QTimer::timeout, this,
1021}
1022
1025 countDownTimer.start(1000);
1026 return QMessageBox::exec();
1027}
1028
1030 secDelay--;
1032
1033 if (secDelay <= 0) {
1034 countDownTimer.stop();
1035 }
1036}
1037
1039 if (secDelay > 0) {
1040 yesButton->setEnabled(false);
1041 yesButton->setText(confirmButtonText + " (" +
1042 QString::number(secDelay) + ")");
1043 } else {
1044 yesButton->setEnabled(true);
1045 yesButton->setText(confirmButtonText);
1046 }
1047}
static constexpr Amount SATOSHI
Definition: amount.h:143
secp256k1_context * ctx
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
std::string str() const
Definition: streams.h:195
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 bool isSpendable(const CTxDestination &dest)=0
Return whether wallet has private key.
virtual WalletBalances getBalances()=0
Get balances.
virtual TransactionError fillPSBT(SigHashType sighash_type, bool sign, bool bip32derivs, PartiallySignedTransaction &psbtx, bool &complete) const =0
Fill PSBT.
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
TransactionError
Definition: error.h:22
CTxDestination DecodeDestination(const std::string &addr, const CChainParams &params)
Definition: key_io.cpp:174
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
#define SEND_CONFIRM_DELAY
@ SER_NETWORK
Definition: serialize.h:154
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:19
static constexpr Amount zero() noexcept
Definition: amount.h:32
A version of CTransaction with the PSBT format.
Definition: psbt.h:334
Collection of wallet balances.
Definition: wallet.h:354
static int count
Definition: tests.c:31
std::string EncodeBase64(Span< const uint8_t > input)
assert(!tx.IsCoinBase())
SynchronizationState
Current sync state passed to tip changed callbacks.
Definition: validation.h:119
static const int PROTOCOL_VERSION
network protocol versioning
Definition: version.h:11
constexpr Amount DEFAULT_PAY_TX_FEE
-paytxfee default
Definition: wallet.h:98