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 tfm::format(std::cerr, "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 } else if (model->wallet().isLegacy() && model->wallet().privateKeysDisabled()) {
715 balance = balances.watch_only_balance;
716 ui->labelBalanceName->setText(tr("Watch-only balance:"));
717 }
718 ui->labelBalance->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), balance));
719 }
720}
721
723{
725 ui->customFee->setDisplayUnit(model->getOptionsModel()->getDisplayUnit());
727}
728
729void SendCoinsDialog::processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg)
730{
731 QPair<QString, CClientUIInterface::MessageBoxFlags> msgParams;
732 // Default to a warning message, override if error message is needed
733 msgParams.second = CClientUIInterface::MSG_WARNING;
734
735 // This comment is specific to SendCoinsDialog usage of WalletModel::SendCoinsReturn.
736 // All status values are used only in WalletModel::prepareTransaction()
737 switch(sendCoinsReturn.status)
738 {
740 msgParams.first = tr("The recipient address is not valid. Please recheck.");
741 break;
743 msgParams.first = tr("The amount to pay must be larger than 0.");
744 break;
746 msgParams.first = tr("The amount exceeds your balance.");
747 break;
749 msgParams.first = tr("The total exceeds your balance when the %1 transaction fee is included.").arg(msgArg);
750 break;
752 msgParams.first = tr("Duplicate address found: addresses should only be used once each.");
753 break;
755 msgParams.first = tr("Transaction creation failed!");
756 msgParams.second = CClientUIInterface::MSG_ERROR;
757 break;
759 msgParams.first = tr("A fee higher than %1 is considered an absurdly high fee.").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), model->wallet().getDefaultMaxTxFee()));
760 break;
761 // included to prevent a compiler warning.
762 case WalletModel::OK:
763 default:
764 return;
765 }
766
767 Q_EMIT message(tr("Send Coins"), msgParams.first, msgParams.second);
768}
769
771{
772 ui->labelFeeMinimized->setVisible(fMinimize);
773 ui->buttonChooseFee ->setVisible(fMinimize);
774 ui->buttonMinimizeFee->setVisible(!fMinimize);
775 ui->frameFeeSelection->setVisible(!fMinimize);
776 ui->horizontalLayoutSmartFee->setContentsMargins(0, (fMinimize ? 0 : 6), 0, 0);
777 fFeeMinimized = fMinimize;
778}
779
781{
782 minimizeFeeSection(false);
783}
784
786{
788 minimizeFeeSection(true);
789}
790
792{
793 // Include watch-only for wallets without private key
795
796 // Same behavior as send: if we have selected coins, only obtain their available balance.
797 // Copy to avoid modifying the member's data.
798 CCoinControl coin_control = *m_coin_control;
799 coin_control.m_allow_other_inputs = !coin_control.HasSelected();
800
801 // Calculate available amount to send.
802 CAmount amount = model->getAvailableBalance(&coin_control);
803 for (int i = 0; i < ui->entries->count(); ++i) {
804 SendCoinsEntry* e = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
805 if (e && !e->isHidden() && e != entry) {
806 amount -= e->getValue().amount;
807 }
808 }
809
810 if (amount > 0) {
812 entry->setAmount(amount);
813 } else {
814 entry->setAmount(0);
815 }
816}
817
819{
820 ui->confTargetSelector ->setEnabled(ui->radioSmartFee->isChecked());
821 ui->labelSmartFee ->setEnabled(ui->radioSmartFee->isChecked());
822 ui->labelSmartFee2 ->setEnabled(ui->radioSmartFee->isChecked());
823 ui->labelSmartFee3 ->setEnabled(ui->radioSmartFee->isChecked());
824 ui->labelFeeEstimation ->setEnabled(ui->radioSmartFee->isChecked());
825 ui->labelCustomFeeWarning ->setEnabled(ui->radioCustomFee->isChecked());
826 ui->labelCustomPerKilobyte ->setEnabled(ui->radioCustomFee->isChecked());
827 ui->customFee ->setEnabled(ui->radioCustomFee->isChecked());
828}
829
831{
832 if(!model || !model->getOptionsModel())
833 return;
834
835 if (ui->radioSmartFee->isChecked())
836 ui->labelFeeMinimized->setText(ui->labelSmartFee->text());
837 else {
838 ui->labelFeeMinimized->setText(tr("%1/kvB").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), ui->customFee->value())));
839 }
840}
841
843{
844 if (ui->radioCustomFee->isChecked()) {
845 m_coin_control->m_feerate = CFeeRate(ui->customFee->value());
846 } else {
847 m_coin_control->m_feerate.reset();
848 }
849 // Avoid using global defaults when sending money from the GUI
850 // Either custom fee will be used or if not selected, the confirmation target from dropdown box
851 m_coin_control->m_confirm_target = getConfTargetForIndex(ui->confTargetSelector->currentIndex());
852 m_coin_control->m_signal_bip125_rbf = ui->optInRBF->isChecked();
853 // Include watch-only for wallets without private key
855}
856
857void SendCoinsDialog::updateNumberOfBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, SyncType synctype, SynchronizationState sync_state) {
858 if (sync_state == SynchronizationState::POST_INIT) {
860 }
861}
862
864{
865 if(!model || !model->getOptionsModel())
866 return;
868 m_coin_control->m_feerate.reset(); // Explicitly use only fee estimation rate for smart fee labels
869 int returned_target;
870 FeeReason reason;
871 CFeeRate feeRate = CFeeRate(model->wallet().getMinimumFee(1000, *m_coin_control, &returned_target, &reason));
872
873 ui->labelSmartFee->setText(tr("%1/kvB").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), feeRate.GetFeePerK())));
874
875 if (reason == FeeReason::FALLBACK) {
876 ui->labelSmartFee2->show(); // (Smart fee not initialized yet. This usually takes a few blocks...)
877 ui->labelFeeEstimation->setText("");
878 ui->fallbackFeeWarningLabel->setVisible(true);
879 int lightness = ui->fallbackFeeWarningLabel->palette().color(QPalette::WindowText).lightness();
880 QColor warning_colour(255 - (lightness / 5), 176 - (lightness / 3), 48 - (lightness / 14));
881 ui->fallbackFeeWarningLabel->setStyleSheet("QLabel { color: " + warning_colour.name() + "; }");
882 ui->fallbackFeeWarningLabel->setIndent(GUIUtil::TextWidth(QFontMetrics(ui->fallbackFeeWarningLabel->font()), "x"));
883 }
884 else
885 {
886 ui->labelSmartFee2->hide();
887 ui->labelFeeEstimation->setText(tr("Estimated to begin confirmation within %n block(s).", "", returned_target));
888 ui->fallbackFeeWarningLabel->setVisible(false);
889 }
890
892}
893
894// Coin Control: copy label "Quantity" to clipboard
896{
897 GUIUtil::setClipboard(ui->labelCoinControlQuantity->text());
898}
899
900// Coin Control: copy label "Amount" to clipboard
902{
903 GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left(ui->labelCoinControlAmount->text().indexOf(" ")));
904}
905
906// Coin Control: copy label "Fee" to clipboard
908{
909 GUIUtil::setClipboard(ui->labelCoinControlFee->text().left(ui->labelCoinControlFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
910}
911
912// Coin Control: copy label "After fee" to clipboard
914{
915 GUIUtil::setClipboard(ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
916}
917
918// Coin Control: copy label "Bytes" to clipboard
920{
921 GUIUtil::setClipboard(ui->labelCoinControlBytes->text().replace(ASYMP_UTF8, ""));
922}
923
924// Coin Control: copy label "Change" to clipboard
926{
927 GUIUtil::setClipboard(ui->labelCoinControlChange->text().left(ui->labelCoinControlChange->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
928}
929
930// Coin Control: settings menu - coin control enabled/disabled by user
932{
933 ui->frameCoinControl->setVisible(checked);
934
935 if (!checked && model) { // coin control features disabled
936 m_coin_control = std::make_unique<CCoinControl>();
937 }
938
940}
941
942// Coin Control: button inputs -> show actual coin control dialog
944{
946 connect(dlg, &QDialog::finished, this, &SendCoinsDialog::coinControlUpdateLabels);
948}
949
950// Coin Control: checkbox custom change address
951#if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0))
953#else
955#endif
956{
957 if (state == Qt::Unchecked)
958 {
959 m_coin_control->destChange = CNoDestination();
960 ui->labelCoinControlChangeLabel->clear();
961 }
962 else
963 // use this to re-validate an already entered address
964 coinControlChangeEdited(ui->lineEditCoinControlChange->text());
965
966 ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked));
967}
968
969// Coin Control: custom change address changed
971{
973 {
974 // Default to no change address until verified
975 m_coin_control->destChange = CNoDestination();
976 ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:red;}");
977
978 const CTxDestination dest = DecodeDestination(text.toStdString());
979
980 if (text.isEmpty()) // Nothing entered
981 {
982 ui->labelCoinControlChangeLabel->setText("");
983 }
984 else if (!IsValidDestination(dest)) // Invalid address
985 {
986 ui->labelCoinControlChangeLabel->setText(tr("Warning: Invalid Bitcoin address"));
987 }
988 else // Valid address
989 {
990 if (!model->wallet().isSpendable(dest)) {
991 ui->labelCoinControlChangeLabel->setText(tr("Warning: Unknown change address"));
992
993 // confirmation dialog
994 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?"),
995 QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel);
996
997 if(btnRetVal == QMessageBox::Yes)
998 m_coin_control->destChange = dest;
999 else
1000 {
1001 ui->lineEditCoinControlChange->setText("");
1002 ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
1003 ui->labelCoinControlChangeLabel->setText("");
1004 }
1005 }
1006 else // Known change address
1007 {
1008 ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
1009
1010 // Query label
1011 QString associatedLabel = model->getAddressTableModel()->labelForAddress(text);
1012 if (!associatedLabel.isEmpty())
1013 ui->labelCoinControlChangeLabel->setText(associatedLabel);
1014 else
1015 ui->labelCoinControlChangeLabel->setText(tr("(no label)"));
1016
1017 m_coin_control->destChange = dest;
1018 }
1019 }
1020 }
1021}
1022
1023// Coin Control: update labels
1025{
1026 if (!model || !model->getOptionsModel())
1027 return;
1028
1030
1031 // set pay amounts
1034
1035 for(int i = 0; i < ui->entries->count(); ++i)
1036 {
1037 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
1038 if(entry && !entry->isHidden())
1039 {
1040 SendCoinsRecipient rcp = entry->getValue();
1042 if (rcp.fSubtractFeeFromAmount)
1044 }
1045 }
1046
1047 if (m_coin_control->HasSelected())
1048 {
1049 // actual coin control calculation
1051
1052 // show coin control stats
1053 ui->labelCoinControlAutomaticallySelected->hide();
1054 ui->widgetCoinControl->show();
1055 }
1056 else
1057 {
1058 // hide coin control stats
1059 ui->labelCoinControlAutomaticallySelected->show();
1060 ui->widgetCoinControl->hide();
1061 ui->labelCoinControlInsuffFunds->hide();
1062 }
1063}
1064
1065SendConfirmationDialog::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)
1066 : QMessageBox(parent), secDelay(_secDelay), m_enable_send(enable_send)
1067{
1068 setIcon(QMessageBox::Question);
1069 setWindowTitle(title); // On macOS, the window title is ignored (as required by the macOS Guidelines).
1070 setText(text);
1071 setInformativeText(informative_text);
1072 setDetailedText(detailed_text);
1073 setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel);
1074 if (always_show_unsigned || !enable_send) addButton(QMessageBox::Save);
1075 setDefaultButton(QMessageBox::Cancel);
1076 yesButton = button(QMessageBox::Yes);
1077 if (confirmButtonText.isEmpty()) {
1078 confirmButtonText = yesButton->text();
1079 }
1080 m_psbt_button = button(QMessageBox::Save);
1081 updateButtons();
1082 connect(&countDownTimer, &QTimer::timeout, this, &SendConfirmationDialog::countDown);
1083}
1084
1086{
1087 updateButtons();
1088 countDownTimer.start(1s);
1089 return QMessageBox::exec();
1090}
1091
1093{
1094 secDelay--;
1095 updateButtons();
1096
1097 if(secDelay <= 0)
1098 {
1099 countDownTimer.stop();
1100 }
1101}
1102
1104{
1105 if(secDelay > 0)
1106 {
1107 yesButton->setEnabled(false);
1108 yesButton->setText(confirmButtonText + (m_enable_send ? (" (" + QString::number(secDelay) + ")") : QString("")));
1109 if (m_psbt_button) {
1110 m_psbt_button->setEnabled(false);
1111 m_psbt_button->setText(m_psbt_button_text + " (" + QString::number(secDelay) + ")");
1112 }
1113 }
1114 else
1115 {
1116 yesButton->setEnabled(m_enable_send);
1117 yesButton->setText(confirmButtonText);
1118 if (m_psbt_button) {
1119 m_psbt_button->setEnabled(true);
1121 }
1122 }
1123}
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 bool isLegacy()=0
Return whether is a legacy wallet.
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
void format(std::ostream &out, FormatStringCheck< sizeof...(Args)> fmt, const Args &... args)
Format list of arguments to the stream according to given format string.
Definition: tinyformat.h:1079
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:380
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:85