Bitcoin Core 31.99.0
P2P Digital Currency
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>
24#include <txmempool.h>
25#include <validation.h>
26#include <wallet/coincontrol.h>
27#include <wallet/fees.h>
28#include <wallet/types.h>
29#include <wallet/wallet.h>
30
31#include <array>
32#include <chrono>
33#include <fstream>
34#include <memory>
35#include <optional>
36
37#include <QFontMetrics>
38#include <QScrollBar>
39#include <QSettings>
40#include <QTextDocument>
41
44
45static constexpr std::array confTargets{2, 4, 6, 12, 24, 48, 144, 504, 1008};
46int getConfTargetForIndex(int index) {
47 if (index+1 > static_cast<int>(confTargets.size())) {
48 return confTargets.back();
49 }
50 if (index < 0) {
51 return confTargets[0];
52 }
53 return confTargets[index];
54}
55int getIndexForConfTarget(int target) {
56 for (unsigned int i = 0; i < confTargets.size(); i++) {
57 if (confTargets[i] >= target) {
58 return i;
59 }
60 }
61 return confTargets.size() - 1;
62}
63
64SendCoinsDialog::SendCoinsDialog(const PlatformStyle *_platformStyle, QWidget *parent) :
65 QDialog(parent, GUIUtil::dialog_flags),
66 ui(new Ui::SendCoinsDialog),
67 m_coin_control(new CCoinControl),
68 platformStyle(_platformStyle)
69{
70 ui->setupUi(this);
71
72 if (!_platformStyle->getImagesOnButtons()) {
73 ui->addButton->setIcon(QIcon());
74 ui->clearButton->setIcon(QIcon());
75 ui->sendButton->setIcon(QIcon());
76 } else {
77 ui->addButton->setIcon(_platformStyle->SingleColorIcon(":/icons/add"));
78 ui->clearButton->setIcon(_platformStyle->SingleColorIcon(":/icons/remove"));
79 ui->sendButton->setIcon(_platformStyle->SingleColorIcon(":/icons/send"));
80 }
81
82 GUIUtil::setupAddressWidget(ui->lineEditCoinControlChange, this);
83
84 addEntry();
85
86 connect(ui->addButton, &QPushButton::clicked, this, &SendCoinsDialog::addEntry);
87 connect(ui->clearButton, &QPushButton::clicked, this, &SendCoinsDialog::clear);
88
89 // Coin Control
90 connect(ui->pushButtonCoinControl, &QPushButton::clicked, this, &SendCoinsDialog::coinControlButtonClicked);
91#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0))
92 connect(ui->checkBoxCoinControlChange, &QCheckBox::checkStateChanged, this, &SendCoinsDialog::coinControlChangeChecked);
93#else
94 connect(ui->checkBoxCoinControlChange, &QCheckBox::stateChanged, this, &SendCoinsDialog::coinControlChangeChecked);
95#endif
96 connect(ui->lineEditCoinControlChange, &QValidatedLineEdit::textEdited, this, &SendCoinsDialog::coinControlChangeEdited);
97
98 // Coin Control: clipboard actions
99 QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this);
100 QAction *clipboardAmountAction = new QAction(tr("Copy amount"), this);
101 QAction *clipboardFeeAction = new QAction(tr("Copy fee"), this);
102 QAction *clipboardAfterFeeAction = new QAction(tr("Copy after fee"), this);
103 QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this);
104 QAction *clipboardChangeAction = new QAction(tr("Copy change"), this);
105 connect(clipboardQuantityAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardQuantity);
106 connect(clipboardAmountAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardAmount);
107 connect(clipboardFeeAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardFee);
108 connect(clipboardAfterFeeAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardAfterFee);
109 connect(clipboardBytesAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardBytes);
110 connect(clipboardChangeAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardChange);
111 ui->labelCoinControlQuantity->addAction(clipboardQuantityAction);
112 ui->labelCoinControlAmount->addAction(clipboardAmountAction);
113 ui->labelCoinControlFee->addAction(clipboardFeeAction);
114 ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction);
115 ui->labelCoinControlBytes->addAction(clipboardBytesAction);
116 ui->labelCoinControlChange->addAction(clipboardChangeAction);
117
118 // init transaction fee section
119 QSettings settings;
120 if (!settings.contains("fFeeSectionMinimized"))
121 settings.setValue("fFeeSectionMinimized", true);
122 if (!settings.contains("nFeeRadio") && settings.contains("nTransactionFee") && settings.value("nTransactionFee").toLongLong() > 0) // compatibility
123 settings.setValue("nFeeRadio", 1); // custom
124 if (!settings.contains("nFeeRadio"))
125 settings.setValue("nFeeRadio", 0); // recommended
126 if (!settings.contains("nSmartFeeSliderPosition"))
127 settings.setValue("nSmartFeeSliderPosition", 0);
128 ui->groupFee->setId(ui->radioSmartFee, 0);
129 ui->groupFee->setId(ui->radioCustomFee, 1);
130 ui->groupFee->button((int)std::max(0, std::min(1, settings.value("nFeeRadio").toInt())))->setChecked(true);
131 ui->customFee->SetAllowEmpty(false);
132 ui->customFee->setValue(settings.value("nTransactionFee").toLongLong());
133 minimizeFeeSection(settings.value("fFeeSectionMinimized").toBool());
134
135 GUIUtil::ExceptionSafeConnect(ui->sendButton, &QPushButton::clicked, this, &SendCoinsDialog::sendButtonClicked);
136}
137
139{
140 this->clientModel = _clientModel;
141
142 if (_clientModel) {
144 }
145}
146
148{
149 this->model = _model;
150
151 if(_model && _model->getOptionsModel())
152 {
153 for(int i = 0; i < ui->entries->count(); ++i)
154 {
155 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
156 if(entry)
157 {
158 entry->setModel(_model);
159 }
160 }
161
165
166 // Coin Control
169 ui->frameCoinControl->setVisible(_model->getOptionsModel()->getCoinControlFeatures());
171
172 // fee section
173 for (const int n : confTargets) {
174 ui->confTargetSelector->addItem(tr("%1 (%2 blocks)").arg(GUIUtil::formatNiceTimeOffset(n*Params().GetConsensus().nPowTargetSpacing)).arg(n));
175 }
176 connect(ui->confTargetSelector, qOverload<int>(&QComboBox::currentIndexChanged), this, &SendCoinsDialog::updateSmartFeeLabel);
177 connect(ui->confTargetSelector, qOverload<int>(&QComboBox::currentIndexChanged), this, &SendCoinsDialog::coinControlUpdateLabels);
178
179 connect(ui->groupFee, &QButtonGroup::idClicked, this, &SendCoinsDialog::updateFeeSectionControls);
180 connect(ui->groupFee, &QButtonGroup::idClicked, this, &SendCoinsDialog::coinControlUpdateLabels);
181
183 CAmount requiredFee = model->wallet().getRequiredFee(1000);
184 ui->customFee->SetMinValue(requiredFee);
185 if (ui->customFee->value() < requiredFee) {
186 ui->customFee->setValue(requiredFee);
187 }
188 ui->customFee->setSingleStep(requiredFee);
191
192 if (model->wallet().hasExternalSigner()) {
193 //: "device" usually means a hardware wallet.
194 ui->sendButton->setText(tr("Sign on device"));
195 if (model->getOptionsModel()->hasSigner()) {
196 ui->sendButton->setEnabled(true);
197 ui->sendButton->setToolTip(tr("Connect your hardware wallet first."));
198 } else {
199 ui->sendButton->setEnabled(false);
200 //: "External signer" means using devices such as hardware wallets.
201 ui->sendButton->setToolTip(tr("Set external signer script path in Options -> Wallet"));
202 }
203 } else if (model->wallet().privateKeysDisabled()) {
204 ui->sendButton->setText(tr("Cr&eate Unsigned"));
205 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));
206 }
207
208 // set the smartfee-sliders default value (wallets default conf.target or last stored value)
209 QSettings settings;
210 if (settings.value("nSmartFeeSliderPosition").toInt() != 0) {
211 // migrate nSmartFeeSliderPosition to nConfTarget
212 // nConfTarget is available since 0.15 (replaced nSmartFeeSliderPosition)
213 int nConfirmTarget = 25 - settings.value("nSmartFeeSliderPosition").toInt(); // 25 == old slider range
214 settings.setValue("nConfTarget", nConfirmTarget);
215 settings.remove("nSmartFeeSliderPosition");
216 }
217 if (settings.value("nConfTarget").toInt() == 0)
218 ui->confTargetSelector->setCurrentIndex(getIndexForConfTarget(model->wallet().getConfirmTarget()));
219 else
220 ui->confTargetSelector->setCurrentIndex(getIndexForConfTarget(settings.value("nConfTarget").toInt()));
221 }
222}
223
225{
226 QSettings settings;
227 settings.setValue("fFeeSectionMinimized", fFeeMinimized);
228 settings.setValue("nFeeRadio", ui->groupFee->checkedId());
229 settings.setValue("nConfTarget", getConfTargetForIndex(ui->confTargetSelector->currentIndex()));
230 settings.setValue("nTransactionFee", (qint64)ui->customFee->value());
231
232 delete ui;
233}
234
235bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informative_text, QString& detailed_text)
236{
237 QList<SendCoinsRecipient> recipients;
238 bool valid = true;
239
240 for(int i = 0; i < ui->entries->count(); ++i)
241 {
242 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
243 if(entry)
244 {
245 if(entry->validate(model->node()))
246 {
247 recipients.append(entry->getValue());
248 }
249 else if (valid)
250 {
251 ui->scrollArea->ensureWidgetVisible(entry);
252 valid = false;
253 }
254 }
255 }
256
257 if(!valid || recipients.isEmpty())
258 {
259 return false;
260 }
261
262 fNewRecipientAllowed = false;
264 if(!ctx.isValid())
265 {
266 // Unlock wallet was cancelled
268 return false;
269 }
270
271 // prepare transaction for getting txFee earlier
272 m_current_transaction = std::make_unique<WalletModelTransaction>(recipients);
273 WalletModel::SendCoinsReturn prepareStatus;
274
276
277 CCoinControl coin_control = *m_coin_control;
278 coin_control.m_allow_other_inputs = !coin_control.HasSelected(); // future, could introduce a checkbox to customize this value.
279 prepareStatus = model->prepareTransaction(*m_current_transaction, coin_control);
280
281 // process prepareStatus and on error generate message shown to user
282 processSendCoinsReturn(prepareStatus,
284
285 if(prepareStatus.status != WalletModel::OK) {
287 return false;
288 }
289
290 CAmount txFee = m_current_transaction->getTransactionFee();
291 QStringList formatted;
292 for (const SendCoinsRecipient &rcp : m_current_transaction->getRecipients())
293 {
294 // generate amount string with wallet name in case of multiwallet
295 QString amount = BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
296 if (model->isMultiwallet()) {
297 amount = tr("%1 from wallet '%2'").arg(amount, GUIUtil::HtmlEscape(model->getWalletName()));
298 }
299
300 // generate address string
301 QString address = rcp.address;
302
303 QString recipientElement;
304
305 {
306 if(rcp.label.length() > 0) // label with address
307 {
308 recipientElement.append(tr("%1 to '%2'").arg(amount, GUIUtil::HtmlEscape(rcp.label)));
309 recipientElement.append(QString(" (%1)").arg(address));
310 }
311 else // just address
312 {
313 recipientElement.append(tr("%1 to %2").arg(amount, address));
314 }
315 }
316 formatted.append(recipientElement);
317 }
318
319 /*: Message displayed when attempting to create a transaction. Cautionary text to prompt the user to verify
320 that the displayed transaction details represent the transaction the user intends to create. */
321 question_string.append(tr("Do you want to create this transaction?"));
322 question_string.append("<br /><span style='font-size:10pt;'>");
324 /*: Text to inform a user attempting to create a transaction of their current options. At this stage,
325 a user can only create a PSBT. This string is displayed when private keys are disabled and an external
326 signer is not available. */
327 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));
329 /*: Text to inform a user attempting to create a transaction of their current options. At this stage,
330 a user can send their transaction or create a PSBT. This string is displayed when both private keys
331 and PSBT controls are enabled. */
332 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));
333 } else {
334 /*: Text to prompt a user to review the details of the transaction they are attempting to send. */
335 question_string.append(tr("Please, review your transaction."));
336 }
337 question_string.append("</span>%1");
338
339 if(txFee > 0)
340 {
341 // append fee string if a fee is required
342 question_string.append("<hr /><b>");
343 question_string.append(tr("Transaction fee"));
344 question_string.append("</b>");
345
346 // append transaction size
347 //: When reviewing a newly created PSBT (via Send flow), the transaction fee is shown, with "virtual size" of the transaction displayed for context
348 question_string.append(" (" + tr("%1 kvB", "PSBT transaction creation").arg((double)m_current_transaction->getTransactionSize() / 1000, 0, 'g', 3) + "): ");
349
350 // append transaction fee value
351 question_string.append("<span style='color:#aa0000; font-weight:bold;'>");
352 question_string.append(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), txFee));
353 question_string.append("</span><br />");
354 }
355
356 // append RBF message
357 question_string.append("<span style='font-size:10pt; font-weight:normal;'>");
358 question_string.append(tr("You can increase the fee later."));
359
360 // add total amount in all subdivision units
361 question_string.append("<hr />");
362 CAmount totalAmount = m_current_transaction->getTotalTransactionAmount() + txFee;
363 QStringList alternativeUnits;
364 for (const BitcoinUnit u : BitcoinUnits::availableUnits()) {
366 alternativeUnits.append(BitcoinUnits::formatHtmlWithUnit(u, totalAmount));
367 }
368 question_string.append(QString("<b>%1</b>: <b>%2</b>").arg(tr("Total Amount"))
370 question_string.append(QString("<br /><span style='font-size:10pt; font-weight:normal;'>(=%1)</span>")
371 .arg(alternativeUnits.join(" " + tr("or") + " ")));
372
373 if (formatted.size() > 1) {
374 question_string = question_string.arg("");
375 informative_text = tr("To review recipient list click \"Show Details…\"");
376 detailed_text = formatted.join("\n\n");
377 } else {
378 question_string = question_string.arg("<br /><br />" + formatted.at(0));
379 }
380
381 return true;
382}
383
385{
386 // Serialize the PSBT
387 DataStream ssTx{};
388 ssTx << psbtx;
389 GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str());
390 QMessageBox msgBox(this);
391 //: Caption of "PSBT has been copied" messagebox
392 msgBox.setText(tr("Unsigned Transaction", "PSBT copied"));
393 msgBox.setInformativeText(tr("The PSBT has been copied to the clipboard. You can also save it."));
394 msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard);
395 msgBox.setDefaultButton(QMessageBox::Discard);
396 msgBox.setObjectName("psbt_copied_message");
397 switch (msgBox.exec()) {
398 case QMessageBox::Save: {
399 QString selectedFilter;
400 QString fileNameSuggestion = "";
401 bool first = true;
402 for (const SendCoinsRecipient &rcp : m_current_transaction->getRecipients()) {
403 if (!first) {
404 fileNameSuggestion.append(" - ");
405 }
406 QString labelOrAddress = rcp.label.isEmpty() ? rcp.address : rcp.label;
407 QString amount = BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
408 fileNameSuggestion.append(labelOrAddress + "-" + amount);
409 first = false;
410 }
411 fileNameSuggestion.append(".psbt");
412 QString filename = GUIUtil::getSaveFileName(this,
413 tr("Save Transaction Data"), fileNameSuggestion,
414 //: Expanded name of the binary PSBT file format. See: BIP 174.
415 tr("Partially Signed Transaction (Binary)") + QLatin1String(" (*.psbt)"), &selectedFilter);
416 if (filename.isEmpty()) {
417 return;
418 }
419 std::ofstream out{filename.toLocal8Bit().data(), std::ofstream::out | std::ofstream::binary};
420 out << ssTx.str();
421 out.close();
422 //: Popup message when a PSBT has been saved to a file
423 Q_EMIT message(tr("PSBT saved"), tr("PSBT saved to disk"), CClientUIInterface::MSG_INFORMATION);
424 break;
425 }
426 case QMessageBox::Discard:
427 break;
428 default:
429 assert(false);
430 } // msgBox.exec()
431}
432
434 std::optional<PSBTError> err;
435 try {
436 err = model->wallet().fillPSBT({.sign = true, .bip32_derivs = true}, /*n_signed=*/nullptr, psbtx, complete);
437 } catch (const std::runtime_error& e) {
438 QMessageBox::critical(nullptr, tr("Sign failed"), e.what());
439 return false;
440 }
441 if (err == PSBTError::EXTERNAL_SIGNER_NOT_FOUND) {
442 //: "External signer" means using devices such as hardware wallets.
443 const QString msg = tr("External signer not found");
444 QMessageBox::critical(nullptr, msg, msg);
445 return false;
446 }
447 if (err == PSBTError::EXTERNAL_SIGNER_FAILED) {
448 //: "External signer" means using devices such as hardware wallets.
449 const QString msg = tr("External signer failure");
450 QMessageBox::critical(nullptr, msg, msg);
451 return false;
452 }
453 if (err) {
454 qWarning() << "Failed to sign PSBT";
456 return false;
457 }
458 // fillPSBT does not always properly finalize
459 complete = FinalizeAndExtractPSBT(psbtx, mtx);
460 return true;
461}
462
463void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
464{
465 if(!model || !model->getOptionsModel())
466 return;
467
468 QString question_string, informative_text, detailed_text;
469 if (!PrepareSendText(question_string, informative_text, detailed_text)) return;
471
472 const QString confirmation = tr("Confirm send coins");
473 const bool enable_send{!model->wallet().privateKeysDisabled() || model->wallet().hasExternalSigner()};
474 const bool always_show_unsigned{model->getOptionsModel()->getEnablePSBTControls()};
475 auto confirmationDialog = new SendConfirmationDialog(confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, enable_send, always_show_unsigned, this);
476 confirmationDialog->setAttribute(Qt::WA_DeleteOnClose);
477 // TODO: Replace QDialog::exec() with safer QDialog::show().
478 const auto retval = static_cast<QMessageBox::StandardButton>(confirmationDialog->exec());
479
480 if(retval != QMessageBox::Yes && retval != QMessageBox::Save)
481 {
483 return;
484 }
485
486 bool send_failure = false;
487 if (retval == QMessageBox::Save) {
488 // "Create Unsigned" clicked
491 bool complete = false;
492 // Fill without signing
493 const auto err{model->wallet().fillPSBT({.sign = false, .bip32_derivs = true}, /*n_signed=*/nullptr, psbtx, complete)};
494 assert(!complete);
495 assert(!err);
496
497 // Copy PSBT to clipboard and offer to save
498 presentPSBT(psbtx);
499 } else {
500 // "Send" clicked
502 bool broadcast = true;
503 if (model->wallet().hasExternalSigner()) {
506 bool complete = false;
507 // Always fill without signing first. This prevents an external signer
508 // from being called prematurely and is not expensive.
509 const auto err{model->wallet().fillPSBT({.sign = false, .bip32_derivs = true}, /*n_signed=*/nullptr, psbtx, complete)};
510 assert(!complete);
511 assert(!err);
512 send_failure = !signWithExternalSigner(psbtx, mtx, complete);
513 // Don't broadcast when user rejects it on the device or there's a failure:
514 broadcast = complete && !send_failure;
515 if (!send_failure) {
516 // A transaction signed with an external signer is not always complete,
517 // e.g. in a multisig wallet.
518 if (complete) {
519 // Prepare transaction for broadcast transaction if complete
520 const CTransactionRef tx = MakeTransactionRef(mtx);
521 m_current_transaction->setWtx(tx);
522 } else {
523 presentPSBT(psbtx);
524 }
525 }
526 }
527
528 // Broadcast the transaction, unless an external signer was used and it
529 // failed, or more signatures are needed.
530 if (broadcast) {
531 // now send the prepared transaction
533 Q_EMIT coinsSent(m_current_transaction->getWtx()->GetHash());
534 }
535 }
536 if (!send_failure) {
537 accept();
538 m_coin_control->UnSelectAll();
540 }
542 m_current_transaction.reset();
543}
544
546{
547 m_current_transaction.reset();
548
549 // Clear coin control settings
550 m_coin_control->UnSelectAll();
551 ui->checkBoxCoinControlChange->setChecked(false);
552 ui->lineEditCoinControlChange->clear();
554
555 // Remove entries until only one left
556 while(ui->entries->count())
557 {
558 ui->entries->takeAt(0)->widget()->deleteLater();
559 }
560 addEntry();
561
563}
564
566{
567 clear();
568}
569
571{
572 clear();
573}
574
576{
577 SendCoinsEntry *entry = new SendCoinsEntry(platformStyle, this);
578 entry->setModel(model);
579 ui->entries->addWidget(entry);
584
585 // Focus the field, so that entry can start immediately
586 entry->clear();
587 entry->setFocus();
588 ui->scrollAreaWidgetContents->resize(ui->scrollAreaWidgetContents->sizeHint());
589
590 // Scroll to the newly added entry on a QueuedConnection because Qt doesn't
591 // adjust the scroll area and scrollbar immediately when the widget is added.
592 // Invoking on a DirectConnection will only scroll to the second-to-last entry.
593 QMetaObject::invokeMethod(ui->scrollArea, [this] {
594 if (ui->scrollArea->verticalScrollBar()) {
595 ui->scrollArea->verticalScrollBar()->setValue(ui->scrollArea->verticalScrollBar()->maximum());
596 }
597 }, Qt::QueuedConnection);
598
599 updateTabsAndLabels();
600 return entry;
601}
602
604{
605 setupTabChain(nullptr);
607}
608
610{
611 entry->hide();
612
613 // If the last entry is about to be removed add an empty one
614 if (ui->entries->count() == 1)
615 addEntry();
616
617 entry->deleteLater();
618
620}
621
622QWidget *SendCoinsDialog::setupTabChain(QWidget *prev)
623{
624 for(int i = 0; i < ui->entries->count(); ++i)
625 {
626 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
627 if(entry)
628 {
629 prev = entry->setupTabChain(prev);
630 }
631 }
632 QWidget::setTabOrder(prev, ui->sendButton);
633 QWidget::setTabOrder(ui->sendButton, ui->clearButton);
634 QWidget::setTabOrder(ui->clearButton, ui->addButton);
635 return ui->addButton;
636}
637
638void SendCoinsDialog::setAddress(const QString &address)
639{
640 SendCoinsEntry *entry = nullptr;
641 // Replace the first entry if it is still unused
642 if(ui->entries->count() == 1)
643 {
644 SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
645 if(first->isClear())
646 {
647 entry = first;
648 }
649 }
650 if(!entry)
651 {
652 entry = addEntry();
653 }
654
655 entry->setAddress(address);
656}
657
659{
661 return;
662
663 SendCoinsEntry *entry = nullptr;
664 // Replace the first entry if it is still unused
665 if(ui->entries->count() == 1)
666 {
667 SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
668 if(first->isClear())
669 {
670 entry = first;
671 }
672 }
673 if(!entry)
674 {
675 entry = addEntry();
676 }
677
678 entry->setValue(rv);
680}
681
683{
684 // Just paste the entry, all pre-checks
685 // are done in paymentserver.cpp.
686 pasteEntry(rv);
687 return true;
688}
689
691{
692 if(model && model->getOptionsModel())
693 {
694 CAmount balance = balances.balance;
695 if (model->wallet().hasExternalSigner()) {
696 ui->labelBalanceName->setText(tr("External balance:"));
697 }
698 ui->labelBalance->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), balance));
699 }
700}
701
703{
705 ui->customFee->setDisplayUnit(model->getOptionsModel()->getDisplayUnit());
707}
708
709void SendCoinsDialog::processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg)
710{
711 QPair<QString, CClientUIInterface::MessageBoxFlags> msgParams;
712 // Default to a warning message, override if error message is needed
713 msgParams.second = CClientUIInterface::MSG_WARNING;
714
715 // This comment is specific to SendCoinsDialog usage of WalletModel::SendCoinsReturn.
716 // All status values are used only in WalletModel::prepareTransaction()
717 switch(sendCoinsReturn.status)
718 {
720 msgParams.first = tr("The recipient address is not valid. Please recheck.");
721 break;
723 msgParams.first = tr("The amount to pay must be larger than 0.");
724 break;
726 msgParams.first = tr("The amount exceeds your balance.");
727 break;
729 msgParams.first = tr("Duplicate address found: addresses should only be used once each.");
730 break;
732 msgParams.first = tr("Transaction creation failed!");
733 msgParams.second = CClientUIInterface::MSG_ERROR;
734 break;
736 msgParams.first = tr("A fee higher than %1 is considered an absurdly high fee.").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), model->wallet().getDefaultMaxTxFee()));
737 break;
738 case WalletModel::OK:
739 return;
740 } // no default case, so the compiler can warn about missing cases
741 Q_EMIT message(tr("Send Coins"), msgParams.first, msgParams.second);
742}
743
745{
746 ui->labelFeeMinimized->setVisible(fMinimize);
747 ui->buttonChooseFee ->setVisible(fMinimize);
748 ui->buttonMinimizeFee->setVisible(!fMinimize);
749 ui->frameFeeSelection->setVisible(!fMinimize);
750 ui->horizontalLayoutSmartFee->setContentsMargins(0, (fMinimize ? 0 : 6), 0, 0);
751 fFeeMinimized = fMinimize;
752}
753
755{
756 minimizeFeeSection(false);
757}
758
760{
762 minimizeFeeSection(true);
763}
764
766{
767 // Same behavior as send: if we have selected coins, only obtain their available balance.
768 // Copy to avoid modifying the member's data.
769 CCoinControl coin_control = *m_coin_control;
770 coin_control.m_allow_other_inputs = !coin_control.HasSelected();
771
772 // Calculate available amount to send.
773 CAmount amount = model->getAvailableBalance(&coin_control);
774 for (int i = 0; i < ui->entries->count(); ++i) {
775 SendCoinsEntry* e = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
776 if (e && !e->isHidden() && e != entry) {
777 amount -= e->getValue().amount;
778 }
779 }
780
781 if (amount > 0) {
783 entry->setAmount(amount);
784 } else {
785 entry->setAmount(0);
786 }
787}
788
790{
791 ui->confTargetSelector ->setEnabled(ui->radioSmartFee->isChecked());
792 ui->labelSmartFee ->setEnabled(ui->radioSmartFee->isChecked());
793 ui->labelSmartFee2 ->setEnabled(ui->radioSmartFee->isChecked());
794 ui->labelSmartFee3 ->setEnabled(ui->radioSmartFee->isChecked());
795 ui->labelFeeEstimation ->setEnabled(ui->radioSmartFee->isChecked());
796 ui->labelCustomFeeWarning ->setEnabled(ui->radioCustomFee->isChecked());
797 ui->labelCustomPerKilobyte ->setEnabled(ui->radioCustomFee->isChecked());
798 ui->customFee ->setEnabled(ui->radioCustomFee->isChecked());
799}
800
802{
803 if(!model || !model->getOptionsModel())
804 return;
805
806 if (ui->radioSmartFee->isChecked())
807 ui->labelFeeMinimized->setText(ui->labelSmartFee->text());
808 else {
809 ui->labelFeeMinimized->setText(tr("%1/kvB").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), ui->customFee->value())));
810 }
811}
812
814{
815 if (ui->radioCustomFee->isChecked()) {
816 m_coin_control->m_feerate = CFeeRate(ui->customFee->value());
817 } else {
818 m_coin_control->m_feerate.reset();
819 }
820 // Avoid using global defaults when sending money from the GUI
821 // Either custom fee will be used or if not selected, the confirmation target from dropdown box
822 m_coin_control->m_confirm_target = getConfTargetForIndex(ui->confTargetSelector->currentIndex());
823}
824
825void SendCoinsDialog::updateNumberOfBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, SyncType synctype, SynchronizationState sync_state) {
826 // During shutdown, clientModel will be nullptr. Attempting to update views at this point may cause a crash
827 // due to accessing backend models that might no longer exist.
828 if (!clientModel) return;
829 // Process event
830 if (sync_state == SynchronizationState::POST_INIT) {
832 }
833}
834
836{
837 if(!model || !model->getOptionsModel())
838 return;
840 m_coin_control->m_feerate.reset(); // Explicitly use only fee estimation rate for smart fee labels
841 std::optional<int> returned_target;
842 FeeReason reason;
843 CFeeRate feeRate = CFeeRate(model->wallet().getMinimumFee(1000, *m_coin_control, &returned_target, &reason));
844
845 ui->labelSmartFee->setText(tr("%1/kvB").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), feeRate.GetFeePerK())));
846
847 if (reason == FeeReason::FALLBACK) {
848 ui->labelSmartFee2->show(); // (Smart fee not initialized yet. This usually takes a few blocks...)
849 ui->labelFeeEstimation->setText("");
850 ui->fallbackFeeWarningLabel->setVisible(true);
851 int lightness = ui->fallbackFeeWarningLabel->palette().color(QPalette::WindowText).lightness();
852 QColor warning_colour(255 - (lightness / 5), 176 - (lightness / 3), 48 - (lightness / 14));
853 ui->fallbackFeeWarningLabel->setStyleSheet("QLabel { color: " + warning_colour.name() + "; }");
854 ui->fallbackFeeWarningLabel->setIndent(GUIUtil::TextWidth(QFontMetrics(ui->fallbackFeeWarningLabel->font()), "x"));
855 }
856 else
857 {
858 ui->labelSmartFee2->hide();
859 ui->labelFeeEstimation->setText("");
860 if (returned_target) {
861 ui->labelFeeEstimation->setText(tr("Estimated to begin confirmation within %n block(s).", "", *returned_target));
862 }
863 ui->fallbackFeeWarningLabel->setVisible(false);
864 }
865
867}
868
869// Coin Control: copy label "Quantity" to clipboard
871{
872 GUIUtil::setClipboard(ui->labelCoinControlQuantity->text());
873}
874
875// Coin Control: copy label "Amount" to clipboard
877{
878 GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left(ui->labelCoinControlAmount->text().indexOf(" ")));
879}
880
881// Coin Control: copy label "Fee" to clipboard
883{
884 GUIUtil::setClipboard(ui->labelCoinControlFee->text().left(ui->labelCoinControlFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
885}
886
887// Coin Control: copy label "After fee" to clipboard
889{
890 GUIUtil::setClipboard(ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
891}
892
893// Coin Control: copy label "Bytes" to clipboard
895{
896 GUIUtil::setClipboard(ui->labelCoinControlBytes->text().replace(ASYMP_UTF8, ""));
897}
898
899// Coin Control: copy label "Change" to clipboard
901{
902 GUIUtil::setClipboard(ui->labelCoinControlChange->text().left(ui->labelCoinControlChange->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
903}
904
905// Coin Control: settings menu - coin control enabled/disabled by user
907{
908 ui->frameCoinControl->setVisible(checked);
909
910 if (!checked && model) { // coin control features disabled
911 m_coin_control = std::make_unique<CCoinControl>();
912 }
913
915}
916
917// Coin Control: button inputs -> show actual coin control dialog
919{
921 connect(dlg, &QDialog::finished, this, &SendCoinsDialog::coinControlUpdateLabels);
923}
924
925// Coin Control: checkbox custom change address
926#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0))
928#else
930#endif
931{
932 if (state == Qt::Unchecked)
933 {
934 m_coin_control->destChange = CNoDestination();
935 ui->labelCoinControlChangeLabel->clear();
936 }
937 else
938 // use this to re-validate an already entered address
939 coinControlChangeEdited(ui->lineEditCoinControlChange->text());
940
941 ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked));
942}
943
944// Coin Control: custom change address changed
946{
948 {
949 // Default to no change address until verified
950 m_coin_control->destChange = CNoDestination();
951 ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:red;}");
952
953 const CTxDestination dest = DecodeDestination(text.toStdString());
954
955 if (text.isEmpty()) // Nothing entered
956 {
957 ui->labelCoinControlChangeLabel->setText("");
958 }
959 else if (!IsValidDestination(dest)) // Invalid address
960 {
961 ui->labelCoinControlChangeLabel->setText(tr("Warning: Invalid Bitcoin address"));
962 }
963 else // Valid address
964 {
965 if (!model->wallet().isSpendable(dest)) {
966 ui->labelCoinControlChangeLabel->setText(tr("Warning: Unknown change address"));
967
968 // confirmation dialog
969 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?"),
970 QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel);
971
972 if(btnRetVal == QMessageBox::Yes)
973 m_coin_control->destChange = dest;
974 else
975 {
976 ui->lineEditCoinControlChange->setText("");
977 ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
978 ui->labelCoinControlChangeLabel->setText("");
979 }
980 }
981 else // Known change address
982 {
983 ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
984
985 // Query label
986 QString associatedLabel = model->getAddressTableModel()->labelForAddress(text);
987 if (!associatedLabel.isEmpty())
988 ui->labelCoinControlChangeLabel->setText(associatedLabel);
989 else
990 ui->labelCoinControlChangeLabel->setText(tr("(no label)"));
991
992 m_coin_control->destChange = dest;
993 }
994 }
995 }
996}
997
998// Coin Control: update labels
1000{
1001 if (!model || !model->getOptionsModel())
1002 return;
1003
1005
1006 // set pay amounts
1009
1010 for(int i = 0; i < ui->entries->count(); ++i)
1011 {
1012 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
1013 if(entry && !entry->isHidden())
1014 {
1015 SendCoinsRecipient rcp = entry->getValue();
1017 if (rcp.fSubtractFeeFromAmount)
1019 }
1020 }
1021
1022 if (m_coin_control->HasSelected())
1023 {
1024 // actual coin control calculation
1026
1027 // show coin control stats
1028 ui->labelCoinControlAutomaticallySelected->hide();
1029 ui->widgetCoinControl->show();
1030 }
1031 else
1032 {
1033 // hide coin control stats
1034 ui->labelCoinControlAutomaticallySelected->show();
1035 ui->widgetCoinControl->hide();
1036 ui->labelCoinControlInsuffFunds->hide();
1037 }
1038}
1039
1040SendConfirmationDialog::SendConfirmationDialog(const QString& title, const QString& text, const QString& informative_text, const QString& detailed_text, int _secDelay, bool enable_send, bool always_show_unsigned, QWidget* parent)
1041 : QMessageBox(parent), secDelay(_secDelay), m_enable_send(enable_send)
1042{
1043 setIcon(QMessageBox::Question);
1044 setWindowTitle(title); // On macOS, the window title is ignored (as required by the macOS Guidelines).
1045 setText(text);
1046 setInformativeText(informative_text);
1047 setDetailedText(detailed_text);
1048 setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel);
1049 if (always_show_unsigned || !enable_send) addButton(QMessageBox::Save);
1050 setDefaultButton(QMessageBox::Cancel);
1051 yesButton = button(QMessageBox::Yes);
1052 if (confirmButtonText.isEmpty()) {
1053 confirmButtonText = yesButton->text();
1054 }
1055 m_psbt_button = button(QMessageBox::Save);
1056 updateButtons();
1057 connect(&countDownTimer, &QTimer::timeout, this, &SendConfirmationDialog::countDown);
1058}
1059
1061{
1062 updateButtons();
1063 countDownTimer.start(1s);
1064 return QMessageBox::exec();
1065}
1066
1068{
1069 secDelay--;
1070 updateButtons();
1071
1072 if(secDelay <= 0)
1073 {
1074 countDownTimer.stop();
1075 }
1076}
1077
1079{
1080 if(secDelay > 0)
1081 {
1082 yesButton->setEnabled(false);
1083 yesButton->setText(confirmButtonText + (m_enable_send ? (" (" + QString::number(secDelay) + ")") : QString("")));
1084 if (m_psbt_button) {
1085 m_psbt_button->setEnabled(false);
1086 m_psbt_button->setText(m_psbt_button_text + " (" + QString::number(secDelay) + ")");
1087 }
1088 }
1089 else
1090 {
1091 yesButton->setEnabled(m_enable_send);
1092 yesButton->setText(confirmButtonText);
1093 if (m_psbt_button) {
1094 m_psbt_button->setEnabled(true);
1096 }
1097 }
1098}
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.
Definition: addresstype.h:143
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.
Definition: bitcoinunits.h:42
@ MSG_INFORMATION
Predefined combinations for certain default usage cases.
Definition: interface_ui.h:62
Fee rate in satoshis per virtualbyte: CAmount / vB the feerate is represented internally as FeeFrac.
Definition: feerate.h:32
CAmount GetFeePerK() const
Return the fee in satoshis for a vsize of 1000 vbytes.
Definition: feerate.h:71
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:165
bool getCoinControlFeatures() const
Definition: optionsmodel.h:107
bool getEnablePSBTControls() const
Definition: optionsmodel.h:109
void coinControlFeaturesChanged(bool)
void displayUnitChanged(BitcoinUnit unit)
BitcoinUnit getDisplayUnit() const
Definition: optionsmodel.h:104
bool hasSigner()
Whether -signer was set or not.
A version of CTransaction with the PSBT format.
Definition: psbt.h:1239
QIcon SingleColorIcon(const QString &filename) const
Colorize an icon (given filename) with the icon color.
bool getImagesOnButtons() const
Definition: platformstyle.h:21
Dialog for sending bitcoins.
void useAvailableBalance(SendCoinsEntry *entry)
WalletModel * model
void presentPSBT(PartiallySignedTransaction &psbt)
ClientModel * clientModel
void coinControlChangeEdited(const QString &)
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()
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
std::unique_ptr< wallet::CCoinControl > m_coin_control
void coinControlClipboardQuantity()
void coinControlButtonClicked()
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 coinControlClipboardBytes()
void message(const QString &title, const QString &message, unsigned int style)
SendCoinsDialog(const PlatformStyle *platformStyle, QWidget *parent=nullptr)
void on_buttonMinimizeFee_clicked()
void coinControlUpdateLabels()
void coinControlFeatureChanged(bool)
void minimizeFeeSection(bool fMinimize)
A single entry in the dialog for sending bitcoins.
void setFocus()
void setAddress(const QString &address)
bool isClear()
Return whether the entry is still empty and unedited.
void subtractFeeFromAmountChanged()
void useAvailableBalance(SendCoinsEntry *entry)
void setValue(const SendCoinsRecipient &value)
void setModel(WalletModel *model)
void removeEntry(SendCoinsEntry *entry)
void payAmountChanged()
void setAmount(const CAmount &amount)
QWidget * setupTabChain(QWidget *prev)
Set up the tab chain manually, as Qt messes up the tab chain by default in some cases (issue https://...
void clear()
bool validate(interfaces::Node &node)
void checkSubtractFeeFromAmount()
SendCoinsRecipient getValue()
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
Definition: walletmodel.h:137
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
Definition: walletmodel.h:138
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
@ DuplicateAddress
Definition: walletmodel.h:62
virtual CAmount getRequiredFee(unsigned int tx_bytes)=0
Get required fee.
virtual unsigned int getConfirmTarget()=0
Get tx confirm target.
virtual bool hasExternalSigner()=0
virtual CAmount getDefaultMaxTxFee()=0
Get max tx fee.
virtual bool isSpendable(const CTxDestination &dest)=0
Return whether wallet has private key.
virtual CAmount getMinimumFee(unsigned int tx_bytes, const wallet::CCoinControl &coin_control, std::optional< int > *returned_target, FeeReason *reason)=0
Get minimum fee.
virtual std::optional< common::PSBTError > fillPSBT(const common::PSBTFillOptions &options, size_t *n_signed, PartiallySignedTransaction &psbtx, bool &complete)=0
Fill PSBT.
virtual bool privateKeysDisabled()=0
Coin Control Features.
Definition: coincontrol.h:83
bool HasSelected() const
Returns true if there are pre-selected inputs.
Definition: coincontrol.cpp:15
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:93
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:300
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:983
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:315
QString formatNiceTimeOffset(qint64 secs)
Definition: guiutil.cpp:783
constexpr auto dialog_flags
Definition: guiutil.h:60
auto ExceptionSafeConnect(Sender sender, Signal signal, Receiver receiver, Slot method, Qt::ConnectionType type=Qt::AutoConnection)
A drop-in replacement of QObject::connect function (see: https://doc.qt.io/qt-5/qobject....
Definition: guiutil.h: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:911
void setupAddressWidget(QValidatedLineEdit *widget, QWidget *parent)
Definition: guiutil.cpp:131
void setClipboard(const QString &str)
Definition: guiutil.cpp:663
PSBTError
Definition: types.h:19
is a home for public enum and struct type definitions that are used internally by node code,...
static CTransactionRef MakeTransactionRef(Tx &&txIn)
Definition: transaction.h:404
std::shared_ptr< const CTransaction > CTransactionRef
Definition: transaction.h:403
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:814
int getConfTargetForIndex(int index)
int getIndexForConfTarget(int target)
static constexpr std::array confTargets
#define SEND_CONFIRM_DELAY
A mutable version of CTransaction.
Definition: transaction.h:358
Collection of wallet balances.
Definition: wallet.h:367
static int count
FeeReason
Definition: fees.h:24
std::string EncodeBase64(std::span< const unsigned char > input)
assert(!tx.IsCoinBase())
SynchronizationState
Current sync state passed to tip changed callbacks.
Definition: validation.h:96
is a home for public enum and struct type definitions that are used by internally by wallet code,...