Bitcoin ABC  0.22.13
P2P Digital Currency
sendcoinsdialog.cpp
Go to the documentation of this file.
1 // Copyright (c) 2011-2016 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)
6 #include <config/bitcoin-config.h>
7 #endif
8 
9 #include <qt/forms/ui_sendcoinsdialog.h>
10 #include <qt/sendcoinsdialog.h>
11 
12 #include <chainparams.h>
13 #include <interfaces/node.h>
14 #include <key_io.h>
15 #include <node/ui_interface.h>
16 #include <qt/addresstablemodel.h>
17 #include <qt/bitcoinunits.h>
18 #include <qt/clientmodel.h>
19 #include <qt/coincontroldialog.h>
20 #include <qt/guiutil.h>
21 #include <qt/optionsmodel.h>
22 #include <qt/platformstyle.h>
23 #include <qt/sendcoinsentry.h>
24 #include <txmempool.h>
25 #include <wallet/coincontrol.h>
26 #include <wallet/fees.h>
27 #include <wallet/wallet.h>
28 
29 #include <QScrollBar>
30 #include <QSettings>
31 #include <QTextDocument>
32 
34  WalletModel *_model, QWidget *parent)
35  : QDialog(parent), ui(new Ui::SendCoinsDialog), clientModel(nullptr),
36  model(_model), fNewRecipientAllowed(true), fFeeMinimized(true),
37  platformStyle(_platformStyle) {
38  ui->setupUi(this);
39 
40  if (!_platformStyle->getImagesOnButtons()) {
41  ui->addButton->setIcon(QIcon());
42  ui->clearButton->setIcon(QIcon());
43  ui->sendButton->setIcon(QIcon());
44  } else {
45  ui->addButton->setIcon(_platformStyle->SingleColorIcon(":/icons/add"));
46  ui->clearButton->setIcon(
47  _platformStyle->SingleColorIcon(":/icons/remove"));
48  ui->sendButton->setIcon(
49  _platformStyle->SingleColorIcon(":/icons/send"));
50  }
51 
52  GUIUtil::setupAddressWidget(ui->lineEditCoinControlChange, this);
53 
54  addEntry();
55 
56  connect(ui->addButton, &QPushButton::clicked, this,
58  connect(ui->clearButton, &QPushButton::clicked, this,
60 
61  // Coin Control
62  connect(ui->pushButtonCoinControl, &QPushButton::clicked, this,
64  connect(ui->checkBoxCoinControlChange, &QCheckBox::stateChanged, this,
66  connect(ui->lineEditCoinControlChange, &QValidatedLineEdit::textEdited,
68 
69  // Coin Control: clipboard actions
70  QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this);
71  QAction *clipboardAmountAction = new QAction(tr("Copy amount"), this);
72  QAction *clipboardFeeAction = new QAction(tr("Copy fee"), this);
73  QAction *clipboardAfterFeeAction = new QAction(tr("Copy after fee"), this);
74  QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this);
75  QAction *clipboardLowOutputAction = new QAction(tr("Copy dust"), this);
76  QAction *clipboardChangeAction = new QAction(tr("Copy change"), this);
77  connect(clipboardQuantityAction, &QAction::triggered, this,
79  connect(clipboardAmountAction, &QAction::triggered, this,
81  connect(clipboardFeeAction, &QAction::triggered, this,
83  connect(clipboardAfterFeeAction, &QAction::triggered, this,
85  connect(clipboardBytesAction, &QAction::triggered, this,
87  connect(clipboardLowOutputAction, &QAction::triggered, this,
89  connect(clipboardChangeAction, &QAction::triggered, this,
91  ui->labelCoinControlQuantity->addAction(clipboardQuantityAction);
92  ui->labelCoinControlAmount->addAction(clipboardAmountAction);
93  ui->labelCoinControlFee->addAction(clipboardFeeAction);
94  ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction);
95  ui->labelCoinControlBytes->addAction(clipboardBytesAction);
96  ui->labelCoinControlLowOutput->addAction(clipboardLowOutputAction);
97  ui->labelCoinControlChange->addAction(clipboardChangeAction);
98 
99  // init transaction fee section
100  QSettings settings;
101  if (!settings.contains("fFeeSectionMinimized")) {
102  settings.setValue("fFeeSectionMinimized", true);
103  }
104  // compatibility
105  if (!settings.contains("nFeeRadio") &&
106  settings.contains("nTransactionFee") &&
107  settings.value("nTransactionFee").toLongLong() > 0) {
108  // custom
109  settings.setValue("nFeeRadio", 1);
110  }
111  if (!settings.contains("nFeeRadio")) {
112  // recommended
113  settings.setValue("nFeeRadio", 0);
114  }
115  if (!settings.contains("nTransactionFee")) {
116  settings.setValue("nTransactionFee",
117  qint64(DEFAULT_PAY_TX_FEE / SATOSHI));
118  }
119  ui->groupFee->setId(ui->radioSmartFee, 0);
120  ui->groupFee->setId(ui->radioCustomFee, 1);
121  ui->groupFee
122  ->button(
123  std::max<int>(0, std::min(1, settings.value("nFeeRadio").toInt())))
124  ->setChecked(true);
125  ui->customFee->SetAllowEmpty(false);
126  ui->customFee->setValue(
127  int64_t(settings.value("nTransactionFee").toLongLong()) * SATOSHI);
128  minimizeFeeSection(settings.value("fFeeSectionMinimized").toBool());
129 
130  // Set the model properly.
131  setModel(model);
132 }
133 
135  this->clientModel = _clientModel;
136 
137  if (_clientModel) {
138  connect(_clientModel, &ClientModel::numBlocksChanged, this,
140  }
141 }
142 
144  this->model = _model;
145 
146  if (_model && _model->getOptionsModel()) {
147  for (int i = 0; i < ui->entries->count(); ++i) {
148  SendCoinsEntry *entry = qobject_cast<SendCoinsEntry *>(
149  ui->entries->itemAt(i)->widget());
150  if (entry) {
151  entry->setModel(_model);
152  }
153  }
154 
155  interfaces::WalletBalances balances = _model->wallet().getBalances();
156  setBalance(balances);
157  connect(_model, &WalletModel::balanceChanged, this,
162 
163  // Coin Control
166  connect(_model->getOptionsModel(),
169  ui->frameCoinControl->setVisible(
172 
173  // fee section
174 #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
175  const auto buttonClickedEvent =
176  QOverload<int>::of(&QButtonGroup::idClicked);
177 #else
178  /* QOverload in introduced from Qt 5.7, but we support down to 5.5.1 */
179  const auto buttonClickedEvent =
180  static_cast<void (QButtonGroup::*)(int)>(
181  &QButtonGroup::buttonClicked);
182 #endif
183  connect(ui->groupFee, buttonClickedEvent, this,
185  connect(ui->groupFee, buttonClickedEvent, this,
187  connect(ui->customFee, &BitcoinAmountField::valueChanged, this,
189  Amount 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  if (model->wallet().privateKeysDisabled()) {
199  ui->sendButton->setText(tr("Cr&eate Unsigned"));
200  ui->sendButton->setToolTip(
201  tr("Creates a Partially Signed Bitcoin Transaction (PSBT) for "
202  "use with e.g. an offline %1 wallet, or a PSBT-compatible "
203  "hardware wallet.")
204  .arg(PACKAGE_NAME));
205  }
206  }
207 }
208 
210  QSettings settings;
211  settings.setValue("fFeeSectionMinimized", fFeeMinimized);
212  settings.setValue("nFeeRadio", ui->groupFee->checkedId());
213  settings.setValue("nTransactionFee",
214  qint64(ui->customFee->value() / SATOSHI));
215 
216  delete ui;
217 }
218 
219 bool SendCoinsDialog::PrepareSendText(QString &question_string,
220  QString &informative_text,
221  QString &detailed_text) {
222  QList<SendCoinsRecipient> recipients;
223  bool valid = true;
224 
225  for (int i = 0; i < ui->entries->count(); ++i) {
226  SendCoinsEntry *entry =
227  qobject_cast<SendCoinsEntry *>(ui->entries->itemAt(i)->widget());
228  if (entry) {
229  if (entry->validate(model->node())) {
230  recipients.append(entry->getValue());
231  } else if (valid) {
232  ui->scrollArea->ensureWidgetVisible(entry);
233  valid = false;
234  }
235  }
236  }
237 
238  if (!valid || recipients.isEmpty()) {
239  return false;
240  }
241 
242  fNewRecipientAllowed = false;
244  if (!ctx.isValid()) {
245  // Unlock wallet was cancelled
246  fNewRecipientAllowed = true;
247  return false;
248  }
249 
250  // prepare transaction for getting txFee earlier
252  std::make_unique<WalletModelTransaction>(recipients);
253  WalletModel::SendCoinsReturn prepareStatus;
254 
255  // Always use a CCoinControl instance, use the CoinControlDialog instance if
256  // CoinControl has been enabled
257  CCoinControl ctrl;
260  }
261 
263 
264  prepareStatus = model->prepareTransaction(*m_current_transaction, ctrl);
265 
266  // process prepareStatus and on error generate message shown to user
267  processSendCoinsReturn(prepareStatus,
270  m_current_transaction->getTransactionFee()));
271 
272  if (prepareStatus.status != WalletModel::OK) {
273  fNewRecipientAllowed = true;
274  return false;
275  }
276 
277  Amount txFee = m_current_transaction->getTransactionFee();
278  QStringList formatted;
279  for (const SendCoinsRecipient &rcp :
280  m_current_transaction->getRecipients()) {
281  // generate amount string with wallet name in case of multiwallet
282  QString amount = BitcoinUnits::formatWithUnit(
283  model->getOptionsModel()->getDisplayUnit(), rcp.amount);
284  if (model->isMultiwallet()) {
285  amount.append(
286  tr(" from wallet '%1'")
288  }
289  // generate address string
290  QString address = rcp.address;
291 
292  QString recipientElement;
293 
294 #ifdef ENABLE_BIP70
295  // normal payment
296  if (!rcp.paymentRequest.IsInitialized())
297 #endif
298  {
299  if (rcp.label.length() > 0) {
300  // label with address
301  recipientElement.append(
302  tr("%1 to '%2'")
303  .arg(amount, GUIUtil::HtmlEscape(rcp.label)));
304  recipientElement.append(QString(" (%1)").arg(address));
305  } else {
306  // just address
307  recipientElement.append(tr("%1 to %2").arg(amount, address));
308  }
309  }
310 #ifdef ENABLE_BIP70
311  // authenticated payment request
312  else if (!rcp.authenticatedMerchant.isEmpty()) {
313  recipientElement.append(
314  tr("%1 to '%2'").arg(amount, rcp.authenticatedMerchant));
315  } else {
316  // unauthenticated payment request
317  recipientElement.append(tr("%1 to %2").arg(amount, address));
318  }
319 #endif
320 
321  formatted.append(recipientElement);
322  }
323 
324  if (model->wallet().privateKeysDisabled()) {
325  question_string.append(tr("Do you want to draft this transaction?"));
326  } else {
327  question_string.append(tr("Are you sure you want to send?"));
328  }
329 
330  question_string.append("<br /><span style='font-size:10pt;'>");
331  if (model->wallet().privateKeysDisabled()) {
332  question_string.append(
333  tr("Please, review your transaction proposal. This will produce a "
334  "Partially Signed Bitcoin Transaction (PSBT) which you can save "
335  "or copy and then sign with e.g. an offline %1 wallet, or a "
336  "PSBT-compatible hardware wallet.")
337  .arg(PACKAGE_NAME));
338  } else {
339  question_string.append(tr("Please, review your transaction."));
340  }
341  question_string.append("</span>%1");
342 
343  if (txFee > Amount::zero()) {
344  // append fee string if a fee is required
345  question_string.append("<hr /><b>");
346  question_string.append(tr("Transaction fee"));
347  question_string.append("</b>");
348 
349  // append transaction size
350  question_string.append(
351  " (" +
352  QString::number(
353  (double)m_current_transaction->getTransactionSize() / 1000) +
354  " kB): ");
355 
356  // append transaction fee value
357  question_string.append(
358  "<span style='color:#aa0000; font-weight:bold;'>");
359  question_string.append(BitcoinUnits::formatHtmlWithUnit(
360  model->getOptionsModel()->getDisplayUnit(), txFee));
361  question_string.append("</span><br />");
362  }
363 
364  // add total amount in all subdivision units
365  question_string.append("<hr />");
366  Amount totalAmount =
367  m_current_transaction->getTotalTransactionAmount() + txFee;
368  QStringList alternativeUnits;
370  if (u != model->getOptionsModel()->getDisplayUnit()) {
371  alternativeUnits.append(
372  BitcoinUnits::formatHtmlWithUnit(u, totalAmount));
373  }
374  }
375  question_string.append(
376  QString("<b>%1</b>: <b>%2</b>")
377  .arg(tr("Total Amount"))
379  model->getOptionsModel()->getDisplayUnit(), totalAmount)));
380  question_string.append(
381  QString("<br /><span style='font-size:10pt; "
382  "font-weight:normal;'>(=%1)</span>")
383  .arg(alternativeUnits.join(" " + tr("or") + " ")));
384 
385  if (formatted.size() > 1) {
386  question_string = question_string.arg("");
387  informative_text =
388  tr("To review recipient list click \"Show Details...\"");
389  detailed_text = formatted.join("\n\n");
390  } else {
391  question_string = question_string.arg("<br /><br />" + formatted.at(0));
392  }
393 
394  return true;
395 }
396 
398  if (!model || !model->getOptionsModel()) {
399  return;
400  }
401 
402  QString question_string, informative_text, detailed_text;
403  if (!PrepareSendText(question_string, informative_text, detailed_text)) {
404  return;
405  }
406  assert(m_current_transaction);
407 
408  const QString confirmation = model->wallet().privateKeysDisabled()
409  ? tr("Confirm transaction proposal")
410  : tr("Confirm send coins");
411  const QString confirmButtonText = model->wallet().privateKeysDisabled()
412  ? tr("Create Unsigned")
413  : tr("Send");
414  SendConfirmationDialog confirmationDialog(
415  confirmation, question_string, informative_text, detailed_text,
416  SEND_CONFIRM_DELAY, confirmButtonText, this);
417  confirmationDialog.exec();
418  QMessageBox::StandardButton retval =
419  static_cast<QMessageBox::StandardButton>(confirmationDialog.result());
420 
421  if (retval != QMessageBox::Yes) {
422  fNewRecipientAllowed = true;
423  return;
424  }
425 
426  bool send_failure = false;
427  if (model->wallet().privateKeysDisabled()) {
428  CMutableTransaction mtx =
430  PartiallySignedTransaction psbtx(mtx);
431  bool complete = false;
432  const TransactionError err = model->wallet().fillPSBT(
433  SigHashType().withForkId(), false /* sign */,
434  true /* bip32derivs */, psbtx, complete);
435  assert(!complete);
436  assert(err == TransactionError::OK);
437  // Serialize the PSBT
439  ssTx << psbtx;
440  GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str());
441  QMessageBox msgBox;
442  msgBox.setText("Unsigned Transaction");
443  msgBox.setInformativeText(
444  "The PSBT has been copied to the clipboard. You can also save it.");
445  msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard);
446  msgBox.setDefaultButton(QMessageBox::Discard);
447  switch (msgBox.exec()) {
448  case QMessageBox::Save: {
449  QString selectedFilter;
450  QString fileNameSuggestion = "";
451  bool first = true;
452  for (const SendCoinsRecipient &rcp :
453  m_current_transaction->getRecipients()) {
454  if (!first) {
455  fileNameSuggestion.append(" - ");
456  }
457  QString labelOrAddress =
458  rcp.label.isEmpty() ? rcp.address : rcp.label;
459  QString amount = BitcoinUnits::formatWithUnit(
460  model->getOptionsModel()->getDisplayUnit(), rcp.amount);
461  fileNameSuggestion.append(labelOrAddress + "-" + amount);
462  first = false;
463  }
464  fileNameSuggestion.append(".psbt");
465  QString filename = GUIUtil::getSaveFileName(
466  this, tr("Save Transaction Data"), fileNameSuggestion,
467  tr("Partially Signed Transaction (Binary) (*.psbt)"),
468  &selectedFilter);
469  if (filename.isEmpty()) {
470  return;
471  }
472  std::ofstream out(filename.toLocal8Bit().data());
473  out << ssTx.str();
474  out.close();
475  Q_EMIT message(tr("PSBT saved"), "PSBT saved to disk",
477  break;
478  }
479  case QMessageBox::Discard:
480  break;
481  default:
482  assert(false);
483  }
484  } else {
485  // now send the prepared transaction
486  WalletModel::SendCoinsReturn sendStatus =
488  // process sendStatus and on error generate message shown to user
489  processSendCoinsReturn(sendStatus);
490 
491  if (sendStatus.status == WalletModel::OK) {
492  Q_EMIT coinsSent(m_current_transaction->getWtx()->GetId());
493  } else {
494  send_failure = true;
495  }
496  }
497  if (!send_failure) {
498  accept();
501  }
502  fNewRecipientAllowed = true;
503  m_current_transaction.reset();
504 }
505 
507  m_current_transaction.reset();
508 
509  // Clear coin control settings
511  ui->checkBoxCoinControlChange->setChecked(false);
512  ui->lineEditCoinControlChange->clear();
514 
515  // Remove entries until only one left
516  while (ui->entries->count()) {
517  ui->entries->takeAt(0)->widget()->deleteLater();
518  }
519  addEntry();
520 
522 }
523 
525  clear();
526 }
527 
529  clear();
530 }
531 
533  SendCoinsEntry *entry = new SendCoinsEntry(platformStyle, model, this);
534  ui->entries->addWidget(entry);
535  connect(entry, &SendCoinsEntry::removeEntry, this,
537  connect(entry, &SendCoinsEntry::useAvailableBalance, this,
539  connect(entry, &SendCoinsEntry::payAmountChanged, this,
541  connect(entry, &SendCoinsEntry::subtractFeeFromAmountChanged, this,
543 
544  // Focus the field, so that entry can start immediately
545  entry->clear();
546  entry->setFocus();
547  ui->scrollAreaWidgetContents->resize(
548  ui->scrollAreaWidgetContents->sizeHint());
549  qApp->processEvents();
550  QScrollBar *bar = ui->scrollArea->verticalScrollBar();
551  if (bar) {
552  bar->setSliderPosition(bar->maximum());
553  }
554 
556  return entry;
557 }
558 
560  setupTabChain(nullptr);
562 }
563 
565  entry->hide();
566 
567  // If the last entry is about to be removed add an empty one
568  if (ui->entries->count() == 1) {
569  addEntry();
570  }
571 
572  entry->deleteLater();
573 
575 }
576 
577 QWidget *SendCoinsDialog::setupTabChain(QWidget *prev) {
578  for (int i = 0; i < ui->entries->count(); ++i) {
579  SendCoinsEntry *entry =
580  qobject_cast<SendCoinsEntry *>(ui->entries->itemAt(i)->widget());
581  if (entry) {
582  prev = entry->setupTabChain(prev);
583  }
584  }
585  QWidget::setTabOrder(prev, ui->sendButton);
586  QWidget::setTabOrder(ui->sendButton, ui->clearButton);
587  QWidget::setTabOrder(ui->clearButton, ui->addButton);
588  return ui->addButton;
589 }
590 
591 void SendCoinsDialog::setAddress(const QString &address) {
592  SendCoinsEntry *entry = nullptr;
593  // Replace the first entry if it is still unused
594  if (ui->entries->count() == 1) {
595  SendCoinsEntry *first =
596  qobject_cast<SendCoinsEntry *>(ui->entries->itemAt(0)->widget());
597  if (first->isClear()) {
598  entry = first;
599  }
600  }
601  if (!entry) {
602  entry = addEntry();
603  }
604 
605  entry->setAddress(address);
606 }
607 
609  if (!fNewRecipientAllowed) {
610  return;
611  }
612 
613  SendCoinsEntry *entry = nullptr;
614  // Replace the first entry if it is still unused
615  if (ui->entries->count() == 1) {
616  SendCoinsEntry *first =
617  qobject_cast<SendCoinsEntry *>(ui->entries->itemAt(0)->widget());
618  if (first->isClear()) {
619  entry = first;
620  }
621  }
622  if (!entry) {
623  entry = addEntry();
624  }
625 
626  entry->setValue(rv);
628 }
629 
631  // Just paste the entry, all pre-checks are done in paymentserver.cpp.
632  pasteEntry(rv);
633  return true;
634 }
635 
637  if (model && model->getOptionsModel()) {
638  Amount balance = balances.balance;
639  if (model->wallet().privateKeysDisabled()) {
640  balance = balances.watch_only_balance;
641  ui->labelBalanceName->setText(tr("Watch-only balance:"));
642  }
643  ui->labelBalance->setText(BitcoinUnits::formatWithUnit(
645  }
646 }
647 
650  ui->customFee->setDisplayUnit(model->getOptionsModel()->getDisplayUnit());
652 }
653 
655  const WalletModel::SendCoinsReturn &sendCoinsReturn,
656  const QString &msgArg) {
657  QPair<QString, CClientUIInterface::MessageBoxFlags> msgParams;
658  // Default to a warning message, override if error message is needed
659  msgParams.second = CClientUIInterface::MSG_WARNING;
660 
661  // This comment is specific to SendCoinsDialog usage of
662  // WalletModel::SendCoinsReturn.
663  // All status values are used only in WalletModel::prepareTransaction()
664  switch (sendCoinsReturn.status) {
666  msgParams.first =
667  tr("The recipient address is not valid. Please recheck.");
668  break;
670  msgParams.first = tr("The amount to pay must be larger than 0.");
671  break;
673  msgParams.first = tr("The amount exceeds your balance.");
674  break;
676  msgParams.first = tr("The total exceeds your balance when the %1 "
677  "transaction fee is included.")
678  .arg(msgArg);
679  break;
681  msgParams.first = tr("Duplicate address found: addresses should "
682  "only be used once each.");
683  break;
685  msgParams.first = tr("Transaction creation failed!");
686  msgParams.second = CClientUIInterface::MSG_ERROR;
687  break;
689  msgParams.first =
690  tr("A fee higher than %1 is considered an absurdly high fee.")
694  break;
696  msgParams.first = tr("Payment request expired.");
697  msgParams.second = CClientUIInterface::MSG_ERROR;
698  break;
699  // included to prevent a compiler warning.
700  case WalletModel::OK:
701  default:
702  return;
703  }
704 
705  Q_EMIT message(tr("Send Coins"), msgParams.first, msgParams.second);
706 }
707 
709  ui->labelFeeMinimized->setVisible(fMinimize);
710  ui->buttonChooseFee->setVisible(fMinimize);
711  ui->buttonMinimizeFee->setVisible(!fMinimize);
712  ui->frameFeeSelection->setVisible(!fMinimize);
713  ui->horizontalLayoutSmartFee->setContentsMargins(0, (fMinimize ? 0 : 6), 0,
714  0);
715  fFeeMinimized = fMinimize;
716 }
717 
719  minimizeFeeSection(false);
720 }
721 
724  minimizeFeeSection(true);
725 }
726 
728  // Get CCoinControl instance if CoinControl is enabled or create a new one.
729  CCoinControl coin_control;
731  coin_control = *CoinControlDialog::coinControl();
732  }
733 
734  // Include watch-only for wallets without private key
735  coin_control.fAllowWatchOnly = model->wallet().privateKeysDisabled();
736 
737  // Calculate available amount to send.
738  Amount amount = model->wallet().getAvailableBalance(coin_control);
739  for (int i = 0; i < ui->entries->count(); ++i) {
740  SendCoinsEntry *e =
741  qobject_cast<SendCoinsEntry *>(ui->entries->itemAt(i)->widget());
742  if (e && !e->isHidden() && e != entry) {
743  amount -= e->getValue().amount;
744  }
745  }
746 
747  if (amount > Amount::zero()) {
749  entry->setAmount(amount);
750  } else {
751  entry->setAmount(Amount::zero());
752  }
753 }
754 
756  ui->labelSmartFee->setEnabled(ui->radioSmartFee->isChecked());
757  ui->labelSmartFee2->setEnabled(ui->radioSmartFee->isChecked());
758  ui->labelFeeEstimation->setEnabled(ui->radioSmartFee->isChecked());
759  ui->labelCustomFeeWarning->setEnabled(ui->radioCustomFee->isChecked());
760  ui->labelCustomPerKilobyte->setEnabled(ui->radioCustomFee->isChecked());
761  ui->customFee->setEnabled(ui->radioCustomFee->isChecked());
762 }
763 
765  if (!model || !model->getOptionsModel()) {
766  return;
767  }
768 
769  if (ui->radioSmartFee->isChecked()) {
770  ui->labelFeeMinimized->setText(ui->labelSmartFee->text());
771  } else {
772  ui->labelFeeMinimized->setText(
775  ui->customFee->value()) +
776  "/kB");
777  }
778 }
779 
781  if (ui->radioCustomFee->isChecked()) {
782  ctrl.m_feerate = CFeeRate(ui->customFee->value());
783  } else {
784  ctrl.m_feerate.reset();
785  }
786  // Include watch-only for wallets without private key
788 }
789 
791  if (!model || !model->getOptionsModel()) {
792  return;
793  }
794 
795  CCoinControl coin_control;
796  updateCoinControlState(coin_control);
797  // Explicitly use only fee estimation rate for smart fee labels
798  coin_control.m_feerate.reset();
799  CFeeRate feeRate(model->wallet().getMinimumFee(1000, coin_control));
800 
801  ui->labelSmartFee->setText(
803  feeRate.GetFeePerK()) +
804  "/kB");
805  // not enough data => minfee
806  if (feeRate <= CFeeRate(Amount::zero())) {
807  // (Smart fee not initialized yet. This usually takes a few blocks...)
808  ui->labelSmartFee2->show();
809  ui->labelFeeEstimation->setText("");
810  } else {
811  ui->labelSmartFee2->hide();
812  ui->labelFeeEstimation->setText(
813  tr("Estimated to begin confirmation by next block."));
814  }
815 
817 }
818 
819 // Coin Control: copy label "Quantity" to clipboard
821  GUIUtil::setClipboard(ui->labelCoinControlQuantity->text());
822 }
823 
824 // Coin Control: copy label "Amount" to clipboard
826  GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left(
827  ui->labelCoinControlAmount->text().indexOf(" ")));
828 }
829 
830 // Coin Control: copy label "Fee" to clipboard
833  ui->labelCoinControlFee->text()
834  .left(ui->labelCoinControlFee->text().indexOf(" "))
835  .replace(ASYMP_UTF8, ""));
836 }
837 
838 // Coin Control: copy label "After fee" to clipboard
841  ui->labelCoinControlAfterFee->text()
842  .left(ui->labelCoinControlAfterFee->text().indexOf(" "))
843  .replace(ASYMP_UTF8, ""));
844 }
845 
846 // Coin Control: copy label "Bytes" to clipboard
849  ui->labelCoinControlBytes->text().replace(ASYMP_UTF8, ""));
850 }
851 
852 // Coin Control: copy label "Dust" to clipboard
854  GUIUtil::setClipboard(ui->labelCoinControlLowOutput->text());
855 }
856 
857 // Coin Control: copy label "Change" to clipboard
860  ui->labelCoinControlChange->text()
861  .left(ui->labelCoinControlChange->text().indexOf(" "))
862  .replace(ASYMP_UTF8, ""));
863 }
864 
865 // Coin Control: settings menu - coin control enabled/disabled by user
867  ui->frameCoinControl->setVisible(checked);
868 
869  // coin control features disabled
870  if (!checked && model) {
872  }
873 
875 }
876 
877 // Coin Control: button inputs -> show actual coin control dialog
880  dlg.setModel(model);
881  dlg.exec();
883 }
884 
885 // Coin Control: checkbox custom change address
887  if (state == Qt::Unchecked) {
889  ui->labelCoinControlChangeLabel->clear();
890  } else {
891  // use this to re-validate an already entered address
892  coinControlChangeEdited(ui->lineEditCoinControlChange->text());
893  }
894 
895  ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked));
896 }
897 
898 // Coin Control: custom change address changed
899 void SendCoinsDialog::coinControlChangeEdited(const QString &text) {
900  if (model && model->getAddressTableModel()) {
901  // Default to no change address until verified
903  ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:red;}");
904 
905  const CTxDestination dest =
906  DecodeDestination(text.toStdString(), model->getChainParams());
907 
908  if (text.isEmpty()) {
909  // Nothing entered
910  ui->labelCoinControlChangeLabel->setText("");
911  } else if (!IsValidDestination(dest)) {
912  // Invalid address
913  ui->labelCoinControlChangeLabel->setText(
914  tr("Warning: Invalid Bitcoin address"));
915  } else {
916  // Valid address
917  if (!model->wallet().isSpendable(dest)) {
918  ui->labelCoinControlChangeLabel->setText(
919  tr("Warning: Unknown change address"));
920 
921  // confirmation dialog
922  QMessageBox::StandardButton btnRetVal = QMessageBox::question(
923  this, tr("Confirm custom change address"),
924  tr("The address you selected for change is not part of "
925  "this wallet. Any or all funds in your wallet may be "
926  "sent to this address. Are you sure?"),
927  QMessageBox::Yes | QMessageBox::Cancel,
928  QMessageBox::Cancel);
929 
930  if (btnRetVal == QMessageBox::Yes) {
932  } else {
933  ui->lineEditCoinControlChange->setText("");
934  ui->labelCoinControlChangeLabel->setStyleSheet(
935  "QLabel{color:black;}");
936  ui->labelCoinControlChangeLabel->setText("");
937  }
938  } else {
939  // Known change address
940  ui->labelCoinControlChangeLabel->setStyleSheet(
941  "QLabel{color:black;}");
942 
943  // Query label
944  QString associatedLabel =
946  if (!associatedLabel.isEmpty()) {
947  ui->labelCoinControlChangeLabel->setText(associatedLabel);
948  } else {
949  ui->labelCoinControlChangeLabel->setText(tr("(no label)"));
950  }
951 
953  }
954  }
955  }
956 }
957 
958 // Coin Control: update labels
960  if (!model || !model->getOptionsModel()) {
961  return;
962  }
963 
965 
966  // set pay amounts
969  for (int i = 0; i < ui->entries->count(); ++i) {
970  SendCoinsEntry *entry =
971  qobject_cast<SendCoinsEntry *>(ui->entries->itemAt(i)->widget());
972  if (entry && !entry->isHidden()) {
973  SendCoinsRecipient rcp = entry->getValue();
975  if (rcp.fSubtractFeeFromAmount) {
977  }
978  }
979  }
980 
981  if (CoinControlDialog::coinControl()->HasSelected()) {
982  // actual coin control calculation
984 
985  // show coin control stats
986  ui->labelCoinControlAutomaticallySelected->hide();
987  ui->widgetCoinControl->show();
988  } else {
989  // hide coin control stats
990  ui->labelCoinControlAutomaticallySelected->show();
991  ui->widgetCoinControl->hide();
992  ui->labelCoinControlInsuffFunds->hide();
993  }
994 }
995 
997  const QString &title, const QString &text, const QString &informative_text,
998  const QString &detailed_text, int _secDelay,
999  const QString &_confirmButtonText, QWidget *parent)
1000  : QMessageBox(parent), secDelay(_secDelay),
1001  confirmButtonText(_confirmButtonText) {
1002  setIcon(QMessageBox::Question);
1003  // On macOS, the window title is ignored (as required by the macOS
1004  // Guidelines).
1005  setWindowTitle(title);
1006  setText(text);
1007  setInformativeText(informative_text);
1008  setDetailedText(detailed_text);
1009  setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel);
1010  setDefaultButton(QMessageBox::Cancel);
1011  yesButton = button(QMessageBox::Yes);
1012  updateYesButton();
1013  connect(&countDownTimer, &QTimer::timeout, this,
1015 }
1016 
1018  updateYesButton();
1019  countDownTimer.start(1000);
1020  return QMessageBox::exec();
1021 }
1022 
1024  secDelay--;
1025  updateYesButton();
1026 
1027  if (secDelay <= 0) {
1028  countDownTimer.stop();
1029  }
1030 }
1031 
1033  if (secDelay > 0) {
1034  yesButton->setEnabled(false);
1035  yesButton->setText(confirmButtonText + " (" +
1036  QString::number(secDelay) + ")");
1037  } else {
1038  yesButton->setEnabled(true);
1039  yesButton->setText(confirmButtonText);
1040  }
1041 }
virtual bool privateKeysDisabled()=0
virtual Amount getRequiredFee(unsigned int tx_bytes)=0
Get required fee.
static QString formatWithUnit(int unit, const Amount amount, bool plussign=false, SeparatorStyle separators=separatorStandard)
Format as string (with unit)
void removeEntry(SendCoinsEntry *entry)
Unit
Bitcoin units (Bitcoin Cash unit work the same as Bitoin).
Definition: bitcoinunits.h:43
interfaces::Wallet & wallet() const
Definition: walletmodel.h:150
static constexpr Amount zero()
Definition: amount.h:35
void setValue(const SendCoinsRecipient &value)
void payAmountChanged()
bool fAllowWatchOnly
Includes watch only addresses which are solvable.
Definition: coincontrol.h:30
SendConfirmationDialog(const QString &title, const QString &text, const QString &informative_text="", const QString &detailed_text="", int secDelay=SEND_CONFIRM_DELAY, const QString &confirmText="Send", QWidget *parent=nullptr)
static QList< Amount > payAmounts
void updateFeeMinimizedLabel()
void setFocus()
void on_buttonChooseFee_clicked()
SendCoinsRecipient getValue()
void reject() override
secp256k1_context * ctx
UnlockContext requestUnlock()
void coinControlClipboardQuantity()
std::string str() const
Definition: streams.h:270
void coinControlClipboardAfterFee()
void setAddress(const QString &address)
bool IsValidDestination(const CTxDestination &dest)
Check whether a CTxDestination is a CNoDestination.
Definition: standard.cpp:269
#define SEND_CONFIRM_DELAY
Definition: amount.h:17
std::string EncodeBase64(const uint8_t *pch, size_t len)
SendCoinsReturn sendCoins(WalletModelTransaction &transaction)
void coinControlFeaturesChanged(bool)
static constexpr Amount SATOSHI
Definition: amount.h:151
void updateCoinControlState(CCoinControl &ctrl)
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:238
bool handlePaymentRequest(const SendCoinsRecipient &recipient)
A version of CTransaction with the PSBT format.
Definition: psbt.h:335
virtual Amount getAvailableBalance(const CCoinControl &coin_control)=0
Get available balance.
Double ended buffer combining vector and stream-like interfaces.
Definition: streams.h:196
void numBlocksChanged(int count, const QDateTime &blockDate, double nVerificationProgress, bool header, SynchronizationState sync_state)
fs::ofstream ofstream
Definition: fs.h:99
AddressTableModel * getAddressTableModel()
#define ASYMP_UTF8
virtual bool isSpendable(const CTxDestination &dest)=0
Return whether wallet has private key.
A single entry in the dialog for sending bitcoins.
Coin Control Features.
Definition: coincontrol.h:19
int getDisplayUnit() const
Definition: optionsmodel.h:96
void coinControlFeatureChanged(bool)
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
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:117
const CChainParams & getChainParams() const
void setBalance(const interfaces::WalletBalances &balances)
void setAddress(const QString &address)
void coinControlClipboardChange()
Collection of wallet balances.
Definition: wallet.h:319
void useAvailableBalance(SendCoinsEntry *entry)
constexpr Amount DEFAULT_PAY_TX_FEE
-paytxfee default
Definition: wallet.h:78
void setClientModel(ClientModel *clientModel)
void setClipboard(const QString &str)
Definition: guiutil.cpp:732
ClientModel * clientModel
void SetNull()
Definition: coincontrol.cpp:9
virtual Amount getMinimumFee(unsigned int tx_bytes, const CCoinControl &coin_control)=0
Get minimum fee.
CTxDestination destChange
Definition: coincontrol.h:21
SendCoinsDialog(const PlatformStyle *platformStyle, WalletModel *model, QWidget *parent=nullptr)
QString labelForAddress(const QString &address) const
Look up label for address in address book, if not found return empty string.
SendCoinsReturn prepareTransaction(WalletModelTransaction &transaction, const CCoinControl &coinControl)
WalletModel * model
Dialog for sending bitcoins.
void coinControlChangeEdited(const QString &)
static void updateLabels(WalletModel *, QDialog *)
CTxDestination DecodeDestination(const std::string &addr, const CChainParams &params)
Definition: key_io.cpp:177
QString getWalletName() const
interfaces::Node & node() const
Definition: walletmodel.h:149
void removeEntry(SendCoinsEntry *entry)
void displayUnitChanged(int unit)
Model for Bitcoin network client.
Definition: clientmodel.h:34
void coinControlUpdateLabels()
void UnSelectAll()
Definition: coincontrol.h:60
void setModel(WalletModel *model)
void accept() override
bool getCoinControlFeatures() const
Definition: optionsmodel.h:99
static Amount balance
void checkSubtractFeeFromAmount()
bool isClear()
Return whether the entry is still empty and unedited.
void minimizeFeeSection(bool fMinimize)
void coinControlClipboardLowOutput()
void updateFeeSectionControls()
static QList< Unit > availableUnits()
Get list of units, for drop-down box.
void subtractFeeFromAmountChanged()
static QString formatHtmlWithUnit(int unit, const Amount amount, bool plussign=false, SeparatorStyle separators=separatorStandard)
Format as HTML string (with unit)
void setModel(WalletModel *model)
static bool fSubtractFeeFromAmount
std::optional< CFeeRate > m_feerate
Override the wallet&#39;s m_pay_tx_fee if set.
Definition: coincontrol.h:34
bool PrepareSendText(QString &question_string, QString &informative_text, QString &detailed_text)
virtual TransactionError fillPSBT(SigHashType sighash_type, bool sign, bool bip32derivs, PartiallySignedTransaction &psbtx, bool &complete) const =0
Fill PSBT.
Interface to Bitcoin wallet from Qt view code.
Definition: walletmodel.h:47
void setAmount(const Amount amount)
static const int PROTOCOL_VERSION
network protocol versioning
Definition: version.h:11
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:273
A mutable version of CTransaction.
Definition: transaction.h:297
void setModel(WalletModel *model)
void processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg=QString())
TransactionError
Definition: error.h:22
Fee rate in satoshis per kilobyte: Amount / kB.
Definition: feerate.h:21
virtual Amount getDefaultMaxTxFee()=0
Get max tx fee.
void coinControlClipboardBytes()
void useAvailableBalance(SendCoinsEntry *entry)
const PlatformStyle * platformStyle
void coinControlClipboardAmount()
bool isMultiwallet()
void on_buttonMinimizeFee_clicked()
virtual WalletBalances getBalances()=0
Get balances.
void pasteEntry(const SendCoinsRecipient &rv)
QAbstractButton * yesButton
void message(const QString &title, const QString &message, unsigned int style)
void coinsSent(const uint256 &txid)
void balanceChanged(const interfaces::WalletBalances &balances)
bool getImagesOnButtons() const
Definition: platformstyle.h:20
boost::variant< CNoDestination, PKHash, ScriptHash > CTxDestination
A txout script template with a specific destination.
Definition: standard.h:87
void coinControlButtonClicked()
void coinControlClipboardFee()
OptionsModel * getOptionsModel()
Signature hash type wrapper class.
Definition: sighashtype.h:37
Predefined combinations for certain default usage cases.
Definition: ui_interface.h:71
void coinControlChangeChecked(int)
static CCoinControl * coinControl()