Bitcoin Core 29.99.0
P2P Digital Currency
sendcoinsdialog.cpp
Go to the documentation of this file.
1// Copyright (c) 2011-2022 The Bitcoin Core developers
2// Distributed under the MIT software license, see the accompanying
3// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4
5#include <bitcoin-build-config.h> // IWYU pragma: keep
6
8#include <qt/forms/ui_sendcoinsdialog.h>
9
11#include <qt/bitcoinunits.h>
12#include <qt/clientmodel.h>
14#include <qt/guiutil.h>
15#include <qt/optionsmodel.h>
16#include <qt/platformstyle.h>
17#include <qt/sendcoinsentry.h>
18
19#include <chainparams.h>
20#include <interfaces/node.h>
21#include <key_io.h>
22#include <node/interface_ui.h>
23#include <node/types.h>
24#include <policy/fees.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(SIGHASH_ALL, /*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(SIGHASH_ALL, /*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(SIGHASH_ALL, /*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("The total exceeds your balance when the %1 transaction fee is included.").arg(msgArg);
747 break;
749 msgParams.first = tr("Duplicate address found: addresses should only be used once each.");
750 break;
752 msgParams.first = tr("Transaction creation failed!");
753 msgParams.second = CClientUIInterface::MSG_ERROR;
754 break;
756 msgParams.first = tr("A fee higher than %1 is considered an absurdly high fee.").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), model->wallet().getDefaultMaxTxFee()));
757 break;
758 // included to prevent a compiler warning.
759 case WalletModel::OK:
760 default:
761 return;
762 }
763
764 Q_EMIT message(tr("Send Coins"), msgParams.first, msgParams.second);
765}
766
768{
769 ui->labelFeeMinimized->setVisible(fMinimize);
770 ui->buttonChooseFee ->setVisible(fMinimize);
771 ui->buttonMinimizeFee->setVisible(!fMinimize);
772 ui->frameFeeSelection->setVisible(!fMinimize);
773 ui->horizontalLayoutSmartFee->setContentsMargins(0, (fMinimize ? 0 : 6), 0, 0);
774 fFeeMinimized = fMinimize;
775}
776
778{
779 minimizeFeeSection(false);
780}
781
783{
785 minimizeFeeSection(true);
786}
787
789{
790 // Include watch-only for wallets without private key
792
793 // Same behavior as send: if we have selected coins, only obtain their available balance.
794 // Copy to avoid modifying the member's data.
795 CCoinControl coin_control = *m_coin_control;
796 coin_control.m_allow_other_inputs = !coin_control.HasSelected();
797
798 // Calculate available amount to send.
799 CAmount amount = model->getAvailableBalance(&coin_control);
800 for (int i = 0; i < ui->entries->count(); ++i) {
801 SendCoinsEntry* e = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
802 if (e && !e->isHidden() && e != entry) {
803 amount -= e->getValue().amount;
804 }
805 }
806
807 if (amount > 0) {
809 entry->setAmount(amount);
810 } else {
811 entry->setAmount(0);
812 }
813}
814
816{
817 ui->confTargetSelector ->setEnabled(ui->radioSmartFee->isChecked());
818 ui->labelSmartFee ->setEnabled(ui->radioSmartFee->isChecked());
819 ui->labelSmartFee2 ->setEnabled(ui->radioSmartFee->isChecked());
820 ui->labelSmartFee3 ->setEnabled(ui->radioSmartFee->isChecked());
821 ui->labelFeeEstimation ->setEnabled(ui->radioSmartFee->isChecked());
822 ui->labelCustomFeeWarning ->setEnabled(ui->radioCustomFee->isChecked());
823 ui->labelCustomPerKilobyte ->setEnabled(ui->radioCustomFee->isChecked());
824 ui->customFee ->setEnabled(ui->radioCustomFee->isChecked());
825}
826
828{
829 if(!model || !model->getOptionsModel())
830 return;
831
832 if (ui->radioSmartFee->isChecked())
833 ui->labelFeeMinimized->setText(ui->labelSmartFee->text());
834 else {
835 ui->labelFeeMinimized->setText(tr("%1/kvB").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), ui->customFee->value())));
836 }
837}
838
840{
841 if (ui->radioCustomFee->isChecked()) {
842 m_coin_control->m_feerate = CFeeRate(ui->customFee->value());
843 } else {
844 m_coin_control->m_feerate.reset();
845 }
846 // Avoid using global defaults when sending money from the GUI
847 // Either custom fee will be used or if not selected, the confirmation target from dropdown box
848 m_coin_control->m_confirm_target = getConfTargetForIndex(ui->confTargetSelector->currentIndex());
849 m_coin_control->m_signal_bip125_rbf = ui->optInRBF->isChecked();
850 // Include watch-only for wallets without private key
852}
853
854void SendCoinsDialog::updateNumberOfBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, SyncType synctype, SynchronizationState sync_state) {
855 // During shutdown, clientModel will be nullptr. Attempting to update views at this point may cause a crash
856 // due to accessing backend models that might no longer exist.
857 if (!clientModel) return;
858 // Process event
859 if (sync_state == SynchronizationState::POST_INIT) {
861 }
862}
863
865{
866 if(!model || !model->getOptionsModel())
867 return;
869 m_coin_control->m_feerate.reset(); // Explicitly use only fee estimation rate for smart fee labels
870 int returned_target;
871 FeeReason reason;
872 CFeeRate feeRate = CFeeRate(model->wallet().getMinimumFee(1000, *m_coin_control, &returned_target, &reason));
873
874 ui->labelSmartFee->setText(tr("%1/kvB").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), feeRate.GetFeePerK())));
875
876 if (reason == FeeReason::FALLBACK) {
877 ui->labelSmartFee2->show(); // (Smart fee not initialized yet. This usually takes a few blocks...)
878 ui->labelFeeEstimation->setText("");
879 ui->fallbackFeeWarningLabel->setVisible(true);
880 int lightness = ui->fallbackFeeWarningLabel->palette().color(QPalette::WindowText).lightness();
881 QColor warning_colour(255 - (lightness / 5), 176 - (lightness / 3), 48 - (lightness / 14));
882 ui->fallbackFeeWarningLabel->setStyleSheet("QLabel { color: " + warning_colour.name() + "; }");
883 ui->fallbackFeeWarningLabel->setIndent(GUIUtil::TextWidth(QFontMetrics(ui->fallbackFeeWarningLabel->font()), "x"));
884 }
885 else
886 {
887 ui->labelSmartFee2->hide();
888 ui->labelFeeEstimation->setText(tr("Estimated to begin confirmation within %n block(s).", "", returned_target));
889 ui->fallbackFeeWarningLabel->setVisible(false);
890 }
891
893}
894
895// Coin Control: copy label "Quantity" to clipboard
897{
898 GUIUtil::setClipboard(ui->labelCoinControlQuantity->text());
899}
900
901// Coin Control: copy label "Amount" to clipboard
903{
904 GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left(ui->labelCoinControlAmount->text().indexOf(" ")));
905}
906
907// Coin Control: copy label "Fee" to clipboard
909{
910 GUIUtil::setClipboard(ui->labelCoinControlFee->text().left(ui->labelCoinControlFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
911}
912
913// Coin Control: copy label "After fee" to clipboard
915{
916 GUIUtil::setClipboard(ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
917}
918
919// Coin Control: copy label "Bytes" to clipboard
921{
922 GUIUtil::setClipboard(ui->labelCoinControlBytes->text().replace(ASYMP_UTF8, ""));
923}
924
925// Coin Control: copy label "Change" to clipboard
927{
928 GUIUtil::setClipboard(ui->labelCoinControlChange->text().left(ui->labelCoinControlChange->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
929}
930
931// Coin Control: settings menu - coin control enabled/disabled by user
933{
934 ui->frameCoinControl->setVisible(checked);
935
936 if (!checked && model) { // coin control features disabled
937 m_coin_control = std::make_unique<CCoinControl>();
938 }
939
941}
942
943// Coin Control: button inputs -> show actual coin control dialog
945{
947 connect(dlg, &QDialog::finished, this, &SendCoinsDialog::coinControlUpdateLabels);
949}
950
951// Coin Control: checkbox custom change address
952#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0))
954#else
956#endif
957{
958 if (state == Qt::Unchecked)
959 {
960 m_coin_control->destChange = CNoDestination();
961 ui->labelCoinControlChangeLabel->clear();
962 }
963 else
964 // use this to re-validate an already entered address
965 coinControlChangeEdited(ui->lineEditCoinControlChange->text());
966
967 ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked));
968}
969
970// Coin Control: custom change address changed
972{
974 {
975 // Default to no change address until verified
976 m_coin_control->destChange = CNoDestination();
977 ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:red;}");
978
979 const CTxDestination dest = DecodeDestination(text.toStdString());
980
981 if (text.isEmpty()) // Nothing entered
982 {
983 ui->labelCoinControlChangeLabel->setText("");
984 }
985 else if (!IsValidDestination(dest)) // Invalid address
986 {
987 ui->labelCoinControlChangeLabel->setText(tr("Warning: Invalid Bitcoin address"));
988 }
989 else // Valid address
990 {
991 if (!model->wallet().isSpendable(dest)) {
992 ui->labelCoinControlChangeLabel->setText(tr("Warning: Unknown change address"));
993
994 // confirmation dialog
995 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?"),
996 QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel);
997
998 if(btnRetVal == QMessageBox::Yes)
999 m_coin_control->destChange = dest;
1000 else
1001 {
1002 ui->lineEditCoinControlChange->setText("");
1003 ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
1004 ui->labelCoinControlChangeLabel->setText("");
1005 }
1006 }
1007 else // Known change address
1008 {
1009 ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
1010
1011 // Query label
1012 QString associatedLabel = model->getAddressTableModel()->labelForAddress(text);
1013 if (!associatedLabel.isEmpty())
1014 ui->labelCoinControlChangeLabel->setText(associatedLabel);
1015 else
1016 ui->labelCoinControlChangeLabel->setText(tr("(no label)"));
1017
1018 m_coin_control->destChange = dest;
1019 }
1020 }
1021 }
1022}
1023
1024// Coin Control: update labels
1026{
1027 if (!model || !model->getOptionsModel())
1028 return;
1029
1031
1032 // set pay amounts
1035
1036 for(int i = 0; i < ui->entries->count(); ++i)
1037 {
1038 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
1039 if(entry && !entry->isHidden())
1040 {
1041 SendCoinsRecipient rcp = entry->getValue();
1043 if (rcp.fSubtractFeeFromAmount)
1045 }
1046 }
1047
1048 if (m_coin_control->HasSelected())
1049 {
1050 // actual coin control calculation
1052
1053 // show coin control stats
1054 ui->labelCoinControlAutomaticallySelected->hide();
1055 ui->widgetCoinControl->show();
1056 }
1057 else
1058 {
1059 // hide coin control stats
1060 ui->labelCoinControlAutomaticallySelected->show();
1061 ui->widgetCoinControl->hide();
1062 ui->labelCoinControlInsuffFunds->hide();
1063 }
1064}
1065
1066SendConfirmationDialog::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)
1067 : QMessageBox(parent), secDelay(_secDelay), m_enable_send(enable_send)
1068{
1069 setIcon(QMessageBox::Question);
1070 setWindowTitle(title); // On macOS, the window title is ignored (as required by the macOS Guidelines).
1071 setText(text);
1072 setInformativeText(informative_text);
1073 setDetailedText(detailed_text);
1074 setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel);
1075 if (always_show_unsigned || !enable_send) addButton(QMessageBox::Save);
1076 setDefaultButton(QMessageBox::Cancel);
1077 yesButton = button(QMessageBox::Yes);
1078 if (confirmButtonText.isEmpty()) {
1079 confirmButtonText = yesButton->text();
1080 }
1081 m_psbt_button = button(QMessageBox::Save);
1082 updateButtons();
1083 connect(&countDownTimer, &QTimer::timeout, this, &SendConfirmationDialog::countDown);
1084}
1085
1087{
1088 updateButtons();
1089 countDownTimer.start(1s);
1090 return QMessageBox::exec();
1091}
1092
1094{
1095 secDelay--;
1096 updateButtons();
1097
1098 if(secDelay <= 0)
1099 {
1100 countDownTimer.stop();
1101 }
1102}
1103
1105{
1106 if(secDelay > 0)
1107 {
1108 yesButton->setEnabled(false);
1109 yesButton->setText(confirmButtonText + (m_enable_send ? (" (" + QString::number(secDelay) + ")") : QString("")));
1110 if (m_psbt_button) {
1111 m_psbt_button->setEnabled(false);
1112 m_psbt_button->setText(m_psbt_button_text + " (" + QString::number(secDelay) + ")");
1113 }
1114 }
1115 else
1116 {
1117 yesButton->setEnabled(m_enable_send);
1118 yesButton->setText(confirmButtonText);
1119 if (m_psbt_button) {
1120 m_psbt_button->setEnabled(true);
1122 }
1123 }
1124}
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 kilovirtualbyte: CAmount / kvB.
Definition: feerate.h:33
CAmount GetFeePerK() const
Return the fee in satoshis for a vsize of 1000 vbytes.
Definition: feerate.h:63
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:147
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 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 coinsSent(const uint256 &txid)
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:48
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
@ AmountWithFeeExceedsBalance
Definition: walletmodel.h:61
@ TransactionCreationFailed
Definition: walletmodel.h:63
@ AmountExceedsBalance
Definition: walletmodel.h:60
@ 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(int sighash_type, bool sign, bool bip32derivs, 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:81
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:91
SyncType
Definition: clientmodel.h:42
#define ASYMP_UTF8
@ SIGHASH_ALL
Definition: interpreter.h:30
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:983
QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedSuffixOut)
Get save filename, mimics QFileDialog::getSaveFileName, except that it appends a default suffix when ...
Definition: guiutil.cpp:313
QString formatNiceTimeOffset(qint64 secs)
Definition: guiutil.cpp:787
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:915
void setupAddressWidget(QValidatedLineEdit *widget, QWidget *parent)
Definition: guiutil.cpp:131
void setClipboard(const QString &str)
Definition: guiutil.cpp:668
PSBTError
Definition: types.h:17
constexpr CAmount DEFAULT_PAY_TX_FEE
-paytxfee default
Definition: wallet.h:104
is a home for public enum and struct type definitions that are used internally by node code,...
FeeReason
Definition: fees.h:60
static CTransactionRef MakeTransactionRef(Tx &&txIn)
Definition: transaction.h:424
std::shared_ptr< const CTransaction > CTransactionRef
Definition: transaction.h:423
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:495
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:378
A version of CTransaction with the PSBT format.
Definition: psbt.h:1111
Collection of wallet balances.
Definition: wallet.h:370
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:86