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