Bitcoin Core  24.1.0
P2P Digital Currency
sendcoinsdialog.cpp
Go to the documentation of this file.
1 // Copyright (c) 2011-2021 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 #if defined(HAVE_CONFIG_H)
7 #endif
8 
9 #include <qt/sendcoinsdialog.h>
11 
12 #include <qt/addresstablemodel.h>
13 #include <qt/bitcoinunits.h>
14 #include <qt/clientmodel.h>
15 #include <qt/coincontroldialog.h>
16 #include <qt/guiutil.h>
17 #include <qt/optionsmodel.h>
18 #include <qt/platformstyle.h>
19 #include <qt/sendcoinsentry.h>
20 
21 #include <chainparams.h>
22 #include <interfaces/node.h>
23 #include <key_io.h>
24 #include <node/interface_ui.h>
25 #include <policy/fees.h>
26 #include <txmempool.h>
27 #include <validation.h>
28 #include <wallet/coincontrol.h>
29 #include <wallet/fees.h>
30 #include <wallet/wallet.h>
31 
32 #include <array>
33 #include <chrono>
34 #include <fstream>
35 #include <memory>
36 
37 #include <QFontMetrics>
38 #include <QScrollBar>
39 #include <QSettings>
40 #include <QTextDocument>
41 
44 
45 static constexpr std::array confTargets{2, 4, 6, 12, 24, 48, 144, 504, 1008};
46 int getConfTargetForIndex(int index) {
47  if (index+1 > static_cast<int>(confTargets.size())) {
48  return confTargets.back();
49  }
50  if (index < 0) {
51  return confTargets[0];
52  }
53  return confTargets[index];
54 }
55 int getIndexForConfTarget(int target) {
56  for (unsigned int i = 0; i < confTargets.size(); i++) {
57  if (confTargets[i] >= target) {
58  return i;
59  }
60  }
61  return confTargets.size() - 1;
62 }
63 
64 SendCoinsDialog::SendCoinsDialog(const PlatformStyle *_platformStyle, QWidget *parent) :
65  QDialog(parent, GUIUtil::dialog_flags),
66  ui(new Ui::SendCoinsDialog),
67  clientModel(nullptr),
68  model(nullptr),
69  m_coin_control(new CCoinControl),
70  fNewRecipientAllowed(true),
71  fFeeMinimized(true),
72  platformStyle(_platformStyle)
73 {
74  ui->setupUi(this);
75 
76  if (!_platformStyle->getImagesOnButtons()) {
77  ui->addButton->setIcon(QIcon());
78  ui->clearButton->setIcon(QIcon());
79  ui->sendButton->setIcon(QIcon());
80  } else {
81  ui->addButton->setIcon(_platformStyle->SingleColorIcon(":/icons/add"));
82  ui->clearButton->setIcon(_platformStyle->SingleColorIcon(":/icons/remove"));
83  ui->sendButton->setIcon(_platformStyle->SingleColorIcon(":/icons/send"));
84  }
85 
87 
88  addEntry();
89 
90  connect(ui->addButton, &QPushButton::clicked, this, &SendCoinsDialog::addEntry);
91  connect(ui->clearButton, &QPushButton::clicked, this, &SendCoinsDialog::clear);
92 
93  // Coin Control
94  connect(ui->pushButtonCoinControl, &QPushButton::clicked, this, &SendCoinsDialog::coinControlButtonClicked);
95  connect(ui->checkBoxCoinControlChange, &QCheckBox::stateChanged, this, &SendCoinsDialog::coinControlChangeChecked);
96  connect(ui->lineEditCoinControlChange, &QValidatedLineEdit::textEdited, this, &SendCoinsDialog::coinControlChangeEdited);
97 
98  // Coin Control: clipboard actions
99  QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this);
100  QAction *clipboardAmountAction = new QAction(tr("Copy amount"), this);
101  QAction *clipboardFeeAction = new QAction(tr("Copy fee"), this);
102  QAction *clipboardAfterFeeAction = new QAction(tr("Copy after fee"), this);
103  QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this);
104  QAction *clipboardLowOutputAction = new QAction(tr("Copy dust"), this);
105  QAction *clipboardChangeAction = new QAction(tr("Copy change"), this);
106  connect(clipboardQuantityAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardQuantity);
107  connect(clipboardAmountAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardAmount);
108  connect(clipboardFeeAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardFee);
109  connect(clipboardAfterFeeAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardAfterFee);
110  connect(clipboardBytesAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardBytes);
111  connect(clipboardLowOutputAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardLowOutput);
112  connect(clipboardChangeAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardChange);
113  ui->labelCoinControlQuantity->addAction(clipboardQuantityAction);
114  ui->labelCoinControlAmount->addAction(clipboardAmountAction);
115  ui->labelCoinControlFee->addAction(clipboardFeeAction);
116  ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction);
117  ui->labelCoinControlBytes->addAction(clipboardBytesAction);
118  ui->labelCoinControlLowOutput->addAction(clipboardLowOutputAction);
119  ui->labelCoinControlChange->addAction(clipboardChangeAction);
120 
121  // init transaction fee section
122  QSettings settings;
123  if (!settings.contains("fFeeSectionMinimized"))
124  settings.setValue("fFeeSectionMinimized", true);
125  if (!settings.contains("nFeeRadio") && settings.contains("nTransactionFee") && settings.value("nTransactionFee").toLongLong() > 0) // compatibility
126  settings.setValue("nFeeRadio", 1); // custom
127  if (!settings.contains("nFeeRadio"))
128  settings.setValue("nFeeRadio", 0); // recommended
129  if (!settings.contains("nSmartFeeSliderPosition"))
130  settings.setValue("nSmartFeeSliderPosition", 0);
131  if (!settings.contains("nTransactionFee"))
132  settings.setValue("nTransactionFee", (qint64)DEFAULT_PAY_TX_FEE);
133  ui->groupFee->setId(ui->radioSmartFee, 0);
134  ui->groupFee->setId(ui->radioCustomFee, 1);
135  ui->groupFee->button((int)std::max(0, std::min(1, settings.value("nFeeRadio").toInt())))->setChecked(true);
136  ui->customFee->SetAllowEmpty(false);
137  ui->customFee->setValue(settings.value("nTransactionFee").toLongLong());
138  minimizeFeeSection(settings.value("fFeeSectionMinimized").toBool());
139 
141 }
142 
144 {
145  this->clientModel = _clientModel;
146 
147  if (_clientModel) {
149  }
150 }
151 
153 {
154  this->model = _model;
155 
156  if(_model && _model->getOptionsModel())
157  {
158  for(int i = 0; i < ui->entries->count(); ++i)
159  {
160  SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
161  if(entry)
162  {
163  entry->setModel(_model);
164  }
165  }
166 
169  refreshBalance();
170 
171  // Coin Control
174  ui->frameCoinControl->setVisible(_model->getOptionsModel()->getCoinControlFeatures());
176 
177  // fee section
178  for (const int n : confTargets) {
179  ui->confTargetSelector->addItem(tr("%1 (%2 blocks)").arg(GUIUtil::formatNiceTimeOffset(n*Params().GetConsensus().nPowTargetSpacing)).arg(n));
180  }
181  connect(ui->confTargetSelector, qOverload<int>(&QComboBox::currentIndexChanged), this, &SendCoinsDialog::updateSmartFeeLabel);
182  connect(ui->confTargetSelector, qOverload<int>(&QComboBox::currentIndexChanged), this, &SendCoinsDialog::coinControlUpdateLabels);
183 
184 #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
185  connect(ui->groupFee, &QButtonGroup::idClicked, this, &SendCoinsDialog::updateFeeSectionControls);
186  connect(ui->groupFee, &QButtonGroup::idClicked, this, &SendCoinsDialog::coinControlUpdateLabels);
187 #else
188  connect(ui->groupFee, qOverload<int>(&QButtonGroup::buttonClicked), this, &SendCoinsDialog::updateFeeSectionControls);
189  connect(ui->groupFee, qOverload<int>(&QButtonGroup::buttonClicked), this, &SendCoinsDialog::coinControlUpdateLabels);
190 #endif
191 
193  connect(ui->optInRBF, &QCheckBox::stateChanged, this, &SendCoinsDialog::updateSmartFeeLabel);
194  connect(ui->optInRBF, &QCheckBox::stateChanged, this, &SendCoinsDialog::coinControlUpdateLabels);
195  CAmount requiredFee = model->wallet().getRequiredFee(1000);
196  ui->customFee->SetMinValue(requiredFee);
197  if (ui->customFee->value() < requiredFee) {
198  ui->customFee->setValue(requiredFee);
199  }
200  ui->customFee->setSingleStep(requiredFee);
203 
204  // set default rbf checkbox state
205  ui->optInRBF->setCheckState(Qt::Checked);
206 
207  if (model->wallet().hasExternalSigner()) {
208  //: "device" usually means a hardware wallet.
209  ui->sendButton->setText(tr("Sign on device"));
210  if (gArgs.GetArg("-signer", "") != "") {
211  ui->sendButton->setEnabled(true);
212  ui->sendButton->setToolTip(tr("Connect your hardware wallet first."));
213  } else {
214  ui->sendButton->setEnabled(false);
215  //: "External signer" means using devices such as hardware wallets.
216  ui->sendButton->setToolTip(tr("Set external signer script path in Options -> Wallet"));
217  }
218  } else if (model->wallet().privateKeysDisabled()) {
219  ui->sendButton->setText(tr("Cr&eate Unsigned"));
220  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(PACKAGE_NAME));
221  }
222 
223  // set the smartfee-sliders default value (wallets default conf.target or last stored value)
224  QSettings settings;
225  if (settings.value("nSmartFeeSliderPosition").toInt() != 0) {
226  // migrate nSmartFeeSliderPosition to nConfTarget
227  // nConfTarget is available since 0.15 (replaced nSmartFeeSliderPosition)
228  int nConfirmTarget = 25 - settings.value("nSmartFeeSliderPosition").toInt(); // 25 == old slider range
229  settings.setValue("nConfTarget", nConfirmTarget);
230  settings.remove("nSmartFeeSliderPosition");
231  }
232  if (settings.value("nConfTarget").toInt() == 0)
234  else
235  ui->confTargetSelector->setCurrentIndex(getIndexForConfTarget(settings.value("nConfTarget").toInt()));
236  }
237 }
238 
240 {
241  QSettings settings;
242  settings.setValue("fFeeSectionMinimized", fFeeMinimized);
243  settings.setValue("nFeeRadio", ui->groupFee->checkedId());
244  settings.setValue("nConfTarget", getConfTargetForIndex(ui->confTargetSelector->currentIndex()));
245  settings.setValue("nTransactionFee", (qint64)ui->customFee->value());
246 
247  delete ui;
248 }
249 
250 bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informative_text, QString& detailed_text)
251 {
252  QList<SendCoinsRecipient> recipients;
253  bool valid = true;
254 
255  for(int i = 0; i < ui->entries->count(); ++i)
256  {
257  SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
258  if(entry)
259  {
260  if(entry->validate(model->node()))
261  {
262  recipients.append(entry->getValue());
263  }
264  else if (valid)
265  {
266  ui->scrollArea->ensureWidgetVisible(entry);
267  valid = false;
268  }
269  }
270  }
271 
272  if(!valid || recipients.isEmpty())
273  {
274  return false;
275  }
276 
277  fNewRecipientAllowed = false;
279  if(!ctx.isValid())
280  {
281  // Unlock wallet was cancelled
282  fNewRecipientAllowed = true;
283  return false;
284  }
285 
286  // prepare transaction for getting txFee earlier
287  m_current_transaction = std::make_unique<WalletModelTransaction>(recipients);
288  WalletModel::SendCoinsReturn prepareStatus;
289 
291 
293 
294  // process prepareStatus and on error generate message shown to user
295  processSendCoinsReturn(prepareStatus,
297 
298  if(prepareStatus.status != WalletModel::OK) {
299  fNewRecipientAllowed = true;
300  return false;
301  }
302 
303  CAmount txFee = m_current_transaction->getTransactionFee();
304  QStringList formatted;
305  for (const SendCoinsRecipient &rcp : m_current_transaction->getRecipients())
306  {
307  // generate amount string with wallet name in case of multiwallet
308  QString amount = BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
309  if (model->isMultiwallet()) {
310  amount.append(tr(" from wallet '%1'").arg(GUIUtil::HtmlEscape(model->getWalletName())));
311  }
312 
313  // generate address string
314  QString address = rcp.address;
315 
316  QString recipientElement;
317 
318  {
319  if(rcp.label.length() > 0) // label with address
320  {
321  recipientElement.append(tr("%1 to '%2'").arg(amount, GUIUtil::HtmlEscape(rcp.label)));
322  recipientElement.append(QString(" (%1)").arg(address));
323  }
324  else // just address
325  {
326  recipientElement.append(tr("%1 to %2").arg(amount, address));
327  }
328  }
329  formatted.append(recipientElement);
330  }
331 
332  /*: Message displayed when attempting to create a transaction. Cautionary text to prompt the user to verify
333  that the displayed transaction details represent the transaction the user intends to create. */
334  question_string.append(tr("Do you want to create this transaction?"));
335  question_string.append("<br /><span style='font-size:10pt;'>");
337  /*: Text to inform a user attempting to create a transaction of their current options. At this stage,
338  a user can only create a PSBT. This string is displayed when private keys are disabled and an external
339  signer is not available. */
340  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(PACKAGE_NAME));
341  } else if (model->getOptionsModel()->getEnablePSBTControls()) {
342  /*: Text to inform a user attempting to create a transaction of their current options. At this stage,
343  a user can send their transaction or create a PSBT. This string is displayed when both private keys
344  and PSBT controls are enabled. */
345  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(PACKAGE_NAME));
346  } else {
347  /*: Text to prompt a user to review the details of the transaction they are attempting to send. */
348  question_string.append(tr("Please, review your transaction."));
349  }
350  question_string.append("</span>%1");
351 
352  if(txFee > 0)
353  {
354  // append fee string if a fee is required
355  question_string.append("<hr /><b>");
356  question_string.append(tr("Transaction fee"));
357  question_string.append("</b>");
358 
359  // append transaction size
360  question_string.append(" (" + QString::number((double)m_current_transaction->getTransactionSize() / 1000) + " kB): ");
361 
362  // append transaction fee value
363  question_string.append("<span style='color:#aa0000; font-weight:bold;'>");
364  question_string.append(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), txFee));
365  question_string.append("</span><br />");
366 
367  // append RBF message according to transaction's signalling
368  question_string.append("<span style='font-size:10pt; font-weight:normal;'>");
369  if (ui->optInRBF->isChecked()) {
370  question_string.append(tr("You can increase the fee later (signals Replace-By-Fee, BIP-125)."));
371  } else {
372  question_string.append(tr("Not signalling Replace-By-Fee, BIP-125."));
373  }
374  question_string.append("</span>");
375  }
376 
377  // add total amount in all subdivision units
378  question_string.append("<hr />");
379  CAmount totalAmount = m_current_transaction->getTotalTransactionAmount() + txFee;
380  QStringList alternativeUnits;
381  for (const BitcoinUnit u : BitcoinUnits::availableUnits()) {
382  if(u != model->getOptionsModel()->getDisplayUnit())
383  alternativeUnits.append(BitcoinUnits::formatHtmlWithUnit(u, totalAmount));
384  }
385  question_string.append(QString("<b>%1</b>: <b>%2</b>").arg(tr("Total Amount"))
387  question_string.append(QString("<br /><span style='font-size:10pt; font-weight:normal;'>(=%1)</span>")
388  .arg(alternativeUnits.join(" " + tr("or") + " ")));
389 
390  if (formatted.size() > 1) {
391  question_string = question_string.arg("");
392  informative_text = tr("To review recipient list click \"Show Details…\"");
393  detailed_text = formatted.join("\n\n");
394  } else {
395  question_string = question_string.arg("<br /><br />" + formatted.at(0));
396  }
397 
398  return true;
399 }
400 
402 {
403  // Serialize the PSBT
405  ssTx << psbtx;
406  GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str());
407  QMessageBox msgBox;
408  msgBox.setText("Unsigned Transaction");
409  msgBox.setInformativeText("The PSBT has been copied to the clipboard. You can also save it.");
410  msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard);
411  msgBox.setDefaultButton(QMessageBox::Discard);
412  switch (msgBox.exec()) {
413  case QMessageBox::Save: {
414  QString selectedFilter;
415  QString fileNameSuggestion = "";
416  bool first = true;
417  for (const SendCoinsRecipient &rcp : m_current_transaction->getRecipients()) {
418  if (!first) {
419  fileNameSuggestion.append(" - ");
420  }
421  QString labelOrAddress = rcp.label.isEmpty() ? rcp.address : rcp.label;
422  QString amount = BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
423  fileNameSuggestion.append(labelOrAddress + "-" + amount);
424  first = false;
425  }
426  fileNameSuggestion.append(".psbt");
427  QString filename = GUIUtil::getSaveFileName(this,
428  tr("Save Transaction Data"), fileNameSuggestion,
429  //: Expanded name of the binary PSBT file format. See: BIP 174.
430  tr("Partially Signed Transaction (Binary)") + QLatin1String(" (*.psbt)"), &selectedFilter);
431  if (filename.isEmpty()) {
432  return;
433  }
434  std::ofstream out{filename.toLocal8Bit().data(), std::ofstream::out | std::ofstream::binary};
435  out << ssTx.str();
436  out.close();
437  Q_EMIT message(tr("PSBT saved"), "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  TransactionError err;
449  try {
450  err = model->wallet().fillPSBT(SIGHASH_ALL, /*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  }
456  //: "External signer" means using devices such as hardware wallets.
457  QMessageBox::critical(nullptr, tr("External signer not found"), "External signer not found");
458  return false;
459  }
461  //: "External signer" means using devices such as hardware wallets.
462  QMessageBox::critical(nullptr, tr("External signer failure"), "External signer failure");
463  return false;
464  }
465  if (err != TransactionError::OK) {
466  tfm::format(std::cerr, "Failed to sign PSBT");
468  return false;
469  }
470  // fillPSBT does not always properly finalize
471  complete = FinalizeAndExtractPSBT(psbtx, mtx);
472  return true;
473 }
474 
475 void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
476 {
477  if(!model || !model->getOptionsModel())
478  return;
479 
480  QString question_string, informative_text, detailed_text;
481  if (!PrepareSendText(question_string, informative_text, detailed_text)) return;
483 
484  const QString confirmation = tr("Confirm send coins");
485  const bool enable_send{!model->wallet().privateKeysDisabled() || model->wallet().hasExternalSigner()};
486  const bool always_show_unsigned{model->getOptionsModel()->getEnablePSBTControls()};
487  auto confirmationDialog = new SendConfirmationDialog(confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, enable_send, always_show_unsigned, this);
488  confirmationDialog->setAttribute(Qt::WA_DeleteOnClose);
489  // TODO: Replace QDialog::exec() with safer QDialog::show().
490  const auto retval = static_cast<QMessageBox::StandardButton>(confirmationDialog->exec());
491 
492  if(retval != QMessageBox::Yes && retval != QMessageBox::Save)
493  {
494  fNewRecipientAllowed = true;
495  return;
496  }
497 
498  bool send_failure = false;
499  if (retval == QMessageBox::Save) {
500  // "Create Unsigned" clicked
502  PartiallySignedTransaction psbtx(mtx);
503  bool complete = false;
504  // Fill without signing
505  TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete);
506  assert(!complete);
508 
509  // Copy PSBT to clipboard and offer to save
510  presentPSBT(psbtx);
511  } else {
512  // "Send" clicked
514  bool broadcast = true;
515  if (model->wallet().hasExternalSigner()) {
517  PartiallySignedTransaction psbtx(mtx);
518  bool complete = false;
519  // Always fill without signing first. This prevents an external signer
520  // from being called prematurely and is not expensive.
521  TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete);
522  assert(!complete);
524  send_failure = !signWithExternalSigner(psbtx, mtx, complete);
525  // Don't broadcast when user rejects it on the device or there's a failure:
526  broadcast = complete && !send_failure;
527  if (!send_failure) {
528  // A transaction signed with an external signer is not always complete,
529  // e.g. in a multisig wallet.
530  if (complete) {
531  // Prepare transaction for broadcast transaction if complete
532  const CTransactionRef tx = MakeTransactionRef(mtx);
533  m_current_transaction->setWtx(tx);
534  } else {
535  presentPSBT(psbtx);
536  }
537  }
538  }
539 
540  // Broadcast the transaction, unless an external signer was used and it
541  // failed, or more signatures are needed.
542  if (broadcast) {
543  // now send the prepared transaction
545  Q_EMIT coinsSent(m_current_transaction->getWtx()->GetHash());
546  }
547  }
548  if (!send_failure) {
549  accept();
550  m_coin_control->UnSelectAll();
552  }
553  fNewRecipientAllowed = true;
554  m_current_transaction.reset();
555 }
556 
558 {
559  m_current_transaction.reset();
560 
561  // Clear coin control settings
562  m_coin_control->UnSelectAll();
563  ui->checkBoxCoinControlChange->setChecked(false);
566 
567  // Remove entries until only one left
568  while(ui->entries->count())
569  {
570  ui->entries->takeAt(0)->widget()->deleteLater();
571  }
572  addEntry();
573 
575 }
576 
578 {
579  clear();
580 }
581 
583 {
584  clear();
585 }
586 
588 {
589  SendCoinsEntry *entry = new SendCoinsEntry(platformStyle, this);
590  entry->setModel(model);
591  ui->entries->addWidget(entry);
596 
597  // Focus the field, so that entry can start immediately
598  entry->clear();
599  entry->setFocus();
601  qApp->processEvents();
602  QScrollBar* bar = ui->scrollArea->verticalScrollBar();
603  if(bar)
604  bar->setSliderPosition(bar->maximum());
605 
607  return entry;
608 }
609 
611 {
612  setupTabChain(nullptr);
614 }
615 
617 {
618  entry->hide();
619 
620  // If the last entry is about to be removed add an empty one
621  if (ui->entries->count() == 1)
622  addEntry();
623 
624  entry->deleteLater();
625 
627 }
628 
629 QWidget *SendCoinsDialog::setupTabChain(QWidget *prev)
630 {
631  for(int i = 0; i < ui->entries->count(); ++i)
632  {
633  SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
634  if(entry)
635  {
636  prev = entry->setupTabChain(prev);
637  }
638  }
639  QWidget::setTabOrder(prev, ui->sendButton);
640  QWidget::setTabOrder(ui->sendButton, ui->clearButton);
641  QWidget::setTabOrder(ui->clearButton, ui->addButton);
642  return ui->addButton;
643 }
644 
645 void SendCoinsDialog::setAddress(const QString &address)
646 {
647  SendCoinsEntry *entry = nullptr;
648  // Replace the first entry if it is still unused
649  if(ui->entries->count() == 1)
650  {
651  SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
652  if(first->isClear())
653  {
654  entry = first;
655  }
656  }
657  if(!entry)
658  {
659  entry = addEntry();
660  }
661 
662  entry->setAddress(address);
663 }
664 
666 {
668  return;
669 
670  SendCoinsEntry *entry = nullptr;
671  // Replace the first entry if it is still unused
672  if(ui->entries->count() == 1)
673  {
674  SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
675  if(first->isClear())
676  {
677  entry = first;
678  }
679  }
680  if(!entry)
681  {
682  entry = addEntry();
683  }
684 
685  entry->setValue(rv);
687 }
688 
690 {
691  // Just paste the entry, all pre-checks
692  // are done in paymentserver.cpp.
693  pasteEntry(rv);
694  return true;
695 }
696 
698 {
699  if(model && model->getOptionsModel())
700  {
701  CAmount balance = balances.balance;
702  if (model->wallet().hasExternalSigner()) {
703  ui->labelBalanceName->setText(tr("External balance:"));
704  } else if (model->wallet().privateKeysDisabled()) {
705  balance = balances.watch_only_balance;
706  ui->labelBalanceName->setText(tr("Watch-only balance:"));
707  }
709  }
710 }
711 
713 {
717 }
718 
719 void SendCoinsDialog::processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg)
720 {
721  QPair<QString, CClientUIInterface::MessageBoxFlags> msgParams;
722  // Default to a warning message, override if error message is needed
723  msgParams.second = CClientUIInterface::MSG_WARNING;
724 
725  // This comment is specific to SendCoinsDialog usage of WalletModel::SendCoinsReturn.
726  // All status values are used only in WalletModel::prepareTransaction()
727  switch(sendCoinsReturn.status)
728  {
730  msgParams.first = tr("The recipient address is not valid. Please recheck.");
731  break;
733  msgParams.first = tr("The amount to pay must be larger than 0.");
734  break;
736  msgParams.first = tr("The amount exceeds your balance.");
737  break;
739  msgParams.first = tr("The total exceeds your balance when the %1 transaction fee is included.").arg(msgArg);
740  break;
742  msgParams.first = tr("Duplicate address found: addresses should only be used once each.");
743  break;
745  msgParams.first = tr("Transaction creation failed!");
746  msgParams.second = CClientUIInterface::MSG_ERROR;
747  break;
749  msgParams.first = tr("A fee higher than %1 is considered an absurdly high fee.").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), model->wallet().getDefaultMaxTxFee()));
750  break;
751  // included to prevent a compiler warning.
752  case WalletModel::OK:
753  default:
754  return;
755  }
756 
757  Q_EMIT message(tr("Send Coins"), msgParams.first, msgParams.second);
758 }
759 
761 {
762  ui->labelFeeMinimized->setVisible(fMinimize);
763  ui->buttonChooseFee ->setVisible(fMinimize);
764  ui->buttonMinimizeFee->setVisible(!fMinimize);
765  ui->frameFeeSelection->setVisible(!fMinimize);
766  ui->horizontalLayoutSmartFee->setContentsMargins(0, (fMinimize ? 0 : 6), 0, 0);
767  fFeeMinimized = fMinimize;
768 }
769 
771 {
772  minimizeFeeSection(false);
773 }
774 
776 {
778  minimizeFeeSection(true);
779 }
780 
782 {
783  // Include watch-only for wallets without private key
785 
786  // Calculate available amount to send.
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 {
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  // Include watch-only for wallets without private key
840 }
841 
842 void SendCoinsDialog::updateNumberOfBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, SyncType synctype, SynchronizationState sync_state) {
843  if (sync_state == SynchronizationState::POST_INIT) {
845  }
846 }
847 
849 {
850  if(!model || !model->getOptionsModel())
851  return;
853  m_coin_control->m_feerate.reset(); // Explicitly use only fee estimation rate for smart fee labels
854  int returned_target;
855  FeeReason reason;
856  CFeeRate feeRate = CFeeRate(model->wallet().getMinimumFee(1000, *m_coin_control, &returned_target, &reason));
857 
859 
860  if (reason == FeeReason::FALLBACK) {
861  ui->labelSmartFee2->show(); // (Smart fee not initialized yet. This usually takes a few blocks...)
862  ui->labelFeeEstimation->setText("");
863  ui->fallbackFeeWarningLabel->setVisible(true);
864  int lightness = ui->fallbackFeeWarningLabel->palette().color(QPalette::WindowText).lightness();
865  QColor warning_colour(255 - (lightness / 5), 176 - (lightness / 3), 48 - (lightness / 14));
866  ui->fallbackFeeWarningLabel->setStyleSheet("QLabel { color: " + warning_colour.name() + "; }");
867  ui->fallbackFeeWarningLabel->setIndent(GUIUtil::TextWidth(QFontMetrics(ui->fallbackFeeWarningLabel->font()), "x"));
868  }
869  else
870  {
871  ui->labelSmartFee2->hide();
872  ui->labelFeeEstimation->setText(tr("Estimated to begin confirmation within %n block(s).", "", returned_target));
873  ui->fallbackFeeWarningLabel->setVisible(false);
874  }
875 
877 }
878 
879 // Coin Control: copy label "Quantity" to clipboard
881 {
883 }
884 
885 // Coin Control: copy label "Amount" to clipboard
887 {
888  GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left(ui->labelCoinControlAmount->text().indexOf(" ")));
889 }
890 
891 // Coin Control: copy label "Fee" to clipboard
893 {
894  GUIUtil::setClipboard(ui->labelCoinControlFee->text().left(ui->labelCoinControlFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
895 }
896 
897 // Coin Control: copy label "After fee" to clipboard
899 {
900  GUIUtil::setClipboard(ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
901 }
902 
903 // Coin Control: copy label "Bytes" to clipboard
905 {
907 }
908 
909 // Coin Control: copy label "Dust" to clipboard
911 {
913 }
914 
915 // Coin Control: copy label "Change" to clipboard
917 {
918  GUIUtil::setClipboard(ui->labelCoinControlChange->text().left(ui->labelCoinControlChange->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
919 }
920 
921 // Coin Control: settings menu - coin control enabled/disabled by user
923 {
924  ui->frameCoinControl->setVisible(checked);
925 
926  if (!checked && model) { // coin control features disabled
927  m_coin_control = std::make_unique<CCoinControl>();
928  }
929 
931 }
932 
933 // Coin Control: button inputs -> show actual coin control dialog
935 {
937  connect(dlg, &QDialog::finished, this, &SendCoinsDialog::coinControlUpdateLabels);
939 }
940 
941 // Coin Control: checkbox custom change address
943 {
944  if (state == Qt::Unchecked)
945  {
946  m_coin_control->destChange = CNoDestination();
948  }
949  else
950  // use this to re-validate an already entered address
952 
953  ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked));
954 }
955 
956 // Coin Control: custom change address changed
958 {
959  if (model && model->getAddressTableModel())
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  {
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
1041  ui->widgetCoinControl->show();
1042  }
1043  else
1044  {
1045  // hide coin control stats
1047  ui->widgetCoinControl->hide();
1049  }
1050 }
1051 
1052 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)
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 }
std::shared_ptr< const CTransaction > CTransactionRef
Definition: transaction.h:414
virtual bool privateKeysDisabled()=0
bool signWithExternalSigner(PartiallySignedTransaction &psbt, CMutableTransaction &mtx, bool &complete)
void removeEntry(SendCoinsEntry *entry)
Predefined combinations for certain default usage cases.
Definition: interface_ui.h:65
static QList< Unit > availableUnits()
Get list of units, for drop-down box.
ArgsManager gArgs
Definition: system.cpp:86
OptionsModel * getOptionsModel() const
Unit
Bitcoin units.
Definition: bitcoinunits.h:42
interfaces::Wallet & wallet() const
Definition: walletmodel.h:145
void setValue(const SendCoinsRecipient &value)
void payAmountChanged()
Utility functions used by the Bitcoin Qt UI.
Definition: bitcoingui.h:60
QButtonGroup * groupFee
SynchronizationState
Current sync state passed to tip changed callbacks.
Definition: validation.h:88
assert(!tx.IsCoinBase())
SendCoinsReturn prepareTransaction(WalletModelTransaction &transaction, const wallet::CCoinControl &coinControl)
void presentPSBT(PartiallySignedTransaction &psbt)
void sendButtonClicked(bool checked)
void updateFeeMinimizedLabel()
void setFocus()
void on_buttonChooseFee_clicked()
SendCoinsRecipient getValue()
QHBoxLayout * horizontalLayoutSmartFee
int TextWidth(const QFontMetrics &fm, const QString &text)
Returns the distance in pixels appropriate for drawing a subsequent character after text...
Definition: guiutil.cpp:895
void reject() override
UnlockContext requestUnlock()
QPushButton * clearButton
QPushButton * sendButton
void coinControlClipboardQuantity()
std::string str() const
Definition: streams.h:224
void coinControlClipboardAfterFee()
void setAddress(const QString &address)
interfaces::WalletBalances getCachedBalance() const
virtual CAmount getDefaultMaxTxFee()=0
Get max tx fee.
bool IsValidDestination(const CTxDestination &dest)
Check whether a CTxDestination is a CNoDestination.
Definition: standard.cpp:356
QValidatedLineEdit * lineEditCoinControlChange
#define SEND_CONFIRM_DELAY
BitcoinUnit getDisplayUnit() const
Definition: optionsmodel.h:93
#define PACKAGE_NAME
std::string EncodeBase64(Span< const unsigned char > input)
void coinControlFeaturesChanged(bool)
QIcon SingleColorIcon(const QString &filename) const
Colorize an icon (given filename) with the icon color.
QRadioButton * radioCustomFee
QString HtmlEscape(const QString &str, bool fMultiLine)
Definition: guiutil.cpp:240
bool handlePaymentRequest(const SendCoinsRecipient &recipient)
A version of CTransaction with the PSBT format.
Definition: psbt.h:946
Double ended buffer combining vector and stream-like interfaces.
Definition: streams.h:185
QScrollArea * scrollArea
QLabel * labelCoinControlAfterFee
void ShowModalDialogAsynchronously(QDialog *dialog)
Shows a QDialog instance asynchronously, and deletes it on close.
Definition: guiutil.cpp:989
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:417
virtual bool isSpendable(const CTxDestination &dest)=0
Return whether wallet has private key.
QLabel * labelCoinControlQuantity
A single entry in the dialog for sending bitcoins.
QLabel * labelCoinControlLowOutput
QPushButton * pushButtonCoinControl
QLabel * labelCoinControlAutomaticallySelected
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
constexpr CAmount DEFAULT_PAY_TX_FEE
-paytxfee default
Definition: wallet.h:77
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:122
void displayUnitChanged(BitcoinUnit unit)
void setBalance(const interfaces::WalletBalances &balances)
void setAddress(const QString &address)
void setEnabled(bool fEnabled)
Enable/Disable.
void coinControlClipboardChange()
Collection of wallet balances.
Definition: wallet.h:365
void useAvailableBalance(SendCoinsEntry *entry)
void setClientModel(ClientModel *clientModel)
void setClipboard(const QString &str)
Definition: guiutil.cpp:652
ClientModel * clientModel
static secp256k1_context * ctx
Definition: tests.c:34
void format(std::ostream &out, const char *fmt, const Args &... args)
Format list of arguments to the stream according to given format string.
Definition: tinyformat.h:1062
void numBlocksChanged(int count, const QDateTime &blockDate, double nVerificationProgress, SyncType header, SynchronizationState sync_state)
QPushButton * buttonChooseFee
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:391
WalletModel * model
Dialog for sending bitcoins.
QCheckBox * checkBoxCoinControlChange
void coinControlChangeEdited(const QString &)
std::variant< CNoDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessV1Taproot, WitnessUnknown > CTxDestination
A txout script template with a specific destination.
Definition: standard.h:149
FeeReason
Definition: fees.h:44
QString getWalletName() const
void SetMinValue(const CAmount &value)
Set the minimum value in satoshis.
bool getEnablePSBTControls() const
Definition: optionsmodel.h:98
interfaces::Node & node() const
Definition: walletmodel.h:144
void removeEntry(SendCoinsEntry *entry)
Model for Bitcoin network client.
Definition: clientmodel.h:54
void setSingleStep(const CAmount &step)
Set single step in satoshis.
bool isMultiwallet() const
void setModel(WalletModel *model)
int getConfTargetForIndex(int index)
SendCoinsDialog(const PlatformStyle *platformStyle, QWidget *parent=nullptr)
void accept() override
bool getCoinControlFeatures() const
Definition: optionsmodel.h:96
void setupUi(QDialog *SendCoinsDialog)
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:415
QRadioButton * radioSmartFee
SyncType
Definition: clientmodel.h:40
void checkSubtractFeeFromAmount()
bool isClear()
Return whether the entry is still empty and unedited.
void minimizeFeeSection(bool fMinimize)
void coinControlClipboardLowOutput()
QLabel * labelCoinControlChangeLabel
void updateFeeSectionControls()
QWidget * scrollAreaWidgetContents
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:52
static const int PROTOCOL_VERSION
network protocol versioning
Definition: version.h:12
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:304
std::string GetArg(const std::string &strArg, const std::string &strDefault) const
Return string argument or default value.
Definition: system.cpp:604
void setAmount(const CAmount &amount)
void setModel(WalletModel *model)
void processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg=QString())
TransactionError
Definition: error.h:22
Fee rate in satoshis per kilovirtualbyte: CAmount / kvB.
Definition: feerate.h:32
static int count
Definition: tests.c:33
QComboBox * confTargetSelector
void setEnabled(bool enabled)
void coinControlClipboardBytes()
void useAvailableBalance(SendCoinsEntry *entry)
A mutable version of CTransaction.
Definition: transaction.h:372
virtual TransactionError fillPSBT(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.
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:767
void coinControlClipboardAmount()
void sendCoins(WalletModelTransaction &transaction)
void on_buttonMinimizeFee_clicked()
QLabel * fallbackFeeWarningLabel
CTxDestination DecodeDestination(const std::string &str, std::string &error_msg, std::vector< int > *error_locations)
Definition: key_io.cpp:281
void pasteEntry(const SendCoinsRecipient &rv)
void setDisplayUnit(BitcoinUnit new_unit)
Change unit used to display amount.
QAbstractButton * yesButton
BitcoinAmountField * customFee
virtual CAmount getRequiredFee(unsigned int tx_bytes)=0
Get required fee.
void message(const QString &title, const QString &message, unsigned int style)
void coinsSent(const uint256 &txid)
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.
QPushButton * buttonMinimizeFee
void setValue(const CAmount &value)
CAmount GetFeePerK() const
Return the fee in satoshis for a vsize of 1000 vbytes.
Definition: feerate.h:65
void setText(const QString &)
void balanceChanged(const interfaces::WalletBalances &balances)
Coin Control Features.
Definition: coincontrol.h:29
bool getImagesOnButtons() const
Definition: platformstyle.h:21
void coinControlButtonClicked()
void coinControlClipboardFee()
QLabel * labelCoinControlInsuffFunds
void SetAllowEmpty(bool allow)
If allow empty is set to false the field will be set to the minimum allowed value if left empty...
void updateNumberOfBlocks(int count, const QDateTime &blockDate, double nVerificationProgress, SyncType synctype, SynchronizationState sync_state)
void coinControlChangeChecked(int)