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