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>
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}
54int getIndexForConfTarget(int target) {
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
63SendCoinsDialog::SendCoinsDialog(const PlatformStyle *_platformStyle, QWidget *parent) :
64 QDialog(parent, GUIUtil::dialog_flags),
65 ui(new Ui::SendCoinsDialog),
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);
104 connect(clipboardQuantityAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardQuantity);
105 connect(clipboardAmountAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardAmount);
106 connect(clipboardFeeAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardFee);
107 connect(clipboardAfterFeeAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardAfterFee);
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
118 QSettings settings;
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
138{
139 this->clientModel = _clientModel;
140
141 if (_clientModel) {
143 }
144}
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
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 CAmount requiredFee = model->wallet().getRequiredFee(1000);
183 ui->customFee->SetMinValue(requiredFee);
184 if (ui->customFee->value() < requiredFee) {
185 ui->customFee->setValue(requiredFee);
186 }
187 ui->customFee->setSingleStep(requiredFee);
190
191 if (model->wallet().hasExternalSigner()) {
192 //: "device" usually means a hardware wallet.
193 ui->sendButton->setText(tr("Sign on device"));
194 if (model->getOptionsModel()->hasSigner()) {
195 ui->sendButton->setEnabled(true);
196 ui->sendButton->setToolTip(tr("Connect your hardware wallet first."));
197 } else {
198 ui->sendButton->setEnabled(false);
199 //: "External signer" means using devices such as hardware wallets.
200 ui->sendButton->setToolTip(tr("Set external signer script path in Options -> Wallet"));
201 }
202 } else if (model->wallet().privateKeysDisabled()) {
203 ui->sendButton->setText(tr("Cr&eate Unsigned"));
204 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));
205 }
206
207 // set the smartfee-sliders default value (wallets default conf.target or last stored value)
208 QSettings settings;
209 if (settings.value("nSmartFeeSliderPosition").toInt() != 0) {
210 // migrate nSmartFeeSliderPosition to nConfTarget
211 // nConfTarget is available since 0.15 (replaced nSmartFeeSliderPosition)
212 int nConfirmTarget = 25 - settings.value("nSmartFeeSliderPosition").toInt(); // 25 == old slider range
213 settings.setValue("nConfTarget", nConfirmTarget);
214 settings.remove("nSmartFeeSliderPosition");
215 }
216 if (settings.value("nConfTarget").toInt() == 0)
217 ui->confTargetSelector->setCurrentIndex(getIndexForConfTarget(model->wallet().getConfirmTarget()));
218 else
219 ui->confTargetSelector->setCurrentIndex(getIndexForConfTarget(settings.value("nConfTarget").toInt()));
220 }
221}
222
224{
225 QSettings settings;
226 settings.setValue("fFeeSectionMinimized", fFeeMinimized);
227 settings.setValue("nFeeRadio", ui->groupFee->checkedId());
228 settings.setValue("nConfTarget", getConfTargetForIndex(ui->confTargetSelector->currentIndex()));
229 settings.setValue("nTransactionFee", (qint64)ui->customFee->value());
230
231 delete ui;
232}
233
234bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informative_text, QString& detailed_text)
235{
236 QList<SendCoinsRecipient> recipients;
237 bool valid = true;
238
239 for(int i = 0; i < ui->entries->count(); ++i)
240 {
241 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
242 if(entry)
243 {
244 if(entry->validate(model->node()))
245 {
246 recipients.append(entry->getValue());
247 }
248 else if (valid)
249 {
250 ui->scrollArea->ensureWidgetVisible(entry);
251 valid = false;
252 }
253 }
254 }
255
256 if(!valid || recipients.isEmpty())
257 {
258 return false;
259 }
260
261 fNewRecipientAllowed = false;
263 if(!ctx.isValid())
264 {
265 // Unlock wallet was cancelled
267 return false;
268 }
269
270 // prepare transaction for getting txFee earlier
271 m_current_transaction = std::make_unique<WalletModelTransaction>(recipients);
272 WalletModel::SendCoinsReturn prepareStatus;
273
275
276 CCoinControl coin_control = *m_coin_control;
277 coin_control.m_allow_other_inputs = !coin_control.HasSelected(); // future, could introduce a checkbox to customize this value.
278 prepareStatus = model->prepareTransaction(*m_current_transaction, coin_control);
279
280 // process prepareStatus and on error generate message shown to user
281 processSendCoinsReturn(prepareStatus,
283
284 if(prepareStatus.status != WalletModel::OK) {
286 return false;
287 }
288
289 CAmount txFee = m_current_transaction->getTransactionFee();
290 QStringList formatted;
291 for (const SendCoinsRecipient &rcp : m_current_transaction->getRecipients())
292 {
293 // generate amount string with wallet name in case of multiwallet
294 QString amount = BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
295 if (model->isMultiwallet()) {
296 amount = tr("%1 from wallet '%2'").arg(amount, GUIUtil::HtmlEscape(model->getWalletName()));
297 }
298
299 // generate address string
300 QString address = rcp.address;
301
302 QString recipientElement;
303
304 {
305 if(rcp.label.length() > 0) // label with address
306 {
307 recipientElement.append(tr("%1 to '%2'").arg(amount, GUIUtil::HtmlEscape(rcp.label)));
308 recipientElement.append(QString(" (%1)").arg(address));
309 }
310 else // just address
311 {
312 recipientElement.append(tr("%1 to %2").arg(amount, address));
313 }
314 }
315 formatted.append(recipientElement);
316 }
317
318 /*: Message displayed when attempting to create a transaction. Cautionary text to prompt the user to verify
319 that the displayed transaction details represent the transaction the user intends to create. */
320 question_string.append(tr("Do you want to create this transaction?"));
321 question_string.append("<br /><span style='font-size:10pt;'>");
323 /*: Text to inform a user attempting to create a transaction of their current options. At this stage,
324 a user can only create a PSBT. This string is displayed when private keys are disabled and an external
325 signer is not available. */
326 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));
328 /*: Text to inform a user attempting to create a transaction of their current options. At this stage,
329 a user can send their transaction or create a PSBT. This string is displayed when both private keys
330 and PSBT controls are enabled. */
331 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));
332 } else {
333 /*: Text to prompt a user to review the details of the transaction they are attempting to send. */
334 question_string.append(tr("Please, review your transaction."));
335 }
336 question_string.append("</span>%1");
337
338 if(txFee > 0)
339 {
340 // append fee string if a fee is required
341 question_string.append("<hr /><b>");
342 question_string.append(tr("Transaction fee"));
343 question_string.append("</b>");
344
345 // append transaction size
346 //: When reviewing a newly created PSBT (via Send flow), the transaction fee is shown, with "virtual size" of the transaction displayed for context
347 question_string.append(" (" + tr("%1 kvB", "PSBT transaction creation").arg((double)m_current_transaction->getTransactionSize() / 1000, 0, 'g', 3) + "): ");
348
349 // append transaction fee value
350 question_string.append("<span style='color:#aa0000; font-weight:bold;'>");
351 question_string.append(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), txFee));
352 question_string.append("</span><br />");
353 }
354
355 // append RBF message
356 question_string.append("<span style='font-size:10pt; font-weight:normal;'>");
357 question_string.append(tr("You can increase the fee later."));
358
359 // add total amount in all subdivision units
360 question_string.append("<hr />");
361 CAmount totalAmount = m_current_transaction->getTotalTransactionAmount() + txFee;
362 QStringList alternativeUnits;
363 for (const BitcoinUnit u : BitcoinUnits::availableUnits()) {
365 alternativeUnits.append(BitcoinUnits::formatHtmlWithUnit(u, totalAmount));
366 }
367 question_string.append(QString("<b>%1</b>: <b>%2</b>").arg(tr("Total Amount"))
369 question_string.append(QString("<br /><span style='font-size:10pt; font-weight:normal;'>(=%1)</span>")
370 .arg(alternativeUnits.join(" " + tr("or") + " ")));
371
372 if (formatted.size() > 1) {
373 question_string = question_string.arg("");
374 informative_text = tr("To review recipient list click \"Show Details…\"");
375 detailed_text = formatted.join("\n\n");
376 } else {
377 question_string = question_string.arg("<br /><br />" + formatted.at(0));
378 }
379
380 return true;
381}
382
384{
385 // Serialize the PSBT
386 DataStream ssTx{};
387 ssTx << psbtx;
388 GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str());
389 QMessageBox msgBox(this);
390 //: Caption of "PSBT has been copied" messagebox
391 msgBox.setText(tr("Unsigned Transaction", "PSBT copied"));
392 msgBox.setInformativeText(tr("The PSBT has been copied to the clipboard. You can also save it."));
393 msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard);
394 msgBox.setDefaultButton(QMessageBox::Discard);
395 msgBox.setObjectName("psbt_copied_message");
396 switch (msgBox.exec()) {
397 case QMessageBox::Save: {
398 QString selectedFilter;
399 QString fileNameSuggestion = "";
400 bool first = true;
401 for (const SendCoinsRecipient &rcp : m_current_transaction->getRecipients()) {
402 if (!first) {
403 fileNameSuggestion.append(" - ");
404 }
405 QString labelOrAddress = rcp.label.isEmpty() ? rcp.address : rcp.label;
406 QString amount = BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
407 fileNameSuggestion.append(labelOrAddress + "-" + amount);
408 first = false;
409 }
410 fileNameSuggestion.append(".psbt");
411 QString filename = GUIUtil::getSaveFileName(this,
412 tr("Save Transaction Data"), fileNameSuggestion,
413 //: Expanded name of the binary PSBT file format. See: BIP 174.
414 tr("Partially Signed Transaction (Binary)") + QLatin1String(" (*.psbt)"), &selectedFilter);
415 if (filename.isEmpty()) {
416 return;
417 }
418 std::ofstream out{filename.toLocal8Bit().data(), std::ofstream::out | std::ofstream::binary};
419 out << ssTx.str();
420 out.close();
421 //: Popup message when a PSBT has been saved to a file
422 Q_EMIT message(tr("PSBT saved"), tr("PSBT saved to disk"), CClientUIInterface::MSG_INFORMATION);
423 break;
424 }
425 case QMessageBox::Discard:
426 break;
427 default:
428 assert(false);
429 } // msgBox.exec()
430}
431
433 std::optional<PSBTError> err;
434 try {
435 err = model->wallet().fillPSBT({.sign = true, .bip32_derivs = true}, /*n_signed=*/nullptr, psbtx, complete);
436 } catch (const std::runtime_error& e) {
437 QMessageBox::critical(nullptr, tr("Sign failed"), e.what());
438 return false;
439 }
440 if (err == PSBTError::EXTERNAL_SIGNER_NOT_FOUND) {
441 //: "External signer" means using devices such as hardware wallets.
442 const QString msg = tr("External signer not found");
443 QMessageBox::critical(nullptr, msg, msg);
444 return false;
445 }
446 if (err == PSBTError::EXTERNAL_SIGNER_FAILED) {
447 //: "External signer" means using devices such as hardware wallets.
448 const QString msg = tr("External signer failure");
449 QMessageBox::critical(nullptr, msg, msg);
450 return false;
451 }
452 if (err) {
453 qWarning() << "Failed to sign PSBT";
455 return false;
456 }
457 // fillPSBT does not always properly finalize
458 complete = FinalizeAndExtractPSBT(psbtx, mtx);
459 return true;
460}
461
462void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
463{
464 if(!model || !model->getOptionsModel())
465 return;
466
467 QString question_string, informative_text, detailed_text;
468 if (!PrepareSendText(question_string, informative_text, detailed_text)) return;
470
471 const QString confirmation = tr("Confirm send coins");
472 const bool enable_send{!model->wallet().privateKeysDisabled() || model->wallet().hasExternalSigner()};
473 const bool always_show_unsigned{model->getOptionsModel()->getEnablePSBTControls()};
474 auto confirmationDialog = new SendConfirmationDialog(confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, enable_send, always_show_unsigned, this);
475 confirmationDialog->setAttribute(Qt::WA_DeleteOnClose);
476 // TODO: Replace QDialog::exec() with safer QDialog::show().
477 const auto retval = static_cast<QMessageBox::StandardButton>(confirmationDialog->exec());
478
479 if(retval != QMessageBox::Yes && retval != QMessageBox::Save)
480 {
482 return;
483 }
484
485 bool send_failure = false;
486 if (retval == QMessageBox::Save) {
487 // "Create Unsigned" clicked
490 bool complete = false;
491 // Fill without signing
492 const auto err{model->wallet().fillPSBT({.sign = false, .bip32_derivs = true}, /*n_signed=*/nullptr, psbtx, complete)};
493 assert(!complete);
494 assert(!err);
495
496 // Copy PSBT to clipboard and offer to save
497 presentPSBT(psbtx);
498 } else {
499 // "Send" clicked
501 bool broadcast = true;
502 if (model->wallet().hasExternalSigner()) {
505 bool complete = false;
506 // Always fill without signing first. This prevents an external signer
507 // from being called prematurely and is not expensive.
508 const auto err{model->wallet().fillPSBT({.sign = false, .bip32_derivs = true}, /*n_signed=*/nullptr, psbtx, complete)};
509 assert(!complete);
510 assert(!err);
511 send_failure = !signWithExternalSigner(psbtx, mtx, complete);
512 // Don't broadcast when user rejects it on the device or there's a failure:
513 broadcast = complete && !send_failure;
514 if (!send_failure) {
515 // A transaction signed with an external signer is not always complete,
516 // e.g. in a multisig wallet.
517 if (complete) {
518 // Prepare transaction for broadcast transaction if complete
519 const CTransactionRef tx = MakeTransactionRef(mtx);
520 m_current_transaction->setWtx(tx);
521 } else {
522 presentPSBT(psbtx);
523 }
524 }
525 }
526
527 // Broadcast the transaction, unless an external signer was used and it
528 // failed, or more signatures are needed.
529 if (broadcast) {
530 // now send the prepared transaction
532 Q_EMIT coinsSent(m_current_transaction->getWtx()->GetHash());
533 }
534 }
535 if (!send_failure) {
536 accept();
537 m_coin_control->UnSelectAll();
539 }
541 m_current_transaction.reset();
542}
543
545{
546 m_current_transaction.reset();
547
548 // Clear coin control settings
549 m_coin_control->UnSelectAll();
550 ui->checkBoxCoinControlChange->setChecked(false);
551 ui->lineEditCoinControlChange->clear();
553
554 // Remove entries until only one left
555 while(ui->entries->count())
556 {
557 ui->entries->takeAt(0)->widget()->deleteLater();
558 }
559 addEntry();
560
562}
563
565{
566 clear();
567}
568
570{
571 clear();
572}
573
575{
576 SendCoinsEntry *entry = new SendCoinsEntry(platformStyle, this);
577 entry->setModel(model);
578 ui->entries->addWidget(entry);
583
584 // Focus the field, so that entry can start immediately
585 entry->clear();
586 entry->setFocus();
587 ui->scrollAreaWidgetContents->resize(ui->scrollAreaWidgetContents->sizeHint());
588
589 // Scroll to the newly added entry on a QueuedConnection because Qt doesn't
590 // adjust the scroll area and scrollbar immediately when the widget is added.
591 // Invoking on a DirectConnection will only scroll to the second-to-last entry.
592 QMetaObject::invokeMethod(ui->scrollArea, [this] {
593 if (ui->scrollArea->verticalScrollBar()) {
594 ui->scrollArea->verticalScrollBar()->setValue(ui->scrollArea->verticalScrollBar()->maximum());
595 }
596 }, Qt::QueuedConnection);
597
598 updateTabsAndLabels();
599 return entry;
600}
601
603{
604 setupTabChain(nullptr);
606}
607
609{
610 entry->hide();
611
612 // If the last entry is about to be removed add an empty one
613 if (ui->entries->count() == 1)
614 addEntry();
615
616 entry->deleteLater();
617
619}
620
621QWidget *SendCoinsDialog::setupTabChain(QWidget *prev)
622{
623 for(int i = 0; i < ui->entries->count(); ++i)
624 {
625 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
626 if(entry)
627 {
628 prev = entry->setupTabChain(prev);
629 }
630 }
631 QWidget::setTabOrder(prev, ui->sendButton);
632 QWidget::setTabOrder(ui->sendButton, ui->clearButton);
633 QWidget::setTabOrder(ui->clearButton, ui->addButton);
634 return ui->addButton;
635}
636
637void SendCoinsDialog::setAddress(const QString &address)
638{
639 SendCoinsEntry *entry = nullptr;
640 // Replace the first entry if it is still unused
641 if(ui->entries->count() == 1)
642 {
643 SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
644 if(first->isClear())
645 {
646 entry = first;
647 }
648 }
649 if(!entry)
650 {
651 entry = addEntry();
652 }
653
654 entry->setAddress(address);
655}
656
658{
660 return;
661
662 SendCoinsEntry *entry = nullptr;
663 // Replace the first entry if it is still unused
664 if(ui->entries->count() == 1)
665 {
666 SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
667 if(first->isClear())
668 {
669 entry = first;
670 }
671 }
672 if(!entry)
673 {
674 entry = addEntry();
675 }
676
677 entry->setValue(rv);
679}
680
682{
683 // Just paste the entry, all pre-checks
684 // are done in paymentserver.cpp.
685 pasteEntry(rv);
686 return true;
687}
688
690{
691 if(model && model->getOptionsModel())
692 {
693 CAmount balance = balances.balance;
694 if (model->wallet().hasExternalSigner()) {
695 ui->labelBalanceName->setText(tr("External balance:"));
696 }
697 ui->labelBalance->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), balance));
698 }
699}
700
702{
704 ui->customFee->setDisplayUnit(model->getOptionsModel()->getDisplayUnit());
706}
707
708void SendCoinsDialog::processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg)
709{
710 QPair<QString, CClientUIInterface::MessageBoxFlags> msgParams;
711 // Default to a warning message, override if error message is needed
712 msgParams.second = CClientUIInterface::MSG_WARNING;
713
714 // This comment is specific to SendCoinsDialog usage of WalletModel::SendCoinsReturn.
715 // All status values are used only in WalletModel::prepareTransaction()
716 switch(sendCoinsReturn.status)
717 {
719 msgParams.first = tr("The recipient address is not valid. Please recheck.");
720 break;
722 msgParams.first = tr("The amount to pay must be larger than 0.");
723 break;
725 msgParams.first = tr("The amount exceeds your balance.");
726 break;
728 msgParams.first = tr("Duplicate address found: addresses should only be used once each.");
729 break;
731 msgParams.first = tr("Transaction creation failed!");
732 msgParams.second = CClientUIInterface::MSG_ERROR;
733 break;
735 msgParams.first = tr("A fee higher than %1 is considered an absurdly high fee.").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), model->wallet().getDefaultMaxTxFee()));
736 break;
737 case WalletModel::OK:
738 return;
739 } // no default case, so the compiler can warn about missing cases
740 Q_EMIT message(tr("Send Coins"), msgParams.first, msgParams.second);
741}
742
744{
745 ui->labelFeeMinimized->setVisible(fMinimize);
746 ui->buttonChooseFee ->setVisible(fMinimize);
747 ui->buttonMinimizeFee->setVisible(!fMinimize);
748 ui->frameFeeSelection->setVisible(!fMinimize);
749 ui->horizontalLayoutSmartFee->setContentsMargins(0, (fMinimize ? 0 : 6), 0, 0);
750 fFeeMinimized = fMinimize;
751}
752
754{
755 minimizeFeeSection(false);
756}
757
759{
761 minimizeFeeSection(true);
762}
763
765{
766 // Same behavior as send: if we have selected coins, only obtain their available balance.
767 // Copy to avoid modifying the member's data.
768 CCoinControl coin_control = *m_coin_control;
769 coin_control.m_allow_other_inputs = !coin_control.HasSelected();
770
771 // Calculate available amount to send.
772 CAmount amount = model->getAvailableBalance(&coin_control);
773 for (int i = 0; i < ui->entries->count(); ++i) {
774 SendCoinsEntry* e = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
775 if (e && !e->isHidden() && e != entry) {
776 amount -= e->getValue().amount;
777 }
778 }
779
780 if (amount > 0) {
782 entry->setAmount(amount);
783 } else {
784 entry->setAmount(0);
785 }
786}
787
789{
790 ui->confTargetSelector ->setEnabled(ui->radioSmartFee->isChecked());
791 ui->labelSmartFee ->setEnabled(ui->radioSmartFee->isChecked());
792 ui->labelSmartFee2 ->setEnabled(ui->radioSmartFee->isChecked());
793 ui->labelSmartFee3 ->setEnabled(ui->radioSmartFee->isChecked());
794 ui->labelFeeEstimation ->setEnabled(ui->radioSmartFee->isChecked());
795 ui->labelCustomFeeWarning ->setEnabled(ui->radioCustomFee->isChecked());
796 ui->labelCustomPerKilobyte ->setEnabled(ui->radioCustomFee->isChecked());
797 ui->customFee ->setEnabled(ui->radioCustomFee->isChecked());
798}
799
801{
802 if(!model || !model->getOptionsModel())
803 return;
804
805 if (ui->radioSmartFee->isChecked())
806 ui->labelFeeMinimized->setText(ui->labelSmartFee->text());
807 else {
808 ui->labelFeeMinimized->setText(tr("%1/kvB").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), ui->customFee->value())));
809 }
810}
811
813{
814 if (ui->radioCustomFee->isChecked()) {
815 m_coin_control->m_feerate = CFeeRate(ui->customFee->value());
816 } else {
817 m_coin_control->m_feerate.reset();
818 }
819 // Avoid using global defaults when sending money from the GUI
820 // Either custom fee will be used or if not selected, the confirmation target from dropdown box
821 m_coin_control->m_confirm_target = getConfTargetForIndex(ui->confTargetSelector->currentIndex());
822}
823
824void SendCoinsDialog::updateNumberOfBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, SyncType synctype, SynchronizationState sync_state) {
825 // During shutdown, clientModel will be nullptr. Attempting to update views at this point may cause a crash
826 // due to accessing backend models that might no longer exist.
827 if (!clientModel) return;
828 // Process event
829 if (sync_state == SynchronizationState::POST_INIT) {
831 }
832}
833
835{
836 if(!model || !model->getOptionsModel())
837 return;
839 m_coin_control->m_feerate.reset(); // Explicitly use only fee estimation rate for smart fee labels
840 int returned_target;
841 FeeReason reason;
842 CFeeRate feeRate = CFeeRate(model->wallet().getMinimumFee(1000, *m_coin_control, &returned_target, &reason));
843
844 ui->labelSmartFee->setText(tr("%1/kvB").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), feeRate.GetFeePerK())));
845
846 if (reason == FeeReason::FALLBACK) {
847 ui->labelSmartFee2->show(); // (Smart fee not initialized yet. This usually takes a few blocks...)
848 ui->labelFeeEstimation->setText("");
849 ui->fallbackFeeWarningLabel->setVisible(true);
850 int lightness = ui->fallbackFeeWarningLabel->palette().color(QPalette::WindowText).lightness();
851 QColor warning_colour(255 - (lightness / 5), 176 - (lightness / 3), 48 - (lightness / 14));
852 ui->fallbackFeeWarningLabel->setStyleSheet("QLabel { color: " + warning_colour.name() + "; }");
853 ui->fallbackFeeWarningLabel->setIndent(GUIUtil::TextWidth(QFontMetrics(ui->fallbackFeeWarningLabel->font()), "x"));
854 }
855 else
856 {
857 ui->labelSmartFee2->hide();
858 ui->labelFeeEstimation->setText(tr("Estimated to begin confirmation within %n block(s).", "", returned_target));
859 ui->fallbackFeeWarningLabel->setVisible(false);
860 }
861
863}
864
865// Coin Control: copy label "Quantity" to clipboard
867{
868 GUIUtil::setClipboard(ui->labelCoinControlQuantity->text());
869}
870
871// Coin Control: copy label "Amount" to clipboard
873{
874 GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left(ui->labelCoinControlAmount->text().indexOf(" ")));
875}
876
877// Coin Control: copy label "Fee" to clipboard
879{
880 GUIUtil::setClipboard(ui->labelCoinControlFee->text().left(ui->labelCoinControlFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
881}
882
883// Coin Control: copy label "After fee" to clipboard
885{
886 GUIUtil::setClipboard(ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
887}
888
889// Coin Control: copy label "Bytes" to clipboard
891{
892 GUIUtil::setClipboard(ui->labelCoinControlBytes->text().replace(ASYMP_UTF8, ""));
893}
894
895// Coin Control: copy label "Change" to clipboard
897{
898 GUIUtil::setClipboard(ui->labelCoinControlChange->text().left(ui->labelCoinControlChange->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
899}
900
901// Coin Control: settings menu - coin control enabled/disabled by user
903{
904 ui->frameCoinControl->setVisible(checked);
905
906 if (!checked && model) { // coin control features disabled
907 m_coin_control = std::make_unique<CCoinControl>();
908 }
909
911}
912
913// Coin Control: button inputs -> show actual coin control dialog
915{
917 connect(dlg, &QDialog::finished, this, &SendCoinsDialog::coinControlUpdateLabels);
919}
920
921// Coin Control: checkbox custom change address
922#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0))
924#else
926#endif
927{
928 if (state == Qt::Unchecked)
929 {
930 m_coin_control->destChange = CNoDestination();
931 ui->labelCoinControlChangeLabel->clear();
932 }
933 else
934 // use this to re-validate an already entered address
935 coinControlChangeEdited(ui->lineEditCoinControlChange->text());
936
937 ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked));
938}
939
940// Coin Control: custom change address changed
942{
944 {
945 // Default to no change address until verified
946 m_coin_control->destChange = CNoDestination();
947 ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:red;}");
948
949 const CTxDestination dest = DecodeDestination(text.toStdString());
950
951 if (text.isEmpty()) // Nothing entered
952 {
953 ui->labelCoinControlChangeLabel->setText("");
954 }
955 else if (!IsValidDestination(dest)) // Invalid address
956 {
957 ui->labelCoinControlChangeLabel->setText(tr("Warning: Invalid Bitcoin address"));
958 }
959 else // Valid address
960 {
961 if (!model->wallet().isSpendable(dest)) {
962 ui->labelCoinControlChangeLabel->setText(tr("Warning: Unknown change address"));
963
964 // confirmation dialog
965 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?"),
966 QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel);
967
968 if(btnRetVal == QMessageBox::Yes)
969 m_coin_control->destChange = dest;
970 else
971 {
972 ui->lineEditCoinControlChange->setText("");
973 ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
974 ui->labelCoinControlChangeLabel->setText("");
975 }
976 }
977 else // Known change address
978 {
979 ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
980
981 // Query label
982 QString associatedLabel = model->getAddressTableModel()->labelForAddress(text);
983 if (!associatedLabel.isEmpty())
984 ui->labelCoinControlChangeLabel->setText(associatedLabel);
985 else
986 ui->labelCoinControlChangeLabel->setText(tr("(no label)"));
987
988 m_coin_control->destChange = dest;
989 }
990 }
991 }
992}
993
994// Coin Control: update labels
996{
997 if (!model || !model->getOptionsModel())
998 return;
999
1001
1002 // set pay amounts
1005
1006 for(int i = 0; i < ui->entries->count(); ++i)
1007 {
1008 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
1009 if(entry && !entry->isHidden())
1010 {
1011 SendCoinsRecipient rcp = entry->getValue();
1013 if (rcp.fSubtractFeeFromAmount)
1015 }
1016 }
1017
1018 if (m_coin_control->HasSelected())
1019 {
1020 // actual coin control calculation
1022
1023 // show coin control stats
1024 ui->labelCoinControlAutomaticallySelected->hide();
1025 ui->widgetCoinControl->show();
1026 }
1027 else
1028 {
1029 // hide coin control stats
1030 ui->labelCoinControlAutomaticallySelected->show();
1031 ui->widgetCoinControl->hide();
1032 ui->labelCoinControlInsuffFunds->hide();
1033 }
1034}
1035
1036SendConfirmationDialog::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)
1037 : QMessageBox(parent), secDelay(_secDelay), m_enable_send(enable_send)
1038{
1039 setIcon(QMessageBox::Question);
1040 setWindowTitle(title); // On macOS, the window title is ignored (as required by the macOS Guidelines).
1041 setText(text);
1042 setInformativeText(informative_text);
1043 setDetailedText(detailed_text);
1044 setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel);
1045 if (always_show_unsigned || !enable_send) addButton(QMessageBox::Save);
1046 setDefaultButton(QMessageBox::Cancel);
1047 yesButton = button(QMessageBox::Yes);
1048 if (confirmButtonText.isEmpty()) {
1049 confirmButtonText = yesButton->text();
1050 }
1051 m_psbt_button = button(QMessageBox::Save);
1052 updateButtons();
1053 connect(&countDownTimer, &QTimer::timeout, this, &SendConfirmationDialog::countDown);
1054}
1055
1057{
1058 updateButtons();
1059 countDownTimer.start(1s);
1060 return QMessageBox::exec();
1061}
1062
1064{
1065 secDelay--;
1066 updateButtons();
1067
1068 if(secDelay <= 0)
1069 {
1070 countDownTimer.stop();
1071 }
1072}
1073
1075{
1076 if(secDelay > 0)
1077 {
1078 yesButton->setEnabled(false);
1079 yesButton->setText(confirmButtonText + (m_enable_send ? (" (" + QString::number(secDelay) + ")") : QString("")));
1080 if (m_psbt_button) {
1081 m_psbt_button->setEnabled(false);
1082 m_psbt_button->setText(m_psbt_button_text + " (" + QString::number(secDelay) + ")");
1083 }
1084 }
1085 else
1086 {
1087 yesButton->setEnabled(m_enable_send);
1088 yesButton->setText(confirmButtonText);
1089 if (m_psbt_button) {
1090 m_psbt_button->setEnabled(true);
1092 }
1093 }
1094}
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:64
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:62
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:1240
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 std::optional< common::PSBTError > fillPSBT(const common::PSBTFillOptions &options, size_t *n_signed, PartiallySignedTransaction &psbtx, bool &complete)=0
Fill PSBT.
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 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: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: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:977
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:781
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:909
void setupAddressWidget(QValidatedLineEdit *widget, QWidget *parent)
Definition: guiutil.cpp:131
void setClipboard(const QString &str)
Definition: guiutil.cpp:661
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:820
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
std::string EncodeBase64(std::span< const unsigned char > input)
assert(!tx.IsCoinBase())
SynchronizationState
Current sync state passed to tip changed callbacks.
Definition: validation.h:93