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