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 <validation.h>
32 
33 #include <QFontMetrics>
34 #include <QScrollBar>
35 #include <QSettings>
36 #include <QTextDocument>
37 
38 static const std::array<int, 9> 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  connect(ui->groupFee, static_cast<void (QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked), this, &SendCoinsDialog::updateFeeSectionControls);
177  connect(ui->groupFee, static_cast<void (QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked), this, &SendCoinsDialog::coinControlUpdateLabels);
179  connect(ui->optInRBF, &QCheckBox::stateChanged, this, &SendCoinsDialog::updateSmartFeeLabel);
180  connect(ui->optInRBF, &QCheckBox::stateChanged, this, &SendCoinsDialog::coinControlUpdateLabels);
181  CAmount requiredFee = model->wallet().getRequiredFee(1000);
182  ui->customFee->SetMinValue(requiredFee);
183  if (ui->customFee->value() < requiredFee) {
184  ui->customFee->setValue(requiredFee);
185  }
186  ui->customFee->setSingleStep(requiredFee);
189 
190  // set default rbf checkbox state
191  ui->optInRBF->setCheckState(Qt::Checked);
192 
193  if (model->wallet().privateKeysDisabled()) {
194  ui->sendButton->setText(tr("Cr&eate Unsigned"));
195  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));
196  }
197 
198  // set the smartfee-sliders default value (wallets default conf.target or last stored value)
199  QSettings settings;
200  if (settings.value("nSmartFeeSliderPosition").toInt() != 0) {
201  // migrate nSmartFeeSliderPosition to nConfTarget
202  // nConfTarget is available since 0.15 (replaced nSmartFeeSliderPosition)
203  int nConfirmTarget = 25 - settings.value("nSmartFeeSliderPosition").toInt(); // 25 == old slider range
204  settings.setValue("nConfTarget", nConfirmTarget);
205  settings.remove("nSmartFeeSliderPosition");
206  }
207  if (settings.value("nConfTarget").toInt() == 0)
208  ui->confTargetSelector->setCurrentIndex(getIndexForConfTarget(model->wallet().getConfirmTarget()));
209  else
210  ui->confTargetSelector->setCurrentIndex(getIndexForConfTarget(settings.value("nConfTarget").toInt()));
211  }
212 }
213 
215 {
216  QSettings settings;
217  settings.setValue("fFeeSectionMinimized", fFeeMinimized);
218  settings.setValue("nFeeRadio", ui->groupFee->checkedId());
219  settings.setValue("nConfTarget", getConfTargetForIndex(ui->confTargetSelector->currentIndex()));
220  settings.setValue("nTransactionFee", (qint64)ui->customFee->value());
221 
222  delete ui;
223 }
224 
225 bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informative_text, QString& detailed_text)
226 {
227  QList<SendCoinsRecipient> recipients;
228  bool valid = true;
229 
230  for(int i = 0; i < ui->entries->count(); ++i)
231  {
232  SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
233  if(entry)
234  {
235  if(entry->validate(model->node()))
236  {
237  recipients.append(entry->getValue());
238  }
239  else if (valid)
240  {
241  ui->scrollArea->ensureWidgetVisible(entry);
242  valid = false;
243  }
244  }
245  }
246 
247  if(!valid || recipients.isEmpty())
248  {
249  return false;
250  }
251 
252  fNewRecipientAllowed = false;
254  if(!ctx.isValid())
255  {
256  // Unlock wallet was cancelled
257  fNewRecipientAllowed = true;
258  return false;
259  }
260 
261  // prepare transaction for getting txFee earlier
262  m_current_transaction = MakeUnique<WalletModelTransaction>(recipients);
263  WalletModel::SendCoinsReturn prepareStatus;
264 
266 
268 
269  // process prepareStatus and on error generate message shown to user
270  processSendCoinsReturn(prepareStatus,
272 
273  if(prepareStatus.status != WalletModel::OK) {
274  fNewRecipientAllowed = true;
275  return false;
276  }
277 
278  CAmount txFee = m_current_transaction->getTransactionFee();
279  QStringList formatted;
280  for (const SendCoinsRecipient &rcp : m_current_transaction->getRecipients())
281  {
282  // generate amount string with wallet name in case of multiwallet
283  QString amount = BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
284  if (model->isMultiwallet()) {
285  amount.append(tr(" from wallet '%1'").arg(GUIUtil::HtmlEscape(model->getWalletName())));
286  }
287 
288  // generate address string
289  QString address = rcp.address;
290 
291  QString recipientElement;
292 
293  {
294  if(rcp.label.length() > 0) // label with address
295  {
296  recipientElement.append(tr("%1 to '%2'").arg(amount, GUIUtil::HtmlEscape(rcp.label)));
297  recipientElement.append(QString(" (%1)").arg(address));
298  }
299  else // just address
300  {
301  recipientElement.append(tr("%1 to %2").arg(amount, address));
302  }
303  }
304  formatted.append(recipientElement);
305  }
306 
307  if (model->wallet().privateKeysDisabled()) {
308  question_string.append(tr("Do you want to draft this transaction?"));
309  } else {
310  question_string.append(tr("Are you sure you want to send?"));
311  }
312 
313  question_string.append("<br /><span style='font-size:10pt;'>");
314  if (model->wallet().privateKeysDisabled()) {
315  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));
316  } else {
317  question_string.append(tr("Please, review your transaction."));
318  }
319  question_string.append("</span>%1");
320 
321  if(txFee > 0)
322  {
323  // append fee string if a fee is required
324  question_string.append("<hr /><b>");
325  question_string.append(tr("Transaction fee"));
326  question_string.append("</b>");
327 
328  // append transaction size
329  question_string.append(" (" + QString::number((double)m_current_transaction->getTransactionSize() / 1000) + " kB): ");
330 
331  // append transaction fee value
332  question_string.append("<span style='color:#aa0000; font-weight:bold;'>");
333  question_string.append(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), txFee));
334  question_string.append("</span><br />");
335 
336  // append RBF message according to transaction's signalling
337  question_string.append("<span style='font-size:10pt; font-weight:normal;'>");
338  if (ui->optInRBF->isChecked()) {
339  question_string.append(tr("You can increase the fee later (signals Replace-By-Fee, BIP-125)."));
340  } else {
341  question_string.append(tr("Not signalling Replace-By-Fee, BIP-125."));
342  }
343  question_string.append("</span>");
344  }
345 
346  // add total amount in all subdivision units
347  question_string.append("<hr />");
348  CAmount totalAmount = m_current_transaction->getTotalTransactionAmount() + txFee;
349  QStringList alternativeUnits;
351  {
352  if(u != model->getOptionsModel()->getDisplayUnit())
353  alternativeUnits.append(BitcoinUnits::formatHtmlWithUnit(u, totalAmount));
354  }
355  question_string.append(QString("<b>%1</b>: <b>%2</b>").arg(tr("Total Amount"))
357  question_string.append(QString("<br /><span style='font-size:10pt; font-weight:normal;'>(=%1)</span>")
358  .arg(alternativeUnits.join(" " + tr("or") + " ")));
359 
360  if (formatted.size() > 1) {
361  question_string = question_string.arg("");
362  informative_text = tr("To review recipient list click \"Show Details...\"");
363  detailed_text = formatted.join("\n\n");
364  } else {
365  question_string = question_string.arg("<br /><br />" + formatted.at(0));
366  }
367 
368  return true;
369 }
370 
372 {
373  if(!model || !model->getOptionsModel())
374  return;
375 
376  QString question_string, informative_text, detailed_text;
377  if (!PrepareSendText(question_string, informative_text, detailed_text)) return;
378  assert(m_current_transaction);
379 
380  const QString confirmation = model->wallet().privateKeysDisabled() ? tr("Confirm transaction proposal") : tr("Confirm send coins");
381  const QString confirmButtonText = model->wallet().privateKeysDisabled() ? tr("Create Unsigned") : tr("Send");
382  SendConfirmationDialog confirmationDialog(confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, confirmButtonText, this);
383  confirmationDialog.exec();
384  QMessageBox::StandardButton retval = static_cast<QMessageBox::StandardButton>(confirmationDialog.result());
385 
386  if(retval != QMessageBox::Yes)
387  {
388  fNewRecipientAllowed = true;
389  return;
390  }
391 
392  bool send_failure = false;
393  if (model->wallet().privateKeysDisabled()) {
395  PartiallySignedTransaction psbtx(mtx);
396  bool complete = false;
397  const TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, psbtx, complete, nullptr);
398  assert(!complete);
399  assert(err == TransactionError::OK);
400  // Serialize the PSBT
402  ssTx << psbtx;
403  GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str());
404  QMessageBox msgBox;
405  msgBox.setText("Unsigned Transaction");
406  msgBox.setInformativeText("The PSBT has been copied to the clipboard. You can also save it.");
407  msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard);
408  msgBox.setDefaultButton(QMessageBox::Discard);
409  switch (msgBox.exec()) {
410  case QMessageBox::Save: {
411  QString selectedFilter;
412  QString fileNameSuggestion = "";
413  bool first = true;
414  for (const SendCoinsRecipient &rcp : m_current_transaction->getRecipients()) {
415  if (!first) {
416  fileNameSuggestion.append(" - ");
417  }
418  QString labelOrAddress = rcp.label.isEmpty() ? rcp.address : rcp.label;
419  QString amount = BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
420  fileNameSuggestion.append(labelOrAddress + "-" + amount);
421  first = false;
422  }
423  fileNameSuggestion.append(".psbt");
424  QString filename = GUIUtil::getSaveFileName(this,
425  tr("Save Transaction Data"), fileNameSuggestion,
426  tr("Partially Signed Transaction (Binary) (*.psbt)"), &selectedFilter);
427  if (filename.isEmpty()) {
428  return;
429  }
430  std::ofstream out(filename.toLocal8Bit().data());
431  out << ssTx.str();
432  out.close();
433  Q_EMIT message(tr("PSBT saved"), "PSBT saved to disk", CClientUIInterface::MSG_INFORMATION);
434  break;
435  }
436  case QMessageBox::Discard:
437  break;
438  default:
439  assert(false);
440  }
441  } else {
442  // now send the prepared transaction
444  // process sendStatus and on error generate message shown to user
445  processSendCoinsReturn(sendStatus);
446 
447  if (sendStatus.status == WalletModel::OK) {
448  Q_EMIT coinsSent(m_current_transaction->getWtx()->GetHash());
449  } else {
450  send_failure = true;
451  }
452  }
453  if (!send_failure) {
454  accept();
455  m_coin_control->UnSelectAll();
457  }
458  fNewRecipientAllowed = true;
459  m_current_transaction.reset();
460 }
461 
463 {
464  m_current_transaction.reset();
465 
466  // Clear coin control settings
467  m_coin_control->UnSelectAll();
468  ui->checkBoxCoinControlChange->setChecked(false);
469  ui->lineEditCoinControlChange->clear();
471 
472  // Remove entries until only one left
473  while(ui->entries->count())
474  {
475  ui->entries->takeAt(0)->widget()->deleteLater();
476  }
477  addEntry();
478 
480 }
481 
483 {
484  clear();
485 }
486 
488 {
489  clear();
490 }
491 
493 {
494  SendCoinsEntry *entry = new SendCoinsEntry(platformStyle, this);
495  entry->setModel(model);
496  ui->entries->addWidget(entry);
501 
502  // Focus the field, so that entry can start immediately
503  entry->clear();
504  entry->setFocus();
505  ui->scrollAreaWidgetContents->resize(ui->scrollAreaWidgetContents->sizeHint());
506  qApp->processEvents();
507  QScrollBar* bar = ui->scrollArea->verticalScrollBar();
508  if(bar)
509  bar->setSliderPosition(bar->maximum());
510 
512  return entry;
513 }
514 
516 {
517  setupTabChain(nullptr);
519 }
520 
522 {
523  entry->hide();
524 
525  // If the last entry is about to be removed add an empty one
526  if (ui->entries->count() == 1)
527  addEntry();
528 
529  entry->deleteLater();
530 
532 }
533 
534 QWidget *SendCoinsDialog::setupTabChain(QWidget *prev)
535 {
536  for(int i = 0; i < ui->entries->count(); ++i)
537  {
538  SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
539  if(entry)
540  {
541  prev = entry->setupTabChain(prev);
542  }
543  }
544  QWidget::setTabOrder(prev, ui->sendButton);
545  QWidget::setTabOrder(ui->sendButton, ui->clearButton);
546  QWidget::setTabOrder(ui->clearButton, ui->addButton);
547  return ui->addButton;
548 }
549 
550 void SendCoinsDialog::setAddress(const QString &address)
551 {
552  SendCoinsEntry *entry = nullptr;
553  // Replace the first entry if it is still unused
554  if(ui->entries->count() == 1)
555  {
556  SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
557  if(first->isClear())
558  {
559  entry = first;
560  }
561  }
562  if(!entry)
563  {
564  entry = addEntry();
565  }
566 
567  entry->setAddress(address);
568 }
569 
571 {
573  return;
574 
575  SendCoinsEntry *entry = nullptr;
576  // Replace the first entry if it is still unused
577  if(ui->entries->count() == 1)
578  {
579  SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
580  if(first->isClear())
581  {
582  entry = first;
583  }
584  }
585  if(!entry)
586  {
587  entry = addEntry();
588  }
589 
590  entry->setValue(rv);
592 }
593 
595 {
596  // Just paste the entry, all pre-checks
597  // are done in paymentserver.cpp.
598  pasteEntry(rv);
599  return true;
600 }
601 
603 {
604  if(model && model->getOptionsModel())
605  {
606  CAmount balance = balances.balance;
607  if (model->wallet().privateKeysDisabled()) {
608  balance = balances.watch_only_balance;
609  ui->labelBalanceName->setText(tr("Watch-only balance:"));
610  }
612  }
613 }
614 
616 {
618  ui->customFee->setDisplayUnit(model->getOptionsModel()->getDisplayUnit());
620 }
621 
622 void SendCoinsDialog::processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg)
623 {
624  QPair<QString, CClientUIInterface::MessageBoxFlags> msgParams;
625  // Default to a warning message, override if error message is needed
626  msgParams.second = CClientUIInterface::MSG_WARNING;
627 
628  // This comment is specific to SendCoinsDialog usage of WalletModel::SendCoinsReturn.
629  // All status values are used only in WalletModel::prepareTransaction()
630  switch(sendCoinsReturn.status)
631  {
633  msgParams.first = tr("The recipient address is not valid. Please recheck.");
634  break;
636  msgParams.first = tr("The amount to pay must be larger than 0.");
637  break;
639  msgParams.first = tr("The amount exceeds your balance.");
640  break;
642  msgParams.first = tr("The total exceeds your balance when the %1 transaction fee is included.").arg(msgArg);
643  break;
645  msgParams.first = tr("Duplicate address found: addresses should only be used once each.");
646  break;
648  msgParams.first = tr("Transaction creation failed!");
649  msgParams.second = CClientUIInterface::MSG_ERROR;
650  break;
652  msgParams.first = tr("A fee higher than %1 is considered an absurdly high fee.").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), model->wallet().getDefaultMaxTxFee()));
653  break;
655  msgParams.first = tr("Payment request expired.");
656  msgParams.second = CClientUIInterface::MSG_ERROR;
657  break;
658  // included to prevent a compiler warning.
659  case WalletModel::OK:
660  default:
661  return;
662  }
663 
664  Q_EMIT message(tr("Send Coins"), msgParams.first, msgParams.second);
665 }
666 
668 {
669  ui->labelFeeMinimized->setVisible(fMinimize);
670  ui->buttonChooseFee ->setVisible(fMinimize);
671  ui->buttonMinimizeFee->setVisible(!fMinimize);
672  ui->frameFeeSelection->setVisible(!fMinimize);
673  ui->horizontalLayoutSmartFee->setContentsMargins(0, (fMinimize ? 0 : 6), 0, 0);
674  fFeeMinimized = fMinimize;
675 }
676 
678 {
679  minimizeFeeSection(false);
680 }
681 
683 {
685  minimizeFeeSection(true);
686 }
687 
689 {
690  // Include watch-only for wallets without private key
691  m_coin_control->fAllowWatchOnly = model->wallet().privateKeysDisabled();
692 
693  // Calculate available amount to send.
695  for (int i = 0; i < ui->entries->count(); ++i) {
696  SendCoinsEntry* e = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
697  if (e && !e->isHidden() && e != entry) {
698  amount -= e->getValue().amount;
699  }
700  }
701 
702  if (amount > 0) {
704  entry->setAmount(amount);
705  } else {
706  entry->setAmount(0);
707  }
708 }
709 
711 {
712  ui->confTargetSelector ->setEnabled(ui->radioSmartFee->isChecked());
713  ui->labelSmartFee ->setEnabled(ui->radioSmartFee->isChecked());
714  ui->labelSmartFee2 ->setEnabled(ui->radioSmartFee->isChecked());
715  ui->labelSmartFee3 ->setEnabled(ui->radioSmartFee->isChecked());
716  ui->labelFeeEstimation ->setEnabled(ui->radioSmartFee->isChecked());
717  ui->labelCustomFeeWarning ->setEnabled(ui->radioCustomFee->isChecked());
718  ui->labelCustomPerKilobyte ->setEnabled(ui->radioCustomFee->isChecked());
719  ui->customFee ->setEnabled(ui->radioCustomFee->isChecked());
720 }
721 
723 {
724  if(!model || !model->getOptionsModel())
725  return;
726 
727  if (ui->radioSmartFee->isChecked())
728  ui->labelFeeMinimized->setText(ui->labelSmartFee->text());
729  else {
730  ui->labelFeeMinimized->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), ui->customFee->value()) + "/kB");
731  }
732 }
733 
735 {
736  if (ui->radioCustomFee->isChecked()) {
737  ctrl.m_feerate = CFeeRate(ui->customFee->value());
738  } else {
739  ctrl.m_feerate.reset();
740  }
741  // Avoid using global defaults when sending money from the GUI
742  // Either custom fee will be used or if not selected, the confirmation target from dropdown box
743  ctrl.m_confirm_target = getConfTargetForIndex(ui->confTargetSelector->currentIndex());
744  ctrl.m_signal_bip125_rbf = ui->optInRBF->isChecked();
745  // Include watch-only for wallets without private key
747 }
748 
749 void SendCoinsDialog::updateNumberOfBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, bool headers, SynchronizationState sync_state) {
750  if (sync_state == SynchronizationState::POST_INIT) {
752  }
753 }
754 
756 {
757  if(!model || !model->getOptionsModel())
758  return;
760  m_coin_control->m_feerate.reset(); // Explicitly use only fee estimation rate for smart fee labels
761  int returned_target;
762  FeeReason reason;
763  CFeeRate feeRate = CFeeRate(model->wallet().getMinimumFee(1000, *m_coin_control, &returned_target, &reason));
764 
765  ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), feeRate.GetFeePerK()) + "/kB");
766 
767  if (reason == FeeReason::FALLBACK) {
768  ui->labelSmartFee2->show(); // (Smart fee not initialized yet. This usually takes a few blocks...)
769  ui->labelFeeEstimation->setText("");
770  ui->fallbackFeeWarningLabel->setVisible(true);
771  int lightness = ui->fallbackFeeWarningLabel->palette().color(QPalette::WindowText).lightness();
772  QColor warning_colour(255 - (lightness / 5), 176 - (lightness / 3), 48 - (lightness / 14));
773  ui->fallbackFeeWarningLabel->setStyleSheet("QLabel { color: " + warning_colour.name() + "; }");
774  ui->fallbackFeeWarningLabel->setIndent(GUIUtil::TextWidth(QFontMetrics(ui->fallbackFeeWarningLabel->font()), "x"));
775  }
776  else
777  {
778  ui->labelSmartFee2->hide();
779  ui->labelFeeEstimation->setText(tr("Estimated to begin confirmation within %n block(s).", "", returned_target));
780  ui->fallbackFeeWarningLabel->setVisible(false);
781  }
782 
784 }
785 
786 // Coin Control: copy label "Quantity" to clipboard
788 {
789  GUIUtil::setClipboard(ui->labelCoinControlQuantity->text());
790 }
791 
792 // Coin Control: copy label "Amount" to clipboard
794 {
795  GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left(ui->labelCoinControlAmount->text().indexOf(" ")));
796 }
797 
798 // Coin Control: copy label "Fee" to clipboard
800 {
801  GUIUtil::setClipboard(ui->labelCoinControlFee->text().left(ui->labelCoinControlFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
802 }
803 
804 // Coin Control: copy label "After fee" to clipboard
806 {
807  GUIUtil::setClipboard(ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
808 }
809 
810 // Coin Control: copy label "Bytes" to clipboard
812 {
813  GUIUtil::setClipboard(ui->labelCoinControlBytes->text().replace(ASYMP_UTF8, ""));
814 }
815 
816 // Coin Control: copy label "Dust" to clipboard
818 {
819  GUIUtil::setClipboard(ui->labelCoinControlLowOutput->text());
820 }
821 
822 // Coin Control: copy label "Change" to clipboard
824 {
825  GUIUtil::setClipboard(ui->labelCoinControlChange->text().left(ui->labelCoinControlChange->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
826 }
827 
828 // Coin Control: settings menu - coin control enabled/disabled by user
830 {
831  ui->frameCoinControl->setVisible(checked);
832 
833  if (!checked && model) // coin control features disabled
834  m_coin_control->SetNull();
835 
837 }
838 
839 // Coin Control: button inputs -> show actual coin control dialog
841 {
843  dlg.exec();
845 }
846 
847 // Coin Control: checkbox custom change address
849 {
850  if (state == Qt::Unchecked)
851  {
852  m_coin_control->destChange = CNoDestination();
853  ui->labelCoinControlChangeLabel->clear();
854  }
855  else
856  // use this to re-validate an already entered address
857  coinControlChangeEdited(ui->lineEditCoinControlChange->text());
858 
859  ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked));
860 }
861 
862 // Coin Control: custom change address changed
864 {
865  if (model && model->getAddressTableModel())
866  {
867  // Default to no change address until verified
868  m_coin_control->destChange = CNoDestination();
869  ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:red;}");
870 
871  const CTxDestination dest = DecodeDestination(text.toStdString());
872 
873  if (text.isEmpty()) // Nothing entered
874  {
875  ui->labelCoinControlChangeLabel->setText("");
876  }
877  else if (!IsValidDestination(dest)) // Invalid address
878  {
879  ui->labelCoinControlChangeLabel->setText(tr("Warning: Invalid Bitcoin address"));
880  }
881  else // Valid address
882  {
883  if (!model->wallet().isSpendable(dest)) {
884  ui->labelCoinControlChangeLabel->setText(tr("Warning: Unknown change address"));
885 
886  // confirmation dialog
887  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?"),
888  QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel);
889 
890  if(btnRetVal == QMessageBox::Yes)
891  m_coin_control->destChange = dest;
892  else
893  {
894  ui->lineEditCoinControlChange->setText("");
895  ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
896  ui->labelCoinControlChangeLabel->setText("");
897  }
898  }
899  else // Known change address
900  {
901  ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
902 
903  // Query label
904  QString associatedLabel = model->getAddressTableModel()->labelForAddress(text);
905  if (!associatedLabel.isEmpty())
906  ui->labelCoinControlChangeLabel->setText(associatedLabel);
907  else
908  ui->labelCoinControlChangeLabel->setText(tr("(no label)"));
909 
910  m_coin_control->destChange = dest;
911  }
912  }
913  }
914 }
915 
916 // Coin Control: update labels
918 {
919  if (!model || !model->getOptionsModel())
920  return;
921 
923 
924  // set pay amounts
927 
928  for(int i = 0; i < ui->entries->count(); ++i)
929  {
930  SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
931  if(entry && !entry->isHidden())
932  {
933  SendCoinsRecipient rcp = entry->getValue();
935  if (rcp.fSubtractFeeFromAmount)
937  }
938  }
939 
940  if (m_coin_control->HasSelected())
941  {
942  // actual coin control calculation
944 
945  // show coin control stats
946  ui->labelCoinControlAutomaticallySelected->hide();
947  ui->widgetCoinControl->show();
948  }
949  else
950  {
951  // hide coin control stats
952  ui->labelCoinControlAutomaticallySelected->show();
953  ui->widgetCoinControl->hide();
954  ui->labelCoinControlInsuffFunds->hide();
955  }
956 }
957 
958 SendConfirmationDialog::SendConfirmationDialog(const QString& title, const QString& text, const QString& informative_text, const QString& detailed_text, int _secDelay, const QString& _confirmButtonText, QWidget* parent)
959  : QMessageBox(parent), secDelay(_secDelay), confirmButtonText(_confirmButtonText)
960 {
961  setIcon(QMessageBox::Question);
962  setWindowTitle(title); // On macOS, the window title is ignored (as required by the macOS Guidelines).
963  setText(text);
964  setInformativeText(informative_text);
965  setDetailedText(detailed_text);
966  setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel);
967  setDefaultButton(QMessageBox::Cancel);
968  yesButton = button(QMessageBox::Yes);
969  updateYesButton();
970  connect(&countDownTimer, &QTimer::timeout, this, &SendConfirmationDialog::countDown);
971 }
972 
974 {
975  updateYesButton();
976  countDownTimer.start(1000);
977  return QMessageBox::exec();
978 }
979 
981 {
982  secDelay--;
983  updateYesButton();
984 
985  if(secDelay <= 0)
986  {
987  countDownTimer.stop();
988  }
989 }
990 
992 {
993  if(secDelay > 0)
994  {
995  yesButton->setEnabled(false);
996  yesButton->setText(confirmButtonText + " (" + QString::number(secDelay) + ")");
997  }
998  else
999  {
1000  yesButton->setEnabled(true);
1001  yesButton->setText(confirmButtonText);
1002  }
1003 }
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:106
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:63
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:888
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:322
#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:215
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
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:84
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:101
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:735
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: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:86
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:262
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:353
virtual unsigned int getConfirmTarget()=0
Get tx confirm target.
const PlatformStyle * platformStyle
QString formatNiceTimeOffset(qint64 secs)
Definition: guiutil.cpp:795
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:214
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)