Bitcoin Core  31.0.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 
7 #include <qt/sendcoinsdialog.h>
8 #include <qt/forms/ui_sendcoinsdialog.h>
9 
10 #include <qt/addresstablemodel.h>
11 #include <qt/bitcoinunits.h>
12 #include <qt/clientmodel.h>
13 #include <qt/coincontroldialog.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 
41 using common::PSBTError;
43 
44 static constexpr std::array confTargets{2, 4, 6, 12, 24, 48, 144, 504, 1008};
45 int 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 }
54 int 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 
63 SendCoinsDialog::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 
163  refreshBalance();
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 
244 bool 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
276  fNewRecipientAllowed = true;
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) {
295  fNewRecipientAllowed = true;
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));
337  } else if (model->getOptionsModel()->getEnablePSBTControls()) {
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()) {
379  if(u != model->getOptionsModel()->getDisplayUnit())
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 
477 void 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  {
496  fNewRecipientAllowed = true;
497  return;
498  }
499 
500  bool send_failure = false;
501  if (retval == QMessageBox::Save) {
502  // "Create Unsigned" clicked
504  PartiallySignedTransaction psbtx(mtx);
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()) {
519  PartiallySignedTransaction psbtx(mtx);
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  }
555  fNewRecipientAllowed = true;
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 
636 QWidget *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 
652 void 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 
723 void 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  // included to prevent a compiler warning.
753  case WalletModel::OK:
754  default:
755  return;
756  }
757 
758  Q_EMIT message(tr("Send Coins"), msgParams.first, msgParams.second);
759 }
760 
762 {
763  ui->labelFeeMinimized->setVisible(fMinimize);
764  ui->buttonChooseFee ->setVisible(fMinimize);
765  ui->buttonMinimizeFee->setVisible(!fMinimize);
766  ui->frameFeeSelection->setVisible(!fMinimize);
767  ui->horizontalLayoutSmartFee->setContentsMargins(0, (fMinimize ? 0 : 6), 0, 0);
768  fFeeMinimized = fMinimize;
769 }
770 
772 {
773  minimizeFeeSection(false);
774 }
775 
777 {
779  minimizeFeeSection(true);
780 }
781 
783 {
784  // Same behavior as send: if we have selected coins, only obtain their available balance.
785  // Copy to avoid modifying the member's data.
786  CCoinControl coin_control = *m_coin_control;
787  coin_control.m_allow_other_inputs = !coin_control.HasSelected();
788 
789  // Calculate available amount to send.
790  CAmount amount = model->getAvailableBalance(&coin_control);
791  for (int i = 0; i < ui->entries->count(); ++i) {
792  SendCoinsEntry* e = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
793  if (e && !e->isHidden() && e != entry) {
794  amount -= e->getValue().amount;
795  }
796  }
797 
798  if (amount > 0) {
800  entry->setAmount(amount);
801  } else {
802  entry->setAmount(0);
803  }
804 }
805 
807 {
808  ui->confTargetSelector ->setEnabled(ui->radioSmartFee->isChecked());
809  ui->labelSmartFee ->setEnabled(ui->radioSmartFee->isChecked());
810  ui->labelSmartFee2 ->setEnabled(ui->radioSmartFee->isChecked());
811  ui->labelSmartFee3 ->setEnabled(ui->radioSmartFee->isChecked());
812  ui->labelFeeEstimation ->setEnabled(ui->radioSmartFee->isChecked());
813  ui->labelCustomFeeWarning ->setEnabled(ui->radioCustomFee->isChecked());
814  ui->labelCustomPerKilobyte ->setEnabled(ui->radioCustomFee->isChecked());
815  ui->customFee ->setEnabled(ui->radioCustomFee->isChecked());
816 }
817 
819 {
820  if(!model || !model->getOptionsModel())
821  return;
822 
823  if (ui->radioSmartFee->isChecked())
824  ui->labelFeeMinimized->setText(ui->labelSmartFee->text());
825  else {
826  ui->labelFeeMinimized->setText(tr("%1/kvB").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), ui->customFee->value())));
827  }
828 }
829 
831 {
832  if (ui->radioCustomFee->isChecked()) {
833  m_coin_control->m_feerate = CFeeRate(ui->customFee->value());
834  } else {
835  m_coin_control->m_feerate.reset();
836  }
837  // Avoid using global defaults when sending money from the GUI
838  // Either custom fee will be used or if not selected, the confirmation target from dropdown box
839  m_coin_control->m_confirm_target = getConfTargetForIndex(ui->confTargetSelector->currentIndex());
840  m_coin_control->m_signal_bip125_rbf = ui->optInRBF->isChecked();
841 }
842 
843 void SendCoinsDialog::updateNumberOfBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, SyncType synctype, SynchronizationState sync_state) {
844  // During shutdown, clientModel will be nullptr. Attempting to update views at this point may cause a crash
845  // due to accessing backend models that might no longer exist.
846  if (!clientModel) return;
847  // Process event
848  if (sync_state == SynchronizationState::POST_INIT) {
850  }
851 }
852 
854 {
855  if(!model || !model->getOptionsModel())
856  return;
858  m_coin_control->m_feerate.reset(); // Explicitly use only fee estimation rate for smart fee labels
859  int returned_target;
860  FeeReason reason;
861  CFeeRate feeRate = CFeeRate(model->wallet().getMinimumFee(1000, *m_coin_control, &returned_target, &reason));
862 
863  ui->labelSmartFee->setText(tr("%1/kvB").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), feeRate.GetFeePerK())));
864 
865  if (reason == FeeReason::FALLBACK) {
866  ui->labelSmartFee2->show(); // (Smart fee not initialized yet. This usually takes a few blocks...)
867  ui->labelFeeEstimation->setText("");
868  ui->fallbackFeeWarningLabel->setVisible(true);
869  int lightness = ui->fallbackFeeWarningLabel->palette().color(QPalette::WindowText).lightness();
870  QColor warning_colour(255 - (lightness / 5), 176 - (lightness / 3), 48 - (lightness / 14));
871  ui->fallbackFeeWarningLabel->setStyleSheet("QLabel { color: " + warning_colour.name() + "; }");
872  ui->fallbackFeeWarningLabel->setIndent(GUIUtil::TextWidth(QFontMetrics(ui->fallbackFeeWarningLabel->font()), "x"));
873  }
874  else
875  {
876  ui->labelSmartFee2->hide();
877  ui->labelFeeEstimation->setText(tr("Estimated to begin confirmation within %n block(s).", "", returned_target));
878  ui->fallbackFeeWarningLabel->setVisible(false);
879  }
880 
882 }
883 
884 // Coin Control: copy label "Quantity" to clipboard
886 {
887  GUIUtil::setClipboard(ui->labelCoinControlQuantity->text());
888 }
889 
890 // Coin Control: copy label "Amount" to clipboard
892 {
893  GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left(ui->labelCoinControlAmount->text().indexOf(" ")));
894 }
895 
896 // Coin Control: copy label "Fee" to clipboard
898 {
899  GUIUtil::setClipboard(ui->labelCoinControlFee->text().left(ui->labelCoinControlFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
900 }
901 
902 // Coin Control: copy label "After fee" to clipboard
904 {
905  GUIUtil::setClipboard(ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
906 }
907 
908 // Coin Control: copy label "Bytes" to clipboard
910 {
911  GUIUtil::setClipboard(ui->labelCoinControlBytes->text().replace(ASYMP_UTF8, ""));
912 }
913 
914 // Coin Control: copy label "Change" to clipboard
916 {
917  GUIUtil::setClipboard(ui->labelCoinControlChange->text().left(ui->labelCoinControlChange->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
918 }
919 
920 // Coin Control: settings menu - coin control enabled/disabled by user
922 {
923  ui->frameCoinControl->setVisible(checked);
924 
925  if (!checked && model) { // coin control features disabled
926  m_coin_control = std::make_unique<CCoinControl>();
927  }
928 
930 }
931 
932 // Coin Control: button inputs -> show actual coin control dialog
934 {
936  connect(dlg, &QDialog::finished, this, &SendCoinsDialog::coinControlUpdateLabels);
938 }
939 
940 // Coin Control: checkbox custom change address
941 #if (QT_VERSION >= QT_VERSION_CHECK(6, 7, 0))
943 #else
945 #endif
946 {
947  if (state == Qt::Unchecked)
948  {
949  m_coin_control->destChange = CNoDestination();
950  ui->labelCoinControlChangeLabel->clear();
951  }
952  else
953  // use this to re-validate an already entered address
954  coinControlChangeEdited(ui->lineEditCoinControlChange->text());
955 
956  ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked));
957 }
958 
959 // Coin Control: custom change address changed
961 {
962  if (model && model->getAddressTableModel())
963  {
964  // Default to no change address until verified
965  m_coin_control->destChange = CNoDestination();
966  ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:red;}");
967 
968  const CTxDestination dest = DecodeDestination(text.toStdString());
969 
970  if (text.isEmpty()) // Nothing entered
971  {
972  ui->labelCoinControlChangeLabel->setText("");
973  }
974  else if (!IsValidDestination(dest)) // Invalid address
975  {
976  ui->labelCoinControlChangeLabel->setText(tr("Warning: Invalid Bitcoin address"));
977  }
978  else // Valid address
979  {
980  if (!model->wallet().isSpendable(dest)) {
981  ui->labelCoinControlChangeLabel->setText(tr("Warning: Unknown change address"));
982 
983  // confirmation dialog
984  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?"),
985  QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel);
986 
987  if(btnRetVal == QMessageBox::Yes)
988  m_coin_control->destChange = dest;
989  else
990  {
991  ui->lineEditCoinControlChange->setText("");
992  ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
993  ui->labelCoinControlChangeLabel->setText("");
994  }
995  }
996  else // Known change address
997  {
998  ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
999 
1000  // Query label
1001  QString associatedLabel = model->getAddressTableModel()->labelForAddress(text);
1002  if (!associatedLabel.isEmpty())
1003  ui->labelCoinControlChangeLabel->setText(associatedLabel);
1004  else
1005  ui->labelCoinControlChangeLabel->setText(tr("(no label)"));
1006 
1007  m_coin_control->destChange = dest;
1008  }
1009  }
1010  }
1011 }
1012 
1013 // Coin Control: update labels
1015 {
1016  if (!model || !model->getOptionsModel())
1017  return;
1018 
1020 
1021  // set pay amounts
1024 
1025  for(int i = 0; i < ui->entries->count(); ++i)
1026  {
1027  SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
1028  if(entry && !entry->isHidden())
1029  {
1030  SendCoinsRecipient rcp = entry->getValue();
1032  if (rcp.fSubtractFeeFromAmount)
1034  }
1035  }
1036 
1037  if (m_coin_control->HasSelected())
1038  {
1039  // actual coin control calculation
1041 
1042  // show coin control stats
1043  ui->labelCoinControlAutomaticallySelected->hide();
1044  ui->widgetCoinControl->show();
1045  }
1046  else
1047  {
1048  // hide coin control stats
1049  ui->labelCoinControlAutomaticallySelected->show();
1050  ui->widgetCoinControl->hide();
1051  ui->labelCoinControlInsuffFunds->hide();
1052  }
1053 }
1054 
1055 SendConfirmationDialog::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)
1056  : QMessageBox(parent), secDelay(_secDelay), m_enable_send(enable_send)
1057 {
1058  setIcon(QMessageBox::Question);
1059  setWindowTitle(title); // On macOS, the window title is ignored (as required by the macOS Guidelines).
1060  setText(text);
1061  setInformativeText(informative_text);
1062  setDetailedText(detailed_text);
1063  setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel);
1064  if (always_show_unsigned || !enable_send) addButton(QMessageBox::Save);
1065  setDefaultButton(QMessageBox::Cancel);
1066  yesButton = button(QMessageBox::Yes);
1067  if (confirmButtonText.isEmpty()) {
1068  confirmButtonText = yesButton->text();
1069  }
1070  m_psbt_button = button(QMessageBox::Save);
1071  updateButtons();
1072  connect(&countDownTimer, &QTimer::timeout, this, &SendConfirmationDialog::countDown);
1073 }
1074 
1076 {
1077  updateButtons();
1078  countDownTimer.start(1s);
1079  return QMessageBox::exec();
1080 }
1081 
1083 {
1084  secDelay--;
1085  updateButtons();
1086 
1087  if(secDelay <= 0)
1088  {
1089  countDownTimer.stop();
1090  }
1091 }
1092 
1094 {
1095  if(secDelay > 0)
1096  {
1097  yesButton->setEnabled(false);
1098  yesButton->setText(confirmButtonText + (m_enable_send ? (" (" + QString::number(secDelay) + ")") : QString("")));
1099  if (m_psbt_button) {
1100  m_psbt_button->setEnabled(false);
1101  m_psbt_button->setText(m_psbt_button_text + " (" + QString::number(secDelay) + ")");
1102  }
1103  }
1104  else
1105  {
1106  yesButton->setEnabled(m_enable_send);
1107  yesButton->setText(confirmButtonText);
1108  if (m_psbt_button) {
1109  m_psbt_button->setEnabled(true);
1111  }
1112  }
1113 }
std::shared_ptr< const CTransaction > CTransactionRef
Definition: transaction.h:403
virtual bool privateKeysDisabled()=0
bool signWithExternalSigner(PartiallySignedTransaction &psbt, CMutableTransaction &mtx, bool &complete)
PSBTError
Definition: types.h:17
void removeEntry(SendCoinsEntry *entry)
Predefined combinations for certain default usage cases.
Definition: interface_ui.h:66
static QList< Unit > availableUnits()
Get list of units, for drop-down box.
OptionsModel * getOptionsModel() const
Unit
Bitcoin units.
Definition: bitcoinunits.h:42
interfaces::Wallet & wallet() const
Definition: walletmodel.h:138
void setValue(const SendCoinsRecipient &value)
void payAmountChanged()
Utility functions used by the Bitcoin Qt UI.
Definition: bitcoingui.h:58
SynchronizationState
Current sync state passed to tip changed callbacks.
Definition: validation.h:93
assert(!tx.IsCoinBase())
SendCoinsReturn prepareTransaction(WalletModelTransaction &transaction, const wallet::CCoinControl &coinControl)
bool IsValidDestination(const CTxDestination &dest)
Check whether a CTxDestination corresponds to one with an address.
void presentPSBT(PartiallySignedTransaction &psbt)
void sendButtonClicked(bool checked)
void updateFeeMinimizedLabel()
void setFocus()
void on_buttonChooseFee_clicked()
SendCoinsRecipient getValue()
bool hasSigner()
Whether -signer was set or not.
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 reject() override
UnlockContext requestUnlock()
void coinControlClipboardQuantity()
void coinControlClipboardAfterFee()
void setAddress(const QString &address)
interfaces::WalletBalances getCachedBalance() const
virtual CAmount getDefaultMaxTxFee()=0
Get max tx fee.
#define SEND_CONFIRM_DELAY
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.
BitcoinUnit getDisplayUnit() const
Definition: optionsmodel.h:103
void coinControlFeaturesChanged(bool)
QIcon SingleColorIcon(const QString &filename) const
Colorize an icon (given filename) with the icon color.
QString HtmlEscape(const QString &str, bool fMultiLine)
Definition: guiutil.cpp:249
bool handlePaymentRequest(const SendCoinsRecipient &recipient)
A version of CTransaction with the PSBT format.
Definition: psbt.h:1138
std::string EncodeBase64(std::span< const unsigned char > input)
void ShowModalDialogAsynchronously(QDialog *dialog)
Shows a QDialog instance asynchronously, and deletes it on close.
Definition: guiutil.cpp:978
static constexpr std::array confTargets
#define ASYMP_UTF8
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
virtual bool isSpendable(const CTxDestination &dest)=0
Return whether wallet has private key.
A single entry in the dialog for sending bitcoins.
bool HasSelected() const
Returns true if there are pre-selected inputs.
Definition: coincontrol.cpp:15
static QString formatWithUnit(Unit unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD)
Format as string (with unit)
constexpr auto dialog_flags
Definition: guiutil.h:60
void coinControlFeatureChanged(bool)
virtual bool hasExternalSigner()=0
std::unique_ptr< wallet::CCoinControl > m_coin_control
QWidget * setupTabChain(QWidget *prev)
Set up the tab chain manually, as Qt messes up the tab chain by default in some cases (issue https://...
std::unique_ptr< WalletModelTransaction > m_current_transaction
int64_t CAmount
Amount in satoshis (Can be negative)
Definition: amount.h:12
static QList< CAmount > payAmounts
QWidget * setupTabChain(QWidget *prev)
Set up the tab chain manually, as Qt messes up the tab chain by default in some cases (issue https://...
Ui::SendCoinsDialog * ui
SendCoinsEntry * addEntry()
void clear()
bool validate(interfaces::Node &node)
void setupAddressWidget(QValidatedLineEdit *widget, QWidget *parent)
Definition: guiutil.cpp:131
void displayUnitChanged(BitcoinUnit unit)
void setBalance(const interfaces::WalletBalances &balances)
void setAddress(const QString &address)
void coinControlClipboardChange()
Collection of wallet balances.
Definition: wallet.h:367
void useAvailableBalance(SendCoinsEntry *entry)
void setClientModel(ClientModel *clientModel)
void setClipboard(const QString &str)
Definition: guiutil.cpp:661
ClientModel * clientModel
Double ended buffer combining vector and stream-like interfaces.
Definition: streams.h:132
void numBlocksChanged(int count, const QDateTime &blockDate, double nVerificationProgress, SyncType header, SynchronizationState sync_state)
void coinsSent(const Txid &txid)
QString labelForAddress(const QString &address) const
Look up label for address in address book, if not found return empty string.
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.html#connect-3), that guaranties that all exceptions are handled within the slot.
Definition: guiutil.h:369
WalletModel * model
Dialog for sending bitcoins.
void coinControlChangeEdited(const QString &)
QString getWalletName() const
bool getEnablePSBTControls() const
Definition: optionsmodel.h:108
interfaces::Node & node() const
Definition: walletmodel.h:137
void removeEntry(SendCoinsEntry *entry)
Model for Bitcoin network client.
Definition: clientmodel.h:56
bool isMultiwallet() const
void setModel(WalletModel *model)
int getConfTargetForIndex(int index)
SendCoinsDialog(const PlatformStyle *platformStyle, QWidget *parent=nullptr)
void accept() override
void coinControlChangeChecked(Qt::CheckState)
bool getCoinControlFeatures() const
Definition: optionsmodel.h:106
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)
static CTransactionRef MakeTransactionRef(Tx &&txIn)
Definition: transaction.h:404
SyncType
Definition: clientmodel.h:42
void checkSubtractFeeFromAmount()
bool isClear()
Return whether the entry is still empty and unedited.
void minimizeFeeSection(bool fMinimize)
void updateFeeSectionControls()
void subtractFeeFromAmountChanged()
static bool fSubtractFeeFromAmount
bool PrepareSendText(QString &question_string, QString &informative_text, QString &detailed_text)
static void updateLabels(wallet::CCoinControl &m_coin_control, WalletModel *, QDialog *)
int getIndexForConfTarget(int target)
const CChainParams & Params()
Return the currently selected parameters.
Interface to Bitcoin wallet from Qt view code.
Definition: walletmodel.h:48
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
std::variant< CNoDestination, PubKeyDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessV1Taproot, PayToAnchor, WitnessUnknown > CTxDestination
A txout script categorized into standard templates.
Definition: addresstype.h:143
void setAmount(const CAmount &amount)
void setModel(WalletModel *model)
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
void processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg=QString())
Fee rate in satoshis per virtualbyte: CAmount / vB the feerate is represented internally as FeeFrac...
Definition: feerate.h:31
static int count
void coinControlClipboardBytes()
void useAvailableBalance(SendCoinsEntry *entry)
A mutable version of CTransaction.
Definition: transaction.h:357
virtual unsigned int getConfirmTarget()=0
Get tx confirm target.
AddressTableModel * getAddressTableModel() const
CAmount getAvailableBalance(const wallet::CCoinControl *control)
static QString formatHtmlWithUnit(Unit unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD)
Format as HTML string (with unit)
const PlatformStyle * platformStyle
QString formatNiceTimeOffset(qint64 secs)
Definition: guiutil.cpp:782
void coinControlClipboardAmount()
void sendCoins(WalletModelTransaction &transaction)
void on_buttonMinimizeFee_clicked()
CTxDestination DecodeDestination(const std::string &str, std::string &error_msg, std::vector< int > *error_locations)
Definition: key_io.cpp:299
is a home for public enum and struct type definitions that are used internally by node code...
void pasteEntry(const SendCoinsRecipient &rv)
QAbstractButton * yesButton
virtual CAmount getRequiredFee(unsigned int tx_bytes)=0
Get required fee.
void message(const QString &title, const QString &message, unsigned int style)
QAbstractButton * m_psbt_button
virtual CAmount getMinimumFee(unsigned int tx_bytes, const wallet::CCoinControl &coin_control, int *returned_target, FeeReason *reason)=0
Get minimum fee.
CAmount GetFeePerK() const
Return the fee in satoshis for a vsize of 1000 vbytes.
Definition: feerate.h:62
void balanceChanged(const interfaces::WalletBalances &balances)
Coin Control Features.
Definition: coincontrol.h:83
bool getImagesOnButtons() const
Definition: platformstyle.h:21
void coinControlButtonClicked()
void coinControlClipboardFee()
void updateNumberOfBlocks(int count, const QDateTime &blockDate, double nVerificationProgress, SyncType synctype, SynchronizationState sync_state)