Bitcoin Core  21.99.0
P2P Digital Currency
sendcoinsdialog.cpp
Go to the documentation of this file.
1 // Copyright (c) 2011-2020 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>
10 #include <qt/forms/ui_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/ui_interface.h>
25 #include <policy/fees.h>
26 #include <txmempool.h>
27 #include <wallet/coincontrol.h>
28 #include <wallet/fees.h>
29 #include <wallet/wallet.h>
30 
31 #include <validation.h>
32 
33 #include <QFontMetrics>
34 #include <QScrollBar>
35 #include <QSettings>
36 #include <QTextDocument>
37 
38 static constexpr std::array confTargets{2, 4, 6, 12, 24, 48, 144, 504, 1008};
39 int getConfTargetForIndex(int index) {
40  if (index+1 > static_cast<int>(confTargets.size())) {
41  return confTargets.back();
42  }
43  if (index < 0) {
44  return confTargets[0];
45  }
46  return confTargets[index];
47 }
48 int getIndexForConfTarget(int target) {
49  for (unsigned int i = 0; i < confTargets.size(); i++) {
50  if (confTargets[i] >= target) {
51  return i;
52  }
53  }
54  return confTargets.size() - 1;
55 }
56 
57 SendCoinsDialog::SendCoinsDialog(const PlatformStyle *_platformStyle, QWidget *parent) :
58  QDialog(parent),
59  ui(new Ui::SendCoinsDialog),
60  clientModel(nullptr),
61  model(nullptr),
62  m_coin_control(new CCoinControl),
63  fNewRecipientAllowed(true),
64  fFeeMinimized(true),
65  platformStyle(_platformStyle)
66 {
67  ui->setupUi(this);
68 
69  if (!_platformStyle->getImagesOnButtons()) {
70  ui->addButton->setIcon(QIcon());
71  ui->clearButton->setIcon(QIcon());
72  ui->sendButton->setIcon(QIcon());
73  } else {
74  ui->addButton->setIcon(_platformStyle->SingleColorIcon(":/icons/add"));
75  ui->clearButton->setIcon(_platformStyle->SingleColorIcon(":/icons/remove"));
76  ui->sendButton->setIcon(_platformStyle->SingleColorIcon(":/icons/send"));
77  }
78 
79  GUIUtil::setupAddressWidget(ui->lineEditCoinControlChange, this);
80 
81  addEntry();
82 
83  connect(ui->addButton, &QPushButton::clicked, this, &SendCoinsDialog::addEntry);
84  connect(ui->clearButton, &QPushButton::clicked, this, &SendCoinsDialog::clear);
85 
86  // Coin Control
87  connect(ui->pushButtonCoinControl, &QPushButton::clicked, this, &SendCoinsDialog::coinControlButtonClicked);
88  connect(ui->checkBoxCoinControlChange, &QCheckBox::stateChanged, this, &SendCoinsDialog::coinControlChangeChecked);
89  connect(ui->lineEditCoinControlChange, &QValidatedLineEdit::textEdited, this, &SendCoinsDialog::coinControlChangeEdited);
90 
91  // Coin Control: clipboard actions
92  QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this);
93  QAction *clipboardAmountAction = new QAction(tr("Copy amount"), this);
94  QAction *clipboardFeeAction = new QAction(tr("Copy fee"), this);
95  QAction *clipboardAfterFeeAction = new QAction(tr("Copy after fee"), this);
96  QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this);
97  QAction *clipboardLowOutputAction = new QAction(tr("Copy dust"), this);
98  QAction *clipboardChangeAction = new QAction(tr("Copy change"), this);
99  connect(clipboardQuantityAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardQuantity);
100  connect(clipboardAmountAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardAmount);
101  connect(clipboardFeeAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardFee);
102  connect(clipboardAfterFeeAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardAfterFee);
103  connect(clipboardBytesAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardBytes);
104  connect(clipboardLowOutputAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardLowOutput);
105  connect(clipboardChangeAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardChange);
106  ui->labelCoinControlQuantity->addAction(clipboardQuantityAction);
107  ui->labelCoinControlAmount->addAction(clipboardAmountAction);
108  ui->labelCoinControlFee->addAction(clipboardFeeAction);
109  ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction);
110  ui->labelCoinControlBytes->addAction(clipboardBytesAction);
111  ui->labelCoinControlLowOutput->addAction(clipboardLowOutputAction);
112  ui->labelCoinControlChange->addAction(clipboardChangeAction);
113 
114  // init transaction fee section
115  QSettings settings;
116  if (!settings.contains("fFeeSectionMinimized"))
117  settings.setValue("fFeeSectionMinimized", true);
118  if (!settings.contains("nFeeRadio") && settings.contains("nTransactionFee") && settings.value("nTransactionFee").toLongLong() > 0) // compatibility
119  settings.setValue("nFeeRadio", 1); // custom
120  if (!settings.contains("nFeeRadio"))
121  settings.setValue("nFeeRadio", 0); // recommended
122  if (!settings.contains("nSmartFeeSliderPosition"))
123  settings.setValue("nSmartFeeSliderPosition", 0);
124  if (!settings.contains("nTransactionFee"))
125  settings.setValue("nTransactionFee", (qint64)DEFAULT_PAY_TX_FEE);
126  ui->groupFee->setId(ui->radioSmartFee, 0);
127  ui->groupFee->setId(ui->radioCustomFee, 1);
128  ui->groupFee->button((int)std::max(0, std::min(1, settings.value("nFeeRadio").toInt())))->setChecked(true);
129  ui->customFee->SetAllowEmpty(false);
130  ui->customFee->setValue(settings.value("nTransactionFee").toLongLong());
131  minimizeFeeSection(settings.value("fFeeSectionMinimized").toBool());
132 }
133 
135 {
136  this->clientModel = _clientModel;
137 
138  if (_clientModel) {
140  }
141 }
142 
144 {
145  this->model = _model;
146 
147  if(_model && _model->getOptionsModel())
148  {
149  for(int i = 0; i < ui->entries->count(); ++i)
150  {
151  SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
152  if(entry)
153  {
154  entry->setModel(_model);
155  }
156  }
157 
158  interfaces::WalletBalances balances = _model->wallet().getBalances();
159  setBalance(balances);
163 
164  // Coin Control
167  ui->frameCoinControl->setVisible(_model->getOptionsModel()->getCoinControlFeatures());
169 
170  // fee section
171  for (const int n : confTargets) {
172  ui->confTargetSelector->addItem(tr("%1 (%2 blocks)").arg(GUIUtil::formatNiceTimeOffset(n*Params().GetConsensus().nPowTargetSpacing)).arg(n));
173  }
174  connect(ui->confTargetSelector, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &SendCoinsDialog::updateSmartFeeLabel);
175  connect(ui->confTargetSelector, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &SendCoinsDialog::coinControlUpdateLabels);
176 
177 #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
178  connect(ui->groupFee, &QButtonGroup::idClicked, this, &SendCoinsDialog::updateFeeSectionControls);
179  connect(ui->groupFee, &QButtonGroup::idClicked, this, &SendCoinsDialog::coinControlUpdateLabels);
180 #else
181  connect(ui->groupFee, static_cast<void (QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked), this, &SendCoinsDialog::updateFeeSectionControls);
182  connect(ui->groupFee, static_cast<void (QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked), this, &SendCoinsDialog::coinControlUpdateLabels);
183 #endif
184 
186  connect(ui->optInRBF, &QCheckBox::stateChanged, this, &SendCoinsDialog::updateSmartFeeLabel);
187  connect(ui->optInRBF, &QCheckBox::stateChanged, this, &SendCoinsDialog::coinControlUpdateLabels);
188  CAmount requiredFee = model->wallet().getRequiredFee(1000);
189  ui->customFee->SetMinValue(requiredFee);
190  if (ui->customFee->value() < requiredFee) {
191  ui->customFee->setValue(requiredFee);
192  }
193  ui->customFee->setSingleStep(requiredFee);
196 
197  // set default rbf checkbox state
198  ui->optInRBF->setCheckState(Qt::Checked);
199 
200  if (model->wallet().privateKeysDisabled()) {
201  ui->sendButton->setText(tr("Cr&eate Unsigned"));
202  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));
203  }
204 
205  // set the smartfee-sliders default value (wallets default conf.target or last stored value)
206  QSettings settings;
207  if (settings.value("nSmartFeeSliderPosition").toInt() != 0) {
208  // migrate nSmartFeeSliderPosition to nConfTarget
209  // nConfTarget is available since 0.15 (replaced nSmartFeeSliderPosition)
210  int nConfirmTarget = 25 - settings.value("nSmartFeeSliderPosition").toInt(); // 25 == old slider range
211  settings.setValue("nConfTarget", nConfirmTarget);
212  settings.remove("nSmartFeeSliderPosition");
213  }
214  if (settings.value("nConfTarget").toInt() == 0)
215  ui->confTargetSelector->setCurrentIndex(getIndexForConfTarget(model->wallet().getConfirmTarget()));
216  else
217  ui->confTargetSelector->setCurrentIndex(getIndexForConfTarget(settings.value("nConfTarget").toInt()));
218  }
219 }
220 
222 {
223  QSettings settings;
224  settings.setValue("fFeeSectionMinimized", fFeeMinimized);
225  settings.setValue("nFeeRadio", ui->groupFee->checkedId());
226  settings.setValue("nConfTarget", getConfTargetForIndex(ui->confTargetSelector->currentIndex()));
227  settings.setValue("nTransactionFee", (qint64)ui->customFee->value());
228 
229  delete ui;
230 }
231 
232 bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informative_text, QString& detailed_text)
233 {
234  QList<SendCoinsRecipient> recipients;
235  bool valid = true;
236 
237  for(int i = 0; i < ui->entries->count(); ++i)
238  {
239  SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
240  if(entry)
241  {
242  if(entry->validate(model->node()))
243  {
244  recipients.append(entry->getValue());
245  }
246  else if (valid)
247  {
248  ui->scrollArea->ensureWidgetVisible(entry);
249  valid = false;
250  }
251  }
252  }
253 
254  if(!valid || recipients.isEmpty())
255  {
256  return false;
257  }
258 
259  fNewRecipientAllowed = false;
261  if(!ctx.isValid())
262  {
263  // Unlock wallet was cancelled
264  fNewRecipientAllowed = true;
265  return false;
266  }
267 
268  // prepare transaction for getting txFee earlier
269  m_current_transaction = MakeUnique<WalletModelTransaction>(recipients);
270  WalletModel::SendCoinsReturn prepareStatus;
271 
273 
275 
276  // process prepareStatus and on error generate message shown to user
277  processSendCoinsReturn(prepareStatus,
279 
280  if(prepareStatus.status != WalletModel::OK) {
281  fNewRecipientAllowed = true;
282  return false;
283  }
284 
285  CAmount txFee = m_current_transaction->getTransactionFee();
286  QStringList formatted;
287  for (const SendCoinsRecipient &rcp : m_current_transaction->getRecipients())
288  {
289  // generate amount string with wallet name in case of multiwallet
290  QString amount = BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
291  if (model->isMultiwallet()) {
292  amount.append(tr(" from wallet '%1'").arg(GUIUtil::HtmlEscape(model->getWalletName())));
293  }
294 
295  // generate address string
296  QString address = rcp.address;
297 
298  QString recipientElement;
299 
300  {
301  if(rcp.label.length() > 0) // label with address
302  {
303  recipientElement.append(tr("%1 to '%2'").arg(amount, GUIUtil::HtmlEscape(rcp.label)));
304  recipientElement.append(QString(" (%1)").arg(address));
305  }
306  else // just address
307  {
308  recipientElement.append(tr("%1 to %2").arg(amount, address));
309  }
310  }
311  formatted.append(recipientElement);
312  }
313 
314  if (model->wallet().privateKeysDisabled()) {
315  question_string.append(tr("Do you want to draft this transaction?"));
316  } else {
317  question_string.append(tr("Are you sure you want to send?"));
318  }
319 
320  question_string.append("<br /><span style='font-size:10pt;'>");
321  if (model->wallet().privateKeysDisabled()) {
322  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));
323  } else {
324  question_string.append(tr("Please, review your transaction."));
325  }
326  question_string.append("</span>%1");
327 
328  if(txFee > 0)
329  {
330  // append fee string if a fee is required
331  question_string.append("<hr /><b>");
332  question_string.append(tr("Transaction fee"));
333  question_string.append("</b>");
334 
335  // append transaction size
336  question_string.append(" (" + QString::number((double)m_current_transaction->getTransactionSize() / 1000) + " kB): ");
337 
338  // append transaction fee value
339  question_string.append("<span style='color:#aa0000; font-weight:bold;'>");
340  question_string.append(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), txFee));
341  question_string.append("</span><br />");
342 
343  // append RBF message according to transaction's signalling
344  question_string.append("<span style='font-size:10pt; font-weight:normal;'>");
345  if (ui->optInRBF->isChecked()) {
346  question_string.append(tr("You can increase the fee later (signals Replace-By-Fee, BIP-125)."));
347  } else {
348  question_string.append(tr("Not signalling Replace-By-Fee, BIP-125."));
349  }
350  question_string.append("</span>");
351  }
352 
353  // add total amount in all subdivision units
354  question_string.append("<hr />");
355  CAmount totalAmount = m_current_transaction->getTotalTransactionAmount() + txFee;
356  QStringList alternativeUnits;
358  {
359  if(u != model->getOptionsModel()->getDisplayUnit())
360  alternativeUnits.append(BitcoinUnits::formatHtmlWithUnit(u, totalAmount));
361  }
362  question_string.append(QString("<b>%1</b>: <b>%2</b>").arg(tr("Total Amount"))
364  question_string.append(QString("<br /><span style='font-size:10pt; font-weight:normal;'>(=%1)</span>")
365  .arg(alternativeUnits.join(" " + tr("or") + " ")));
366 
367  if (formatted.size() > 1) {
368  question_string = question_string.arg("");
369  informative_text = tr("To review recipient list click \"Show Details...\"");
370  detailed_text = formatted.join("\n\n");
371  } else {
372  question_string = question_string.arg("<br /><br />" + formatted.at(0));
373  }
374 
375  return true;
376 }
377 
379 {
380  if(!model || !model->getOptionsModel())
381  return;
382 
383  QString question_string, informative_text, detailed_text;
384  if (!PrepareSendText(question_string, informative_text, detailed_text)) return;
386 
387  const QString confirmation = model->wallet().privateKeysDisabled() ? tr("Confirm transaction proposal") : tr("Confirm send coins");
388  const QString confirmButtonText = model->wallet().privateKeysDisabled() ? tr("Create Unsigned") : tr("Send");
389  SendConfirmationDialog confirmationDialog(confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, confirmButtonText, this);
390  confirmationDialog.exec();
391  QMessageBox::StandardButton retval = static_cast<QMessageBox::StandardButton>(confirmationDialog.result());
392 
393  if(retval != QMessageBox::Yes)
394  {
395  fNewRecipientAllowed = true;
396  return;
397  }
398 
399  bool send_failure = false;
400  if (model->wallet().privateKeysDisabled()) {
402  PartiallySignedTransaction psbtx(mtx);
403  bool complete = false;
404  const TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, psbtx, complete, nullptr);
405  assert(!complete);
407  // Serialize the PSBT
409  ssTx << psbtx;
410  GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str());
411  QMessageBox msgBox;
412  msgBox.setText("Unsigned Transaction");
413  msgBox.setInformativeText("The PSBT has been copied to the clipboard. You can also save it.");
414  msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard);
415  msgBox.setDefaultButton(QMessageBox::Discard);
416  switch (msgBox.exec()) {
417  case QMessageBox::Save: {
418  QString selectedFilter;
419  QString fileNameSuggestion = "";
420  bool first = true;
421  for (const SendCoinsRecipient &rcp : m_current_transaction->getRecipients()) {
422  if (!first) {
423  fileNameSuggestion.append(" - ");
424  }
425  QString labelOrAddress = rcp.label.isEmpty() ? rcp.address : rcp.label;
426  QString amount = BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
427  fileNameSuggestion.append(labelOrAddress + "-" + amount);
428  first = false;
429  }
430  fileNameSuggestion.append(".psbt");
431  QString filename = GUIUtil::getSaveFileName(this,
432  tr("Save Transaction Data"), fileNameSuggestion,
433  tr("Partially Signed Transaction (Binary) (*.psbt)"), &selectedFilter);
434  if (filename.isEmpty()) {
435  return;
436  }
437  std::ofstream out(filename.toLocal8Bit().data());
438  out << ssTx.str();
439  out.close();
440  Q_EMIT message(tr("PSBT saved"), "PSBT saved to disk", CClientUIInterface::MSG_INFORMATION);
441  break;
442  }
443  case QMessageBox::Discard:
444  break;
445  default:
446  assert(false);
447  }
448  } else {
449  // now send the prepared transaction
451  // process sendStatus and on error generate message shown to user
452  processSendCoinsReturn(sendStatus);
453 
454  if (sendStatus.status == WalletModel::OK) {
455  Q_EMIT coinsSent(m_current_transaction->getWtx()->GetHash());
456  } else {
457  send_failure = true;
458  }
459  }
460  if (!send_failure) {
461  accept();
462  m_coin_control->UnSelectAll();
464  }
465  fNewRecipientAllowed = true;
466  m_current_transaction.reset();
467 }
468 
470 {
471  m_current_transaction.reset();
472 
473  // Clear coin control settings
474  m_coin_control->UnSelectAll();
475  ui->checkBoxCoinControlChange->setChecked(false);
476  ui->lineEditCoinControlChange->clear();
478 
479  // Remove entries until only one left
480  while(ui->entries->count())
481  {
482  ui->entries->takeAt(0)->widget()->deleteLater();
483  }
484  addEntry();
485 
487 }
488 
490 {
491  clear();
492 }
493 
495 {
496  clear();
497 }
498 
500 {
501  SendCoinsEntry *entry = new SendCoinsEntry(platformStyle, this);
502  entry->setModel(model);
503  ui->entries->addWidget(entry);
508 
509  // Focus the field, so that entry can start immediately
510  entry->clear();
511  entry->setFocus();
512  ui->scrollAreaWidgetContents->resize(ui->scrollAreaWidgetContents->sizeHint());
513  qApp->processEvents();
514  QScrollBar* bar = ui->scrollArea->verticalScrollBar();
515  if(bar)
516  bar->setSliderPosition(bar->maximum());
517 
519  return entry;
520 }
521 
523 {
524  setupTabChain(nullptr);
526 }
527 
529 {
530  entry->hide();
531 
532  // If the last entry is about to be removed add an empty one
533  if (ui->entries->count() == 1)
534  addEntry();
535 
536  entry->deleteLater();
537 
539 }
540 
541 QWidget *SendCoinsDialog::setupTabChain(QWidget *prev)
542 {
543  for(int i = 0; i < ui->entries->count(); ++i)
544  {
545  SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
546  if(entry)
547  {
548  prev = entry->setupTabChain(prev);
549  }
550  }
551  QWidget::setTabOrder(prev, ui->sendButton);
552  QWidget::setTabOrder(ui->sendButton, ui->clearButton);
553  QWidget::setTabOrder(ui->clearButton, ui->addButton);
554  return ui->addButton;
555 }
556 
557 void SendCoinsDialog::setAddress(const QString &address)
558 {
559  SendCoinsEntry *entry = nullptr;
560  // Replace the first entry if it is still unused
561  if(ui->entries->count() == 1)
562  {
563  SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
564  if(first->isClear())
565  {
566  entry = first;
567  }
568  }
569  if(!entry)
570  {
571  entry = addEntry();
572  }
573 
574  entry->setAddress(address);
575 }
576 
578 {
580  return;
581 
582  SendCoinsEntry *entry = nullptr;
583  // Replace the first entry if it is still unused
584  if(ui->entries->count() == 1)
585  {
586  SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
587  if(first->isClear())
588  {
589  entry = first;
590  }
591  }
592  if(!entry)
593  {
594  entry = addEntry();
595  }
596 
597  entry->setValue(rv);
599 }
600 
602 {
603  // Just paste the entry, all pre-checks
604  // are done in paymentserver.cpp.
605  pasteEntry(rv);
606  return true;
607 }
608 
610 {
611  if(model && model->getOptionsModel())
612  {
613  CAmount balance = balances.balance;
614  if (model->wallet().privateKeysDisabled()) {
615  balance = balances.watch_only_balance;
616  ui->labelBalanceName->setText(tr("Watch-only balance:"));
617  }
619  }
620 }
621 
623 {
625  ui->customFee->setDisplayUnit(model->getOptionsModel()->getDisplayUnit());
627 }
628 
629 void SendCoinsDialog::processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg)
630 {
631  QPair<QString, CClientUIInterface::MessageBoxFlags> msgParams;
632  // Default to a warning message, override if error message is needed
633  msgParams.second = CClientUIInterface::MSG_WARNING;
634 
635  // This comment is specific to SendCoinsDialog usage of WalletModel::SendCoinsReturn.
636  // All status values are used only in WalletModel::prepareTransaction()
637  switch(sendCoinsReturn.status)
638  {
640  msgParams.first = tr("The recipient address is not valid. Please recheck.");
641  break;
643  msgParams.first = tr("The amount to pay must be larger than 0.");
644  break;
646  msgParams.first = tr("The amount exceeds your balance.");
647  break;
649  msgParams.first = tr("The total exceeds your balance when the %1 transaction fee is included.").arg(msgArg);
650  break;
652  msgParams.first = tr("Duplicate address found: addresses should only be used once each.");
653  break;
655  msgParams.first = tr("Transaction creation failed!");
656  msgParams.second = CClientUIInterface::MSG_ERROR;
657  break;
659  msgParams.first = tr("A fee higher than %1 is considered an absurdly high fee.").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), model->wallet().getDefaultMaxTxFee()));
660  break;
662  msgParams.first = tr("Payment request expired.");
663  msgParams.second = CClientUIInterface::MSG_ERROR;
664  break;
665  // included to prevent a compiler warning.
666  case WalletModel::OK:
667  default:
668  return;
669  }
670 
671  Q_EMIT message(tr("Send Coins"), msgParams.first, msgParams.second);
672 }
673 
675 {
676  ui->labelFeeMinimized->setVisible(fMinimize);
677  ui->buttonChooseFee ->setVisible(fMinimize);
678  ui->buttonMinimizeFee->setVisible(!fMinimize);
679  ui->frameFeeSelection->setVisible(!fMinimize);
680  ui->horizontalLayoutSmartFee->setContentsMargins(0, (fMinimize ? 0 : 6), 0, 0);
681  fFeeMinimized = fMinimize;
682 }
683 
685 {
686  minimizeFeeSection(false);
687 }
688 
690 {
692  minimizeFeeSection(true);
693 }
694 
696 {
697  // Include watch-only for wallets without private key
698  m_coin_control->fAllowWatchOnly = model->wallet().privateKeysDisabled();
699 
700  // Calculate available amount to send.
702  for (int i = 0; i < ui->entries->count(); ++i) {
703  SendCoinsEntry* e = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
704  if (e && !e->isHidden() && e != entry) {
705  amount -= e->getValue().amount;
706  }
707  }
708 
709  if (amount > 0) {
711  entry->setAmount(amount);
712  } else {
713  entry->setAmount(0);
714  }
715 }
716 
718 {
719  ui->confTargetSelector ->setEnabled(ui->radioSmartFee->isChecked());
720  ui->labelSmartFee ->setEnabled(ui->radioSmartFee->isChecked());
721  ui->labelSmartFee2 ->setEnabled(ui->radioSmartFee->isChecked());
722  ui->labelSmartFee3 ->setEnabled(ui->radioSmartFee->isChecked());
723  ui->labelFeeEstimation ->setEnabled(ui->radioSmartFee->isChecked());
724  ui->labelCustomFeeWarning ->setEnabled(ui->radioCustomFee->isChecked());
725  ui->labelCustomPerKilobyte ->setEnabled(ui->radioCustomFee->isChecked());
726  ui->customFee ->setEnabled(ui->radioCustomFee->isChecked());
727 }
728 
730 {
731  if(!model || !model->getOptionsModel())
732  return;
733 
734  if (ui->radioSmartFee->isChecked())
735  ui->labelFeeMinimized->setText(ui->labelSmartFee->text());
736  else {
737  ui->labelFeeMinimized->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), ui->customFee->value()) + "/kB");
738  }
739 }
740 
742 {
743  if (ui->radioCustomFee->isChecked()) {
744  ctrl.m_feerate = CFeeRate(ui->customFee->value());
745  } else {
746  ctrl.m_feerate.reset();
747  }
748  // Avoid using global defaults when sending money from the GUI
749  // Either custom fee will be used or if not selected, the confirmation target from dropdown box
750  ctrl.m_confirm_target = getConfTargetForIndex(ui->confTargetSelector->currentIndex());
751  ctrl.m_signal_bip125_rbf = ui->optInRBF->isChecked();
752  // Include watch-only for wallets without private key
754 }
755 
756 void SendCoinsDialog::updateNumberOfBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, bool headers, SynchronizationState sync_state) {
757  if (sync_state == SynchronizationState::POST_INIT) {
759  }
760 }
761 
763 {
764  if(!model || !model->getOptionsModel())
765  return;
767  m_coin_control->m_feerate.reset(); // Explicitly use only fee estimation rate for smart fee labels
768  int returned_target;
769  FeeReason reason;
770  CFeeRate feeRate = CFeeRate(model->wallet().getMinimumFee(1000, *m_coin_control, &returned_target, &reason));
771 
772  ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), feeRate.GetFeePerK()) + "/kB");
773 
774  if (reason == FeeReason::FALLBACK) {
775  ui->labelSmartFee2->show(); // (Smart fee not initialized yet. This usually takes a few blocks...)
776  ui->labelFeeEstimation->setText("");
777  ui->fallbackFeeWarningLabel->setVisible(true);
778  int lightness = ui->fallbackFeeWarningLabel->palette().color(QPalette::WindowText).lightness();
779  QColor warning_colour(255 - (lightness / 5), 176 - (lightness / 3), 48 - (lightness / 14));
780  ui->fallbackFeeWarningLabel->setStyleSheet("QLabel { color: " + warning_colour.name() + "; }");
781  ui->fallbackFeeWarningLabel->setIndent(GUIUtil::TextWidth(QFontMetrics(ui->fallbackFeeWarningLabel->font()), "x"));
782  }
783  else
784  {
785  ui->labelSmartFee2->hide();
786  ui->labelFeeEstimation->setText(tr("Estimated to begin confirmation within %n block(s).", "", returned_target));
787  ui->fallbackFeeWarningLabel->setVisible(false);
788  }
789 
791 }
792 
793 // Coin Control: copy label "Quantity" to clipboard
795 {
796  GUIUtil::setClipboard(ui->labelCoinControlQuantity->text());
797 }
798 
799 // Coin Control: copy label "Amount" to clipboard
801 {
802  GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left(ui->labelCoinControlAmount->text().indexOf(" ")));
803 }
804 
805 // Coin Control: copy label "Fee" to clipboard
807 {
808  GUIUtil::setClipboard(ui->labelCoinControlFee->text().left(ui->labelCoinControlFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
809 }
810 
811 // Coin Control: copy label "After fee" to clipboard
813 {
814  GUIUtil::setClipboard(ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
815 }
816 
817 // Coin Control: copy label "Bytes" to clipboard
819 {
820  GUIUtil::setClipboard(ui->labelCoinControlBytes->text().replace(ASYMP_UTF8, ""));
821 }
822 
823 // Coin Control: copy label "Dust" to clipboard
825 {
826  GUIUtil::setClipboard(ui->labelCoinControlLowOutput->text());
827 }
828 
829 // Coin Control: copy label "Change" to clipboard
831 {
832  GUIUtil::setClipboard(ui->labelCoinControlChange->text().left(ui->labelCoinControlChange->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
833 }
834 
835 // Coin Control: settings menu - coin control enabled/disabled by user
837 {
838  ui->frameCoinControl->setVisible(checked);
839 
840  if (!checked && model) // coin control features disabled
841  m_coin_control->SetNull();
842 
844 }
845 
846 // Coin Control: button inputs -> show actual coin control dialog
848 {
850  dlg.exec();
852 }
853 
854 // Coin Control: checkbox custom change address
856 {
857  if (state == Qt::Unchecked)
858  {
859  m_coin_control->destChange = CNoDestination();
860  ui->labelCoinControlChangeLabel->clear();
861  }
862  else
863  // use this to re-validate an already entered address
864  coinControlChangeEdited(ui->lineEditCoinControlChange->text());
865 
866  ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked));
867 }
868 
869 // Coin Control: custom change address changed
871 {
872  if (model && model->getAddressTableModel())
873  {
874  // Default to no change address until verified
875  m_coin_control->destChange = CNoDestination();
876  ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:red;}");
877 
878  const CTxDestination dest = DecodeDestination(text.toStdString());
879 
880  if (text.isEmpty()) // Nothing entered
881  {
882  ui->labelCoinControlChangeLabel->setText("");
883  }
884  else if (!IsValidDestination(dest)) // Invalid address
885  {
886  ui->labelCoinControlChangeLabel->setText(tr("Warning: Invalid Bitcoin address"));
887  }
888  else // Valid address
889  {
890  if (!model->wallet().isSpendable(dest)) {
891  ui->labelCoinControlChangeLabel->setText(tr("Warning: Unknown change address"));
892 
893  // confirmation dialog
894  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?"),
895  QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel);
896 
897  if(btnRetVal == QMessageBox::Yes)
898  m_coin_control->destChange = dest;
899  else
900  {
901  ui->lineEditCoinControlChange->setText("");
902  ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
903  ui->labelCoinControlChangeLabel->setText("");
904  }
905  }
906  else // Known change address
907  {
908  ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
909 
910  // Query label
911  QString associatedLabel = model->getAddressTableModel()->labelForAddress(text);
912  if (!associatedLabel.isEmpty())
913  ui->labelCoinControlChangeLabel->setText(associatedLabel);
914  else
915  ui->labelCoinControlChangeLabel->setText(tr("(no label)"));
916 
917  m_coin_control->destChange = dest;
918  }
919  }
920  }
921 }
922 
923 // Coin Control: update labels
925 {
926  if (!model || !model->getOptionsModel())
927  return;
928 
930 
931  // set pay amounts
934 
935  for(int i = 0; i < ui->entries->count(); ++i)
936  {
937  SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
938  if(entry && !entry->isHidden())
939  {
940  SendCoinsRecipient rcp = entry->getValue();
942  if (rcp.fSubtractFeeFromAmount)
944  }
945  }
946 
947  if (m_coin_control->HasSelected())
948  {
949  // actual coin control calculation
951 
952  // show coin control stats
953  ui->labelCoinControlAutomaticallySelected->hide();
954  ui->widgetCoinControl->show();
955  }
956  else
957  {
958  // hide coin control stats
959  ui->labelCoinControlAutomaticallySelected->show();
960  ui->widgetCoinControl->hide();
961  ui->labelCoinControlInsuffFunds->hide();
962  }
963 }
964 
965 SendConfirmationDialog::SendConfirmationDialog(const QString& title, const QString& text, const QString& informative_text, const QString& detailed_text, int _secDelay, const QString& _confirmButtonText, QWidget* parent)
966  : QMessageBox(parent), secDelay(_secDelay), confirmButtonText(_confirmButtonText)
967 {
968  setIcon(QMessageBox::Question);
969  setWindowTitle(title); // On macOS, the window title is ignored (as required by the macOS Guidelines).
970  setText(text);
971  setInformativeText(informative_text);
972  setDetailedText(detailed_text);
973  setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel);
974  setDefaultButton(QMessageBox::Cancel);
975  yesButton = button(QMessageBox::Yes);
976  if (confirmButtonText.isEmpty()) {
977  confirmButtonText = yesButton->text();
978  }
979  updateYesButton();
980  connect(&countDownTimer, &QTimer::timeout, this, &SendConfirmationDialog::countDown);
981 }
982 
984 {
985  updateYesButton();
986  countDownTimer.start(1000);
987  return QMessageBox::exec();
988 }
989 
991 {
992  secDelay--;
993  updateYesButton();
994 
995  if(secDelay <= 0)
996  {
997  countDownTimer.stop();
998  }
999 }
1000 
1002 {
1003  if(secDelay > 0)
1004  {
1005  yesButton->setEnabled(false);
1006  yesButton->setText(confirmButtonText + " (" + QString::number(secDelay) + ")");
1007  }
1008  else
1009  {
1010  yesButton->setEnabled(true);
1011  yesButton->setText(confirmButtonText);
1012  }
1013 }
virtual bool privateKeysDisabled()=0
void removeEntry(SendCoinsEntry *entry)
void updateNumberOfBlocks(int count, const QDateTime &blockDate, double nVerificationProgress, bool headers, SynchronizationState sync_state)
Unit
Bitcoin units.
Definition: bitcoinunits.h:41
interfaces::Wallet & wallet() const
Definition: walletmodel.h:146
void setValue(const SendCoinsRecipient &value)
void payAmountChanged()
SynchronizationState
Current sync state passed to tip changed callbacks.
Definition: validation.h:98
bool fAllowWatchOnly
Includes watch only addresses which are solvable.
Definition: coincontrol.h:34
assert(!tx.IsCoinBase())
void updateFeeMinimizedLabel()
constexpr CAmount DEFAULT_PAY_TX_FEE
-paytxfee default
Definition: wallet.h:63
void setFocus()
void on_buttonChooseFee_clicked()
SendCoinsRecipient getValue()
int TextWidth(const QFontMetrics &fm, const QString &text)
Returns the distance in pixels appropriate for drawing a subsequent character after text...
Definition: guiutil.cpp:917
void reject() override
UnlockContext requestUnlock()
void coinControlClipboardQuantity()
std::string str() const
Definition: streams.h:280
void coinControlClipboardAfterFee()
void setAddress(const QString &address)
virtual CAmount getDefaultMaxTxFee()=0
Get max tx fee.
bool IsValidDestination(const CTxDestination &dest)
Check whether a CTxDestination is a CNoDestination.
Definition: standard.cpp:321
#define SEND_CONFIRM_DELAY
#define PACKAGE_NAME
SendCoinsReturn sendCoins(WalletModelTransaction &transaction)
void coinControlFeaturesChanged(bool)
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:216
bool handlePaymentRequest(const SendCoinsRecipient &recipient)
A version of CTransaction with the PSBT format.
Definition: psbt.h:390
Double ended buffer combining vector and stream-like interfaces.
Definition: streams.h:202
std::variant< CNoDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessUnknown > CTxDestination
A txout script template with a specific destination.
Definition: standard.h:212
void numBlocksChanged(int count, const QDateTime &blockDate, double nVerificationProgress, bool header, SynchronizationState sync_state)
fs::ofstream ofstream
Definition: fs.h:91
static constexpr std::array confTargets
AddressTableModel * getAddressTableModel()
#define ASYMP_UTF8
static void updateLabels(CCoinControl &m_coin_control, WalletModel *, QDialog *)
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:22
int getDisplayUnit() const
Definition: optionsmodel.h:85
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
int64_t CAmount
Amount in satoshis (Can be negative)
Definition: amount.h:12
static QList< CAmount > payAmounts
QWidget * setupTabChain(QWidget *prev)
Set up the tab chain manually, as Qt messes up the tab chain by default in some cases (issue https://...
std::string EncodeBase64(Span< const unsigned char > input)
Ui::SendCoinsDialog * ui
Optional< bool > m_signal_bip125_rbf
Override the wallet&#39;s m_signal_rbf if set.
Definition: coincontrol.h:42
SendCoinsEntry * addEntry()
void clear()
bool validate(interfaces::Node &node)
void setupAddressWidget(QValidatedLineEdit *widget, QWidget *parent)
Definition: guiutil.cpp:102
void setBalance(const interfaces::WalletBalances &balances)
static CAmount balance
void setAddress(const QString &address)
void coinControlClipboardChange()
Collection of wallet balances.
Definition: wallet.h:349
void useAvailableBalance(SendCoinsEntry *entry)
void setClientModel(ClientModel *clientModel)
void setClipboard(const QString &str)
Definition: guiutil.cpp:736
static QString formatHtmlWithUnit(int unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD)
Format as HTML string (with unit)
ClientModel * clientModel
static secp256k1_context * ctx
Definition: tests.c:36
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)
virtual CAmount getMinimumFee(unsigned int tx_bytes, const CCoinControl &coin_control, int *returned_target, FeeReason *reason)=0
Get minimum fee.
WalletModel * model
Dialog for sending bitcoins.
static QString formatWithUnit(int unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD)
Format as string (with unit)
Optional< unsigned int > m_confirm_target
Override the default confirmation target if set.
Definition: coincontrol.h:40
void coinControlChangeEdited(const QString &)
FeeReason
Definition: fees.h:43
QString getWalletName() const
interfaces::Node & node() const
Definition: walletmodel.h:145
void removeEntry(SendCoinsEntry *entry)
void displayUnitChanged(int unit)
Model for Bitcoin network client.
Definition: clientmodel.h:46
void coinControlUpdateLabels()
void setModel(WalletModel *model)
int getConfTargetForIndex(int index)
SendCoinsDialog(const PlatformStyle *platformStyle, QWidget *parent=nullptr)
void accept() override
SendConfirmationDialog(const QString &title, const QString &text, const QString &informative_text="", const QString &detailed_text="", int secDelay=SEND_CONFIRM_DELAY, const QString &confirmText="", QWidget *parent=nullptr)
bool getCoinControlFeatures() const
Definition: optionsmodel.h:87
virtual TransactionError fillPSBT(int sighash_type, bool sign, bool bip32derivs, PartiallySignedTransaction &psbtx, bool &complete, size_t *n_signed)=0
Fill PSBT.
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.
CTxDestination DecodeDestination(const std::string &str)
Definition: key_io.cpp:211
void subtractFeeFromAmountChanged()
static bool fSubtractFeeFromAmount
bool PrepareSendText(QString &question_string, QString &informative_text, QString &detailed_text)
int getIndexForConfTarget(int target)
const CChainParams & Params()
Return the currently selected parameters.
Interface to Bitcoin wallet from Qt view code.
Definition: walletmodel.h:51
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:263
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 kilobyte: CAmount / kB.
Definition: feerate.h:29
static int count
Definition: tests.c:35
void coinControlClipboardBytes()
void useAvailableBalance(SendCoinsEntry *entry)
A mutable version of CTransaction.
Definition: transaction.h:344
virtual unsigned int getConfirmTarget()=0
Get tx confirm target.
const PlatformStyle * platformStyle
QString formatNiceTimeOffset(qint64 secs)
Definition: guiutil.cpp:824
std::unique_ptr< CCoinControl > m_coin_control
Optional< CFeeRate > m_feerate
Override the wallet&#39;s m_pay_tx_fee if set.
Definition: coincontrol.h:38
void coinControlClipboardAmount()
bool isMultiwallet()
void on_buttonMinimizeFee_clicked()
virtual CAmount getAvailableBalance(const CCoinControl &coin_control)=0
Get available balance.
virtual WalletBalances getBalances()=0
Get balances.
void pasteEntry(const SendCoinsRecipient &rv)
QAbstractButton * yesButton
virtual CAmount getRequiredFee(unsigned int tx_bytes)=0
Get required fee.
void message(const QString &title, const QString &message, unsigned int style)
void coinsSent(const uint256 &txid)
CAmount GetFeePerK() const
Return the fee in satoshis for a size of 1000 bytes.
Definition: feerate.h:60
void balanceChanged(const interfaces::WalletBalances &balances)
bool getImagesOnButtons() const
Definition: platformstyle.h:21
void coinControlButtonClicked()
void coinControlClipboardFee()
OptionsModel * getOptionsModel()
Predefined combinations for certain default usage cases.
Definition: ui_interface.h:66
void coinControlChangeChecked(int)