Bitcoin Core 31.0.0
P2P Digital Currency
Loading...
Searching...
No Matches
sendcoinsdialog.cpp
Go to the documentation of this file.
1// Copyright (c) 2011-present 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#include <bitcoin-build-config.h> // IWYU pragma: keep
6
8#include <qt/forms/ui_sendcoinsdialog.h>
9
11#include <qt/bitcoinunits.h>
12#include <qt/clientmodel.h>
14#include <qt/guiutil.h>
15#include <qt/optionsmodel.h>
16#include <qt/platformstyle.h>
17#include <qt/sendcoinsentry.h>
18
19#include <chainparams.h>
20#include <interfaces/node.h>
21#include <key_io.h>
22#include <node/interface_ui.h>
23#include <node/types.h>
25#include <txmempool.h>
26#include <validation.h>
27#include <wallet/coincontrol.h>
28#include <wallet/fees.h>
29#include <wallet/wallet.h>
30
31#include <array>
32#include <chrono>
33#include <fstream>
34#include <memory>
35
36#include <QFontMetrics>
37#include <QScrollBar>
38#include <QSettings>
39#include <QTextDocument>
40
43
44static constexpr std::array confTargets{2, 4, 6, 12, 24, 48, 144, 504, 1008};
45int getConfTargetForIndex(int index) {
46 if (index+1 > static_cast<int>(confTargets.size())) {
47 return confTargets.back();
48 }
49 if (index < 0) {
50 return confTargets[0];
51 }
52 return confTargets[index];
53}
55 for (unsigned int i = 0; i < confTargets.size(); i++) {
56 if (confTargets[i] >= target) {
57 return i;
58 }
59 }
60 return confTargets.size() - 1;
61}
62
64 QDialog(parent, GUIUtil::dialog_flags),
66 m_coin_control(new CCoinControl),
67 platformStyle(_platformStyle)
68{
69 ui->setupUi(this);
70
71 if (!_platformStyle->getImagesOnButtons()) {
72 ui->addButton->setIcon(QIcon());
73 ui->clearButton->setIcon(QIcon());
74 ui->sendButton->setIcon(QIcon());
75 } else {
76 ui->addButton->setIcon(_platformStyle->SingleColorIcon(":/icons/add"));
77 ui->clearButton->setIcon(_platformStyle->SingleColorIcon(":/icons/remove"));
78 ui->sendButton->setIcon(_platformStyle->SingleColorIcon(":/icons/send"));
79 }
80
81 GUIUtil::setupAddressWidget(ui->lineEditCoinControlChange, this);
82
83 addEntry();
84
85 connect(ui->addButton, &QPushButton::clicked, this, &SendCoinsDialog::addEntry);
86 connect(ui->clearButton, &QPushButton::clicked, this, &SendCoinsDialog::clear);
87
88 // Coin Control
89 connect(ui->pushButtonCoinControl, &QPushButton::clicked, this, &SendCoinsDialog::coinControlButtonClicked);
90#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0))
91 connect(ui->checkBoxCoinControlChange, &QCheckBox::checkStateChanged, this, &SendCoinsDialog::coinControlChangeChecked);
92#else
93 connect(ui->checkBoxCoinControlChange, &QCheckBox::stateChanged, this, &SendCoinsDialog::coinControlChangeChecked);
94#endif
95 connect(ui->lineEditCoinControlChange, &QValidatedLineEdit::textEdited, this, &SendCoinsDialog::coinControlChangeEdited);
96
97 // Coin Control: clipboard actions
98 QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this);
99 QAction *clipboardAmountAction = new QAction(tr("Copy amount"), this);
100 QAction *clipboardFeeAction = new QAction(tr("Copy fee"), this);
101 QAction *clipboardAfterFeeAction = new QAction(tr("Copy after fee"), this);
102 QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this);
103 QAction *clipboardChangeAction = new QAction(tr("Copy change"), this);
105 connect(clipboardAmountAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardAmount);
106 connect(clipboardFeeAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardFee);
108 connect(clipboardBytesAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardBytes);
109 connect(clipboardChangeAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardChange);
110 ui->labelCoinControlQuantity->addAction(clipboardQuantityAction);
111 ui->labelCoinControlAmount->addAction(clipboardAmountAction);
112 ui->labelCoinControlFee->addAction(clipboardFeeAction);
113 ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction);
114 ui->labelCoinControlBytes->addAction(clipboardBytesAction);
115 ui->labelCoinControlChange->addAction(clipboardChangeAction);
116
117 // init transaction fee section
119 if (!settings.contains("fFeeSectionMinimized"))
120 settings.setValue("fFeeSectionMinimized", true);
121 if (!settings.contains("nFeeRadio") && settings.contains("nTransactionFee") && settings.value("nTransactionFee").toLongLong() > 0) // compatibility
122 settings.setValue("nFeeRadio", 1); // custom
123 if (!settings.contains("nFeeRadio"))
124 settings.setValue("nFeeRadio", 0); // recommended
125 if (!settings.contains("nSmartFeeSliderPosition"))
126 settings.setValue("nSmartFeeSliderPosition", 0);
127 ui->groupFee->setId(ui->radioSmartFee, 0);
128 ui->groupFee->setId(ui->radioCustomFee, 1);
129 ui->groupFee->button((int)std::max(0, std::min(1, settings.value("nFeeRadio").toInt())))->setChecked(true);
130 ui->customFee->SetAllowEmpty(false);
131 ui->customFee->setValue(settings.value("nTransactionFee").toLongLong());
132 minimizeFeeSection(settings.value("fFeeSectionMinimized").toBool());
133
134 GUIUtil::ExceptionSafeConnect(ui->sendButton, &QPushButton::clicked, this, &SendCoinsDialog::sendButtonClicked);
135}
136
145
147{
148 this->model = _model;
149
150 if(_model && _model->getOptionsModel())
151 {
152 for(int i = 0; i < ui->entries->count(); ++i)
153 {
154 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
155 if(entry)
156 {
157 entry->setModel(_model);
158 }
159 }
160
162 connect(_model->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &SendCoinsDialog::refreshBalance);
164
165 // Coin Control
168 ui->frameCoinControl->setVisible(_model->getOptionsModel()->getCoinControlFeatures());
170
171 // fee section
172 for (const int n : confTargets) {
173 ui->confTargetSelector->addItem(tr("%1 (%2 blocks)").arg(GUIUtil::formatNiceTimeOffset(n*Params().GetConsensus().nPowTargetSpacing)).arg(n));
174 }
175 connect(ui->confTargetSelector, qOverload<int>(&QComboBox::currentIndexChanged), this, &SendCoinsDialog::updateSmartFeeLabel);
176 connect(ui->confTargetSelector, qOverload<int>(&QComboBox::currentIndexChanged), this, &SendCoinsDialog::coinControlUpdateLabels);
177
178 connect(ui->groupFee, &QButtonGroup::idClicked, this, &SendCoinsDialog::updateFeeSectionControls);
179 connect(ui->groupFee, &QButtonGroup::idClicked, this, &SendCoinsDialog::coinControlUpdateLabels);
180
182#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0))
183 connect(ui->optInRBF, &QCheckBox::checkStateChanged, this, &SendCoinsDialog::updateSmartFeeLabel);
184 connect(ui->optInRBF, &QCheckBox::checkStateChanged, this, &SendCoinsDialog::coinControlUpdateLabels);
185#else
186 connect(ui->optInRBF, &QCheckBox::stateChanged, this, &SendCoinsDialog::updateSmartFeeLabel);
187 connect(ui->optInRBF, &QCheckBox::stateChanged, this, &SendCoinsDialog::coinControlUpdateLabels);
188#endif
190 ui->customFee->SetMinValue(requiredFee);
191 if (ui->customFee->value() < requiredFee) {
192 ui->customFee->setValue(requiredFee);
193 }
194 ui->customFee->setSingleStep(requiredFee);
197
198 // set default rbf checkbox state
199 ui->optInRBF->setCheckState(Qt::Checked);
200
201 if (model->wallet().hasExternalSigner()) {
202 //: "device" usually means a hardware wallet.
203 ui->sendButton->setText(tr("Sign on device"));
204 if (model->getOptionsModel()->hasSigner()) {
205 ui->sendButton->setEnabled(true);
206 ui->sendButton->setToolTip(tr("Connect your hardware wallet first."));
207 } else {
208 ui->sendButton->setEnabled(false);
209 //: "External signer" means using devices such as hardware wallets.
210 ui->sendButton->setToolTip(tr("Set external signer script path in Options -> Wallet"));
211 }
212 } else if (model->wallet().privateKeysDisabled()) {
213 ui->sendButton->setText(tr("Cr&eate Unsigned"));
214 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(CLIENT_NAME));
215 }
216
217 // set the smartfee-sliders default value (wallets default conf.target or last stored value)
219 if (settings.value("nSmartFeeSliderPosition").toInt() != 0) {
220 // migrate nSmartFeeSliderPosition to nConfTarget
221 // nConfTarget is available since 0.15 (replaced nSmartFeeSliderPosition)
222 int nConfirmTarget = 25 - settings.value("nSmartFeeSliderPosition").toInt(); // 25 == old slider range
223 settings.setValue("nConfTarget", nConfirmTarget);
224 settings.remove("nSmartFeeSliderPosition");
225 }
226 if (settings.value("nConfTarget").toInt() == 0)
227 ui->confTargetSelector->setCurrentIndex(getIndexForConfTarget(model->wallet().getConfirmTarget()));
228 else
229 ui->confTargetSelector->setCurrentIndex(getIndexForConfTarget(settings.value("nConfTarget").toInt()));
230 }
231}
232
234{
236 settings.setValue("fFeeSectionMinimized", fFeeMinimized);
237 settings.setValue("nFeeRadio", ui->groupFee->checkedId());
238 settings.setValue("nConfTarget", getConfTargetForIndex(ui->confTargetSelector->currentIndex()));
239 settings.setValue("nTransactionFee", (qint64)ui->customFee->value());
240
241 delete ui;
242}
243
245{
246 QList<SendCoinsRecipient> recipients;
247 bool valid = true;
248
249 for(int i = 0; i < ui->entries->count(); ++i)
250 {
251 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
252 if(entry)
253 {
254 if(entry->validate(model->node()))
255 {
256 recipients.append(entry->getValue());
257 }
258 else if (valid)
259 {
260 ui->scrollArea->ensureWidgetVisible(entry);
261 valid = false;
262 }
263 }
264 }
265
266 if(!valid || recipients.isEmpty())
267 {
268 return false;
269 }
270
271 fNewRecipientAllowed = false;
273 if(!ctx.isValid())
274 {
275 // Unlock wallet was cancelled
277 return false;
278 }
279
280 // prepare transaction for getting txFee earlier
281 m_current_transaction = std::make_unique<WalletModelTransaction>(recipients);
283
285
287 coin_control.m_allow_other_inputs = !coin_control.HasSelected(); // future, could introduce a checkbox to customize this value.
289
290 // process prepareStatus and on error generate message shown to user
293
294 if(prepareStatus.status != WalletModel::OK) {
296 return false;
297 }
298
299 CAmount txFee = m_current_transaction->getTransactionFee();
301 for (const SendCoinsRecipient &rcp : m_current_transaction->getRecipients())
302 {
303 // generate amount string with wallet name in case of multiwallet
305 if (model->isMultiwallet()) {
306 amount = tr("%1 from wallet '%2'").arg(amount, GUIUtil::HtmlEscape(model->getWalletName()));
307 }
308
309 // generate address string
310 QString address = rcp.address;
311
313
314 {
315 if(rcp.label.length() > 0) // label with address
316 {
317 recipientElement.append(tr("%1 to '%2'").arg(amount, GUIUtil::HtmlEscape(rcp.label)));
318 recipientElement.append(QString(" (%1)").arg(address));
319 }
320 else // just address
321 {
322 recipientElement.append(tr("%1 to %2").arg(amount, address));
323 }
324 }
326 }
327
328 /*: Message displayed when attempting to create a transaction. Cautionary text to prompt the user to verify
329 that the displayed transaction details represent the transaction the user intends to create. */
330 question_string.append(tr("Do you want to create this transaction?"));
331 question_string.append("<br /><span style='font-size:10pt;'>");
333 /*: Text to inform a user attempting to create a transaction of their current options. At this stage,
334 a user can only create a PSBT. This string is displayed when private keys are disabled and an external
335 signer is not available. */
336 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(CLIENT_NAME));
338 /*: Text to inform a user attempting to create a transaction of their current options. At this stage,
339 a user can send their transaction or create a PSBT. This string is displayed when both private keys
340 and PSBT controls are enabled. */
341 question_string.append(tr("Please, review your transaction. You can create and send this transaction or create 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(CLIENT_NAME));
342 } else {
343 /*: Text to prompt a user to review the details of the transaction they are attempting to send. */
344 question_string.append(tr("Please, review your transaction."));
345 }
346 question_string.append("</span>%1");
347
348 if(txFee > 0)
349 {
350 // append fee string if a fee is required
351 question_string.append("<hr /><b>");
352 question_string.append(tr("Transaction fee"));
353 question_string.append("</b>");
354
355 // append transaction size
356 //: When reviewing a newly created PSBT (via Send flow), the transaction fee is shown, with "virtual size" of the transaction displayed for context
357 question_string.append(" (" + tr("%1 kvB", "PSBT transaction creation").arg((double)m_current_transaction->getTransactionSize() / 1000, 0, 'g', 3) + "): ");
358
359 // append transaction fee value
360 question_string.append("<span style='color:#aa0000; font-weight:bold;'>");
362 question_string.append("</span><br />");
363
364 // append RBF message according to transaction's signalling
365 question_string.append("<span style='font-size:10pt; font-weight:normal;'>");
366 if (ui->optInRBF->isChecked()) {
367 question_string.append(tr("You can increase the fee later (signals Replace-By-Fee, BIP-125)."));
368 } else {
369 question_string.append(tr("Not signalling Replace-By-Fee, BIP-125."));
370 }
371 question_string.append("</span>");
372 }
373
374 // add total amount in all subdivision units
375 question_string.append("<hr />");
376 CAmount totalAmount = m_current_transaction->getTotalTransactionAmount() + txFee;
378 for (const BitcoinUnit u : BitcoinUnits::availableUnits()) {
381 }
382 question_string.append(QString("<b>%1</b>: <b>%2</b>").arg(tr("Total Amount"))
384 question_string.append(QString("<br /><span style='font-size:10pt; font-weight:normal;'>(=%1)</span>")
385 .arg(alternativeUnits.join(" " + tr("or") + " ")));
386
387 if (formatted.size() > 1) {
389 informative_text = 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{
400 // Serialize the PSBT
402 ssTx << psbtx;
404 QMessageBox msgBox(this);
405 //: Caption of "PSBT has been copied" messagebox
406 msgBox.setText(tr("Unsigned Transaction", "PSBT copied"));
407 msgBox.setInformativeText(tr("The PSBT has been copied to the clipboard. You can also save it."));
408 msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard);
409 msgBox.setDefaultButton(QMessageBox::Discard);
410 msgBox.setObjectName("psbt_copied_message");
411 switch (msgBox.exec()) {
412 case QMessageBox::Save: {
415 bool first = true;
416 for (const SendCoinsRecipient &rcp : m_current_transaction->getRecipients()) {
417 if (!first) {
418 fileNameSuggestion.append(" - ");
419 }
420 QString labelOrAddress = rcp.label.isEmpty() ? rcp.address : rcp.label;
422 fileNameSuggestion.append(labelOrAddress + "-" + amount);
423 first = false;
424 }
425 fileNameSuggestion.append(".psbt");
426 QString filename = GUIUtil::getSaveFileName(this,
427 tr("Save Transaction Data"), fileNameSuggestion,
428 //: Expanded name of the binary PSBT file format. See: BIP 174.
429 tr("Partially Signed Transaction (Binary)") + QLatin1String(" (*.psbt)"), &selectedFilter);
430 if (filename.isEmpty()) {
431 return;
432 }
433 std::ofstream out{filename.toLocal8Bit().data(), std::ofstream::out | std::ofstream::binary};
434 out << ssTx.str();
435 out.close();
436 //: Popup message when a PSBT has been saved to a file
437 Q_EMIT message(tr("PSBT saved"), tr("PSBT saved to disk"), CClientUIInterface::MSG_INFORMATION);
438 break;
439 }
440 case QMessageBox::Discard:
441 break;
442 default:
443 assert(false);
444 } // msgBox.exec()
445}
446
448 std::optional<PSBTError> err;
449 try {
450 err = model->wallet().fillPSBT(std::nullopt, /*sign=*/true, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete);
451 } catch (const std::runtime_error& e) {
452 QMessageBox::critical(nullptr, tr("Sign failed"), e.what());
453 return false;
454 }
455 if (err == PSBTError::EXTERNAL_SIGNER_NOT_FOUND) {
456 //: "External signer" means using devices such as hardware wallets.
457 const QString msg = tr("External signer not found");
458 QMessageBox::critical(nullptr, msg, msg);
459 return false;
460 }
461 if (err == PSBTError::EXTERNAL_SIGNER_FAILED) {
462 //: "External signer" means using devices such as hardware wallets.
463 const QString msg = tr("External signer failure");
464 QMessageBox::critical(nullptr, msg, msg);
465 return false;
466 }
467 if (err) {
468 qWarning() << "Failed to sign PSBT";
470 return false;
471 }
472 // fillPSBT does not always properly finalize
473 complete = FinalizeAndExtractPSBT(psbtx, mtx);
474 return true;
475}
476
478{
479 if(!model || !model->getOptionsModel())
480 return;
481
485
486 const QString confirmation = tr("Confirm send coins");
490 confirmationDialog->setAttribute(Qt::WA_DeleteOnClose);
491 // TODO: Replace QDialog::exec() with safer QDialog::show().
492 const auto retval = static_cast<QMessageBox::StandardButton>(confirmationDialog->exec());
493
494 if(retval != QMessageBox::Yes && retval != QMessageBox::Save)
495 {
497 return;
498 }
499
500 bool send_failure = false;
501 if (retval == QMessageBox::Save) {
502 // "Create Unsigned" clicked
505 bool complete = false;
506 // Fill without signing
507 const auto err{model->wallet().fillPSBT(std::nullopt, /*sign=*/false, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete)};
508 assert(!complete);
509 assert(!err);
510
511 // Copy PSBT to clipboard and offer to save
513 } else {
514 // "Send" clicked
516 bool broadcast = true;
517 if (model->wallet().hasExternalSigner()) {
520 bool complete = false;
521 // Always fill without signing first. This prevents an external signer
522 // from being called prematurely and is not expensive.
523 const auto err{model->wallet().fillPSBT(std::nullopt, /*sign=*/false, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete)};
524 assert(!complete);
525 assert(!err);
527 // Don't broadcast when user rejects it on the device or there's a failure:
528 broadcast = complete && !send_failure;
529 if (!send_failure) {
530 // A transaction signed with an external signer is not always complete,
531 // e.g. in a multisig wallet.
532 if (complete) {
533 // Prepare transaction for broadcast transaction if complete
535 m_current_transaction->setWtx(tx);
536 } else {
538 }
539 }
540 }
541
542 // Broadcast the transaction, unless an external signer was used and it
543 // failed, or more signatures are needed.
544 if (broadcast) {
545 // now send the prepared transaction
547 Q_EMIT coinsSent(m_current_transaction->getWtx()->GetHash());
548 }
549 }
550 if (!send_failure) {
551 accept();
552 m_coin_control->UnSelectAll();
554 }
556 m_current_transaction.reset();
557}
558
560{
561 m_current_transaction.reset();
562
563 // Clear coin control settings
564 m_coin_control->UnSelectAll();
565 ui->checkBoxCoinControlChange->setChecked(false);
566 ui->lineEditCoinControlChange->clear();
568
569 // Remove entries until only one left
570 while(ui->entries->count())
571 {
572 ui->entries->takeAt(0)->widget()->deleteLater();
573 }
574 addEntry();
575
577}
578
580{
581 clear();
582}
583
585{
586 clear();
587}
588
590{
591 SendCoinsEntry *entry = new SendCoinsEntry(platformStyle, this);
592 entry->setModel(model);
593 ui->entries->addWidget(entry);
598
599 // Focus the field, so that entry can start immediately
600 entry->clear();
601 entry->setFocus();
602 ui->scrollAreaWidgetContents->resize(ui->scrollAreaWidgetContents->sizeHint());
603
604 // Scroll to the newly added entry on a QueuedConnection because Qt doesn't
605 // adjust the scroll area and scrollbar immediately when the widget is added.
606 // Invoking on a DirectConnection will only scroll to the second-to-last entry.
607 QMetaObject::invokeMethod(ui->scrollArea, [this] {
608 if (ui->scrollArea->verticalScrollBar()) {
609 ui->scrollArea->verticalScrollBar()->setValue(ui->scrollArea->verticalScrollBar()->maximum());
610 }
611 }, Qt::QueuedConnection);
612
613 updateTabsAndLabels();
614 return entry;
615}
616
622
624{
625 entry->hide();
626
627 // If the last entry is about to be removed add an empty one
628 if (ui->entries->count() == 1)
629 addEntry();
630
631 entry->deleteLater();
632
634}
635
637{
638 for(int i = 0; i < ui->entries->count(); ++i)
639 {
640 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
641 if(entry)
642 {
643 prev = entry->setupTabChain(prev);
644 }
645 }
646 QWidget::setTabOrder(prev, ui->sendButton);
647 QWidget::setTabOrder(ui->sendButton, ui->clearButton);
648 QWidget::setTabOrder(ui->clearButton, ui->addButton);
649 return ui->addButton;
650}
651
653{
654 SendCoinsEntry *entry = nullptr;
655 // Replace the first entry if it is still unused
656 if(ui->entries->count() == 1)
657 {
658 SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
659 if(first->isClear())
660 {
661 entry = first;
662 }
663 }
664 if(!entry)
665 {
666 entry = addEntry();
667 }
668
669 entry->setAddress(address);
670}
671
673{
675 return;
676
677 SendCoinsEntry *entry = nullptr;
678 // Replace the first entry if it is still unused
679 if(ui->entries->count() == 1)
680 {
681 SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
682 if(first->isClear())
683 {
684 entry = first;
685 }
686 }
687 if(!entry)
688 {
689 entry = addEntry();
690 }
691
692 entry->setValue(rv);
694}
695
697{
698 // Just paste the entry, all pre-checks
699 // are done in paymentserver.cpp.
700 pasteEntry(rv);
701 return true;
702}
703
705{
706 if(model && model->getOptionsModel())
707 {
708 CAmount balance = balances.balance;
709 if (model->wallet().hasExternalSigner()) {
710 ui->labelBalanceName->setText(tr("External balance:"));
711 }
712 ui->labelBalance->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), balance));
713 }
714}
715
717{
719 ui->customFee->setDisplayUnit(model->getOptionsModel()->getDisplayUnit());
721}
722
724{
726 // Default to a warning message, override if error message is needed
728
729 // This comment is specific to SendCoinsDialog usage of WalletModel::SendCoinsReturn.
730 // All status values are used only in WalletModel::prepareTransaction()
731 switch(sendCoinsReturn.status)
732 {
734 msgParams.first = tr("The recipient address is not valid. Please recheck.");
735 break;
737 msgParams.first = tr("The amount to pay must be larger than 0.");
738 break;
740 msgParams.first = tr("The amount exceeds your balance.");
741 break;
743 msgParams.first = tr("Duplicate address found: addresses should only be used once each.");
744 break;
746 msgParams.first = tr("Transaction creation failed!");
748 break;
750 msgParams.first = tr("A fee higher than %1 is considered an absurdly high fee.").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), model->wallet().getDefaultMaxTxFee()));
751 break;
752 // included to prevent a compiler warning.
753 case WalletModel::OK:
754 default:
755 return;
756 }
757
758 Q_EMIT message(tr("Send Coins"), msgParams.first, msgParams.second);
759}
760
762{
763 ui->labelFeeMinimized->setVisible(fMinimize);
764 ui->buttonChooseFee ->setVisible(fMinimize);
765 ui->buttonMinimizeFee->setVisible(!fMinimize);
766 ui->frameFeeSelection->setVisible(!fMinimize);
767 ui->horizontalLayoutSmartFee->setContentsMargins(0, (fMinimize ? 0 : 6), 0, 0);
769}
770
775
781
783{
784 // Same behavior as send: if we have selected coins, only obtain their available balance.
785 // Copy to avoid modifying the member's data.
788
789 // Calculate available amount to send.
791 for (int i = 0; i < ui->entries->count(); ++i) {
792 SendCoinsEntry* e = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
793 if (e && !e->isHidden() && e != entry) {
794 amount -= e->getValue().amount;
795 }
796 }
797
798 if (amount > 0) {
800 entry->setAmount(amount);
801 } else {
802 entry->setAmount(0);
803 }
804}
805
807{
808 ui->confTargetSelector ->setEnabled(ui->radioSmartFee->isChecked());
809 ui->labelSmartFee ->setEnabled(ui->radioSmartFee->isChecked());
810 ui->labelSmartFee2 ->setEnabled(ui->radioSmartFee->isChecked());
811 ui->labelSmartFee3 ->setEnabled(ui->radioSmartFee->isChecked());
812 ui->labelFeeEstimation ->setEnabled(ui->radioSmartFee->isChecked());
813 ui->labelCustomFeeWarning ->setEnabled(ui->radioCustomFee->isChecked());
814 ui->labelCustomPerKilobyte ->setEnabled(ui->radioCustomFee->isChecked());
815 ui->customFee ->setEnabled(ui->radioCustomFee->isChecked());
816}
817
819{
820 if(!model || !model->getOptionsModel())
821 return;
822
823 if (ui->radioSmartFee->isChecked())
824 ui->labelFeeMinimized->setText(ui->labelSmartFee->text());
825 else {
826 ui->labelFeeMinimized->setText(tr("%1/kvB").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), ui->customFee->value())));
827 }
828}
829
831{
832 if (ui->radioCustomFee->isChecked()) {
833 m_coin_control->m_feerate = CFeeRate(ui->customFee->value());
834 } else {
835 m_coin_control->m_feerate.reset();
836 }
837 // Avoid using global defaults when sending money from the GUI
838 // Either custom fee will be used or if not selected, the confirmation target from dropdown box
839 m_coin_control->m_confirm_target = getConfTargetForIndex(ui->confTargetSelector->currentIndex());
840 m_coin_control->m_signal_bip125_rbf = ui->optInRBF->isChecked();
841}
842
844 // During shutdown, clientModel will be nullptr. Attempting to update views at this point may cause a crash
845 // due to accessing backend models that might no longer exist.
846 if (!clientModel) return;
847 // Process event
850 }
851}
852
854{
855 if(!model || !model->getOptionsModel())
856 return;
858 m_coin_control->m_feerate.reset(); // Explicitly use only fee estimation rate for smart fee labels
859 int returned_target;
860 FeeReason reason;
862
863 ui->labelSmartFee->setText(tr("%1/kvB").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), feeRate.GetFeePerK())));
864
865 if (reason == FeeReason::FALLBACK) {
866 ui->labelSmartFee2->show(); // (Smart fee not initialized yet. This usually takes a few blocks...)
867 ui->labelFeeEstimation->setText("");
868 ui->fallbackFeeWarningLabel->setVisible(true);
869 int lightness = ui->fallbackFeeWarningLabel->palette().color(QPalette::WindowText).lightness();
870 QColor warning_colour(255 - (lightness / 5), 176 - (lightness / 3), 48 - (lightness / 14));
871 ui->fallbackFeeWarningLabel->setStyleSheet("QLabel { color: " + warning_colour.name() + "; }");
872 ui->fallbackFeeWarningLabel->setIndent(GUIUtil::TextWidth(QFontMetrics(ui->fallbackFeeWarningLabel->font()), "x"));
873 }
874 else
875 {
876 ui->labelSmartFee2->hide();
877 ui->labelFeeEstimation->setText(tr("Estimated to begin confirmation within %n block(s).", "", returned_target));
878 ui->fallbackFeeWarningLabel->setVisible(false);
879 }
880
882}
883
884// Coin Control: copy label "Quantity" to clipboard
886{
887 GUIUtil::setClipboard(ui->labelCoinControlQuantity->text());
888}
889
890// Coin Control: copy label "Amount" to clipboard
892{
893 GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left(ui->labelCoinControlAmount->text().indexOf(" ")));
894}
895
896// Coin Control: copy label "Fee" to clipboard
898{
899 GUIUtil::setClipboard(ui->labelCoinControlFee->text().left(ui->labelCoinControlFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
900}
901
902// Coin Control: copy label "After fee" to clipboard
904{
905 GUIUtil::setClipboard(ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
906}
907
908// Coin Control: copy label "Bytes" to clipboard
910{
911 GUIUtil::setClipboard(ui->labelCoinControlBytes->text().replace(ASYMP_UTF8, ""));
912}
913
914// Coin Control: copy label "Change" to clipboard
916{
917 GUIUtil::setClipboard(ui->labelCoinControlChange->text().left(ui->labelCoinControlChange->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
918}
919
920// Coin Control: settings menu - coin control enabled/disabled by user
922{
923 ui->frameCoinControl->setVisible(checked);
924
925 if (!checked && model) { // coin control features disabled
926 m_coin_control = std::make_unique<CCoinControl>();
927 }
928
930}
931
932// Coin Control: button inputs -> show actual coin control dialog
939
940// Coin Control: checkbox custom change address
941#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0))
943#else
945#endif
946{
947 if (state == Qt::Unchecked)
948 {
949 m_coin_control->destChange = CNoDestination();
950 ui->labelCoinControlChangeLabel->clear();
951 }
952 else
953 // use this to re-validate an already entered address
954 coinControlChangeEdited(ui->lineEditCoinControlChange->text());
955
956 ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked));
957}
958
959// Coin Control: custom change address changed
961{
963 {
964 // Default to no change address until verified
965 m_coin_control->destChange = CNoDestination();
966 ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:red;}");
967
968 const CTxDestination dest = DecodeDestination(text.toStdString());
969
970 if (text.isEmpty()) // Nothing entered
971 {
972 ui->labelCoinControlChangeLabel->setText("");
973 }
974 else if (!IsValidDestination(dest)) // Invalid address
975 {
976 ui->labelCoinControlChangeLabel->setText(tr("Warning: Invalid Bitcoin address"));
977 }
978 else // Valid address
979 {
980 if (!model->wallet().isSpendable(dest)) {
981 ui->labelCoinControlChangeLabel->setText(tr("Warning: Unknown change address"));
982
983 // confirmation dialog
984 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?"),
985 QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel);
986
987 if(btnRetVal == QMessageBox::Yes)
988 m_coin_control->destChange = dest;
989 else
990 {
991 ui->lineEditCoinControlChange->setText("");
992 ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
993 ui->labelCoinControlChangeLabel->setText("");
994 }
995 }
996 else // Known change address
997 {
998 ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
999
1000 // Query label
1002 if (!associatedLabel.isEmpty())
1003 ui->labelCoinControlChangeLabel->setText(associatedLabel);
1004 else
1005 ui->labelCoinControlChangeLabel->setText(tr("(no label)"));
1006
1007 m_coin_control->destChange = dest;
1008 }
1009 }
1010 }
1011}
1012
1013// Coin Control: update labels
1015{
1016 if (!model || !model->getOptionsModel())
1017 return;
1018
1020
1021 // set pay amounts
1024
1025 for(int i = 0; i < ui->entries->count(); ++i)
1026 {
1027 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
1028 if(entry && !entry->isHidden())
1029 {
1030 SendCoinsRecipient rcp = entry->getValue();
1031 CoinControlDialog::payAmounts.append(rcp.amount);
1032 if (rcp.fSubtractFeeFromAmount)
1034 }
1035 }
1036
1037 if (m_coin_control->HasSelected())
1038 {
1039 // actual coin control calculation
1041
1042 // show coin control stats
1043 ui->labelCoinControlAutomaticallySelected->hide();
1044 ui->widgetCoinControl->show();
1045 }
1046 else
1047 {
1048 // hide coin control stats
1049 ui->labelCoinControlAutomaticallySelected->show();
1050 ui->widgetCoinControl->hide();
1051 ui->labelCoinControlInsuffFunds->hide();
1052 }
1053}
1054
1056 : QMessageBox(parent), secDelay(_secDelay), m_enable_send(enable_send)
1057{
1058 setIcon(QMessageBox::Question);
1059 setWindowTitle(title); // On macOS, the window title is ignored (as required by the macOS Guidelines).
1060 setText(text);
1063 setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel);
1064 if (always_show_unsigned || !enable_send) addButton(QMessageBox::Save);
1065 setDefaultButton(QMessageBox::Cancel);
1066 yesButton = button(QMessageBox::Yes);
1067 if (confirmButtonText.isEmpty()) {
1068 confirmButtonText = yesButton->text();
1069 }
1070 m_psbt_button = button(QMessageBox::Save);
1071 updateButtons();
1072 connect(&countDownTimer, &QTimer::timeout, this, &SendConfirmationDialog::countDown);
1073}
1074
1076{
1077 updateButtons();
1078 countDownTimer.start(1s);
1079 return QMessageBox::exec();
1080}
1081
1083{
1084 secDelay--;
1085 updateButtons();
1086
1087 if(secDelay <= 0)
1088 {
1089 countDownTimer.stop();
1090 }
1091}
1092
1094{
1095 if(secDelay > 0)
1096 {
1097 yesButton->setEnabled(false);
1098 yesButton->setText(confirmButtonText + (m_enable_send ? (" (" + QString::number(secDelay) + ")") : QString("")));
1099 if (m_psbt_button) {
1100 m_psbt_button->setEnabled(false);
1101 m_psbt_button->setText(m_psbt_button_text + " (" + QString::number(secDelay) + ")");
1102 }
1103 }
1104 else
1105 {
1106 yesButton->setEnabled(m_enable_send);
1107 yesButton->setText(confirmButtonText);
1108 if (m_psbt_button) {
1109 m_psbt_button->setEnabled(true);
1111 }
1112 }
1113}
bool IsValidDestination(const CTxDestination &dest)
Check whether a CTxDestination corresponds to one with an address.
std::variant< CNoDestination, PubKeyDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessV1Taproot, PayToAnchor, WitnessUnknown > CTxDestination
A txout script categorized into standard templates.
int64_t CAmount
Amount in satoshis (Can be negative)
Definition amount.h:12
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.
static QString formatHtmlWithUnit(Unit unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD)
Format as HTML string (with unit)
static QList< Unit > availableUnits()
Get list of units, for drop-down box.
static QString formatWithUnit(Unit unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD)
Format as string (with unit)
Unit
Bitcoin units.
@ MSG_INFORMATION
Predefined combinations for certain default usage cases.
Fee rate in satoshis per virtualbyte: CAmount / vB the feerate is represented internally as FeeFrac.
Definition feerate.h:32
Model for Bitcoin network client.
Definition clientmodel.h:57
void numBlocksChanged(int count, const QDateTime &blockDate, double nVerificationProgress, SyncType header, SynchronizationState sync_state)
static QList< CAmount > payAmounts
static void updateLabels(wallet::CCoinControl &m_coin_control, WalletModel *, QDialog *)
static bool fSubtractFeeFromAmount
Double ended buffer combining vector and stream-like interfaces.
Definition streams.h:133
bool getEnablePSBTControls() const
void coinControlFeaturesChanged(bool)
void displayUnitChanged(BitcoinUnit unit)
BitcoinUnit getDisplayUnit() const
bool hasSigner()
Whether -signer was set or not.
Dialog for sending bitcoins.
void useAvailableBalance(SendCoinsEntry *entry)
WalletModel * model
void presentPSBT(PartiallySignedTransaction &psbt)
ClientModel * clientModel
void coinControlChangeEdited(const QString &)
Ui::SendCoinsDialog * ui
void on_buttonChooseFee_clicked()
void processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg=QString())
void setClientModel(ClientModel *clientModel)
SendCoinsEntry * addEntry()
void updateNumberOfBlocks(int count, const QDateTime &blockDate, double nVerificationProgress, SyncType synctype, SynchronizationState sync_state)
void pasteEntry(const SendCoinsRecipient &rv)
void accept() override
const PlatformStyle * platformStyle
std::unique_ptr< wallet::CCoinControl > m_coin_control
void coinControlClipboardQuantity()
void coinControlClipboardAfterFee()
bool signWithExternalSigner(PartiallySignedTransaction &psbt, CMutableTransaction &mtx, bool &complete)
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 coinControlChangeChecked(Qt::CheckState)
bool handlePaymentRequest(const SendCoinsRecipient &recipient)
void setBalance(const interfaces::WalletBalances &balances)
void coinControlClipboardAmount()
void setAddress(const QString &address)
void coinsSent(const Txid &txid)
void coinControlClipboardChange()
std::unique_ptr< WalletModelTransaction > m_current_transaction
void removeEntry(SendCoinsEntry *entry)
void reject() override
void message(const QString &title, const QString &message, unsigned int style)
SendCoinsDialog(const PlatformStyle *platformStyle, QWidget *parent=nullptr)
void on_buttonMinimizeFee_clicked()
void coinControlFeatureChanged(bool)
void minimizeFeeSection(bool fMinimize)
A single entry in the dialog for sending bitcoins.
void setFocus()
void setAddress(const QString &address)
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()
SendConfirmationDialog(const QString &title, const QString &text, const QString &informative_text="", const QString &detailed_text="", int secDelay=SEND_CONFIRM_DELAY, bool enable_send=true, bool always_show_unsigned=true, QWidget *parent=nullptr)
QAbstractButton * m_psbt_button
QAbstractButton * yesButton
Interface to Bitcoin wallet from Qt view code.
Definition walletmodel.h:49
interfaces::Node & node() const
AddressTableModel * getAddressTableModel() const
SendCoinsReturn prepareTransaction(WalletModelTransaction &transaction, const wallet::CCoinControl &coinControl)
void sendCoins(WalletModelTransaction &transaction)
CAmount getAvailableBalance(const wallet::CCoinControl *control)
bool isMultiwallet() const
interfaces::Wallet & wallet() const
OptionsModel * getOptionsModel() const
UnlockContext requestUnlock()
void balanceChanged(const interfaces::WalletBalances &balances)
interfaces::WalletBalances getCachedBalance() const
QString getWalletName() const
@ TransactionCreationFailed
Definition walletmodel.h:63
@ AmountExceedsBalance
Definition walletmodel.h:61
virtual CAmount getRequiredFee(unsigned int tx_bytes)=0
Get required fee.
virtual std::optional< common::PSBTError > fillPSBT(std::optional< int > sighash_type, bool sign, bool bip32derivs, size_t *n_signed, PartiallySignedTransaction &psbtx, bool &complete)=0
Fill PSBT.
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 bool privateKeysDisabled()=0
virtual CAmount getMinimumFee(unsigned int tx_bytes, const wallet::CCoinControl &coin_control, int *returned_target, FeeReason *reason)=0
Get minimum fee.
Coin Control Features.
Definition coincontrol.h:84
bool m_allow_other_inputs
If true, the selection process can add extra unselected inputs from the wallet while requires all sel...
Definition coincontrol.h:94
SyncType
Definition clientmodel.h:42
#define ASYMP_UTF8
CTxDestination DecodeDestination(const std::string &str, std::string &error_msg, std::vector< int > *error_locations)
Definition key_io.cpp:299
Utility functions used by the Bitcoin Qt UI.
Definition bitcoingui.h:58
QString HtmlEscape(const QString &str, bool fMultiLine)
Definition guiutil.cpp:249
void ShowModalDialogAsynchronously(QDialog *dialog)
Shows a QDialog instance asynchronously, and deletes it on close.
Definition guiutil.cpp:978
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:313
QString formatNiceTimeOffset(qint64 secs)
Definition guiutil.cpp:782
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:369
int TextWidth(const QFontMetrics &fm, const QString &text)
Returns the distance in pixels appropriate for drawing a subsequent character after text.
Definition guiutil.cpp:910
void setupAddressWidget(QValidatedLineEdit *widget, QWidget *parent)
Definition guiutil.cpp:131
void setClipboard(const QString &str)
Definition guiutil.cpp:661
PSBTError
Definition types.h:17
is a home for public enum and struct type definitions that are used internally by node code,...
static CTransactionRef MakeTransactionRef(Tx &&txIn)
std::shared_ptr< const CTransaction > CTransactionRef
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:567
int getConfTargetForIndex(int index)
int getIndexForConfTarget(int target)
static constexpr std::array confTargets
#define SEND_CONFIRM_DELAY
A mutable version of CTransaction.
A version of CTransaction with the PSBT format.
Definition psbt.h:1139
Collection of wallet balances.
Definition wallet.h:368
static int count
std::string EncodeBase64(std::span< const unsigned char > input)
constexpr auto Ticks(Dur2 d)
Helper to count the seconds of a duration/time_point.
Definition time.h:73
assert(!tx.IsCoinBase())
SynchronizationState
Current sync state passed to tip changed callbacks.
Definition validation.h:93