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