Bitcoin Core  0.18.99
P2P Digital Currency
sendcoinsdialog.cpp
Go to the documentation of this file.
1 // Copyright (c) 2011-2018 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 <wallet/coincontrol.h>
25 #include <ui_interface.h>
26 #include <txmempool.h>
27 #include <policy/fees.h>
28 #include <wallet/fees.h>
29 
30 #include <QFontMetrics>
31 #include <QScrollBar>
32 #include <QSettings>
33 #include <QTextDocument>
34 
35 static const std::array<int, 9> confTargets = { {2, 4, 6, 12, 24, 48, 144, 504, 1008} };
36 int getConfTargetForIndex(int index) {
37  if (index+1 > static_cast<int>(confTargets.size())) {
38  return confTargets.back();
39  }
40  if (index < 0) {
41  return confTargets[0];
42  }
43  return confTargets[index];
44 }
45 int getIndexForConfTarget(int target) {
46  for (unsigned int i = 0; i < confTargets.size(); i++) {
47  if (confTargets[i] >= target) {
48  return i;
49  }
50  }
51  return confTargets.size() - 1;
52 }
53 
54 SendCoinsDialog::SendCoinsDialog(const PlatformStyle *_platformStyle, QWidget *parent) :
55  QDialog(parent),
56  ui(new Ui::SendCoinsDialog),
57  clientModel(nullptr),
58  model(nullptr),
59  fNewRecipientAllowed(true),
60  fFeeMinimized(true),
61  platformStyle(_platformStyle)
62 {
63  ui->setupUi(this);
64 
65  if (!_platformStyle->getImagesOnButtons()) {
66  ui->addButton->setIcon(QIcon());
67  ui->clearButton->setIcon(QIcon());
68  ui->sendButton->setIcon(QIcon());
69  } else {
70  ui->addButton->setIcon(_platformStyle->SingleColorIcon(":/icons/add"));
71  ui->clearButton->setIcon(_platformStyle->SingleColorIcon(":/icons/remove"));
72  ui->sendButton->setIcon(_platformStyle->SingleColorIcon(":/icons/send"));
73  }
74 
75  GUIUtil::setupAddressWidget(ui->lineEditCoinControlChange, this);
76 
77  addEntry();
78 
79  connect(ui->addButton, &QPushButton::clicked, this, &SendCoinsDialog::addEntry);
80  connect(ui->clearButton, &QPushButton::clicked, this, &SendCoinsDialog::clear);
81 
82  // Coin Control
83  connect(ui->pushButtonCoinControl, &QPushButton::clicked, this, &SendCoinsDialog::coinControlButtonClicked);
84  connect(ui->checkBoxCoinControlChange, &QCheckBox::stateChanged, this, &SendCoinsDialog::coinControlChangeChecked);
85  connect(ui->lineEditCoinControlChange, &QValidatedLineEdit::textEdited, this, &SendCoinsDialog::coinControlChangeEdited);
86 
87  // Coin Control: clipboard actions
88  QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this);
89  QAction *clipboardAmountAction = new QAction(tr("Copy amount"), this);
90  QAction *clipboardFeeAction = new QAction(tr("Copy fee"), this);
91  QAction *clipboardAfterFeeAction = new QAction(tr("Copy after fee"), this);
92  QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this);
93  QAction *clipboardLowOutputAction = new QAction(tr("Copy dust"), this);
94  QAction *clipboardChangeAction = new QAction(tr("Copy change"), this);
95  connect(clipboardQuantityAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardQuantity);
96  connect(clipboardAmountAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardAmount);
97  connect(clipboardFeeAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardFee);
98  connect(clipboardAfterFeeAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardAfterFee);
99  connect(clipboardBytesAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardBytes);
100  connect(clipboardLowOutputAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardLowOutput);
101  connect(clipboardChangeAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardChange);
102  ui->labelCoinControlQuantity->addAction(clipboardQuantityAction);
103  ui->labelCoinControlAmount->addAction(clipboardAmountAction);
104  ui->labelCoinControlFee->addAction(clipboardFeeAction);
105  ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction);
106  ui->labelCoinControlBytes->addAction(clipboardBytesAction);
107  ui->labelCoinControlLowOutput->addAction(clipboardLowOutputAction);
108  ui->labelCoinControlChange->addAction(clipboardChangeAction);
109 
110  // init transaction fee section
111  QSettings settings;
112  if (!settings.contains("fFeeSectionMinimized"))
113  settings.setValue("fFeeSectionMinimized", true);
114  if (!settings.contains("nFeeRadio") && settings.contains("nTransactionFee") && settings.value("nTransactionFee").toLongLong() > 0) // compatibility
115  settings.setValue("nFeeRadio", 1); // custom
116  if (!settings.contains("nFeeRadio"))
117  settings.setValue("nFeeRadio", 0); // recommended
118  if (!settings.contains("nSmartFeeSliderPosition"))
119  settings.setValue("nSmartFeeSliderPosition", 0);
120  if (!settings.contains("nTransactionFee"))
121  settings.setValue("nTransactionFee", (qint64)DEFAULT_PAY_TX_FEE);
122  ui->groupFee->setId(ui->radioSmartFee, 0);
123  ui->groupFee->setId(ui->radioCustomFee, 1);
124  ui->groupFee->button((int)std::max(0, std::min(1, settings.value("nFeeRadio").toInt())))->setChecked(true);
125  ui->customFee->SetAllowEmpty(false);
126  ui->customFee->setValue(settings.value("nTransactionFee").toLongLong());
127  minimizeFeeSection(settings.value("fFeeSectionMinimized").toBool());
128 }
129 
131 {
132  this->clientModel = _clientModel;
133 
134  if (_clientModel) {
136  }
137 }
138 
140 {
141  this->model = _model;
142 
143  if(_model && _model->getOptionsModel())
144  {
145  for(int i = 0; i < ui->entries->count(); ++i)
146  {
147  SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
148  if(entry)
149  {
150  entry->setModel(_model);
151  }
152  }
153 
154  interfaces::WalletBalances balances = _model->wallet().getBalances();
155  setBalance(balances);
159 
160  // Coin Control
163  ui->frameCoinControl->setVisible(_model->getOptionsModel()->getCoinControlFeatures());
165 
166  // fee section
167  for (const int n : confTargets) {
168  ui->confTargetSelector->addItem(tr("%1 (%2 blocks)").arg(GUIUtil::formatNiceTimeOffset(n*Params().GetConsensus().nPowTargetSpacing)).arg(n));
169  }
170  connect(ui->confTargetSelector, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &SendCoinsDialog::updateSmartFeeLabel);
171  connect(ui->confTargetSelector, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &SendCoinsDialog::coinControlUpdateLabels);
172  connect(ui->groupFee, static_cast<void (QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked), this, &SendCoinsDialog::updateFeeSectionControls);
173  connect(ui->groupFee, static_cast<void (QButtonGroup::*)(int)>(&QButtonGroup::buttonClicked), this, &SendCoinsDialog::coinControlUpdateLabels);
175  connect(ui->optInRBF, &QCheckBox::stateChanged, this, &SendCoinsDialog::updateSmartFeeLabel);
176  connect(ui->optInRBF, &QCheckBox::stateChanged, this, &SendCoinsDialog::coinControlUpdateLabels);
177  CAmount requiredFee = model->wallet().getRequiredFee(1000);
178  ui->customFee->SetMinValue(requiredFee);
179  if (ui->customFee->value() < requiredFee) {
180  ui->customFee->setValue(requiredFee);
181  }
182  ui->customFee->setSingleStep(requiredFee);
185 
186  // set default rbf checkbox state
187  ui->optInRBF->setCheckState(Qt::Checked);
188 
189  // set the smartfee-sliders default value (wallets default conf.target or last stored value)
190  QSettings settings;
191  if (settings.value("nSmartFeeSliderPosition").toInt() != 0) {
192  // migrate nSmartFeeSliderPosition to nConfTarget
193  // nConfTarget is available since 0.15 (replaced nSmartFeeSliderPosition)
194  int nConfirmTarget = 25 - settings.value("nSmartFeeSliderPosition").toInt(); // 25 == old slider range
195  settings.setValue("nConfTarget", nConfirmTarget);
196  settings.remove("nSmartFeeSliderPosition");
197  }
198  if (settings.value("nConfTarget").toInt() == 0)
199  ui->confTargetSelector->setCurrentIndex(getIndexForConfTarget(model->wallet().getConfirmTarget()));
200  else
201  ui->confTargetSelector->setCurrentIndex(getIndexForConfTarget(settings.value("nConfTarget").toInt()));
202  }
203 }
204 
206 {
207  QSettings settings;
208  settings.setValue("fFeeSectionMinimized", fFeeMinimized);
209  settings.setValue("nFeeRadio", ui->groupFee->checkedId());
210  settings.setValue("nConfTarget", getConfTargetForIndex(ui->confTargetSelector->currentIndex()));
211  settings.setValue("nTransactionFee", (qint64)ui->customFee->value());
212 
213  delete ui;
214 }
215 
217 {
218  if(!model || !model->getOptionsModel())
219  return;
220 
221  QList<SendCoinsRecipient> recipients;
222  bool valid = true;
223 
224  for(int i = 0; i < ui->entries->count(); ++i)
225  {
226  SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
227  if(entry)
228  {
229  if(entry->validate(model->node()))
230  {
231  recipients.append(entry->getValue());
232  }
233  else
234  {
235  valid = false;
236  }
237  }
238  }
239 
240  if(!valid || recipients.isEmpty())
241  {
242  return;
243  }
244 
245  fNewRecipientAllowed = false;
247  if(!ctx.isValid())
248  {
249  // Unlock wallet was cancelled
250  fNewRecipientAllowed = true;
251  return;
252  }
253 
254  // prepare transaction for getting txFee earlier
255  WalletModelTransaction currentTransaction(recipients);
256  WalletModel::SendCoinsReturn prepareStatus;
257 
258  // Always use a CCoinControl instance, use the CoinControlDialog instance if CoinControl has been enabled
259  CCoinControl ctrl;
262 
264 
265  prepareStatus = model->prepareTransaction(currentTransaction, ctrl);
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;
274  }
275 
276  CAmount txFee = currentTransaction.getTransactionFee();
277 
278  // Format confirmation message
279  QStringList formatted;
280  for (const SendCoinsRecipient &rcp : currentTransaction.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(model->getWalletName()));
286  }
287 
288  // generate address string
289  QString address = rcp.address;
290 
291  QString recipientElement;
292 
293 #ifdef ENABLE_BIP70
294  if (!rcp.paymentRequest.IsInitialized()) // normal payment
295 #endif
296  {
297  if(rcp.label.length() > 0) // label with address
298  {
299  recipientElement.append(tr("%1 to '%2'").arg(amount, rcp.label));
300  recipientElement.append(QString(" (%1)").arg(address));
301  }
302  else // just address
303  {
304  recipientElement.append(tr("%1 to %2").arg(amount, address));
305  }
306  }
307 #ifdef ENABLE_BIP70
308  else if(!rcp.authenticatedMerchant.isEmpty()) // authenticated payment request
309  {
310  recipientElement.append(tr("%1 to '%2'").arg(amount, rcp.authenticatedMerchant));
311  }
312  else // unauthenticated payment request
313  {
314  recipientElement.append(tr("%1 to %2").arg(amount, address));
315  }
316 #endif
317 
318  formatted.append(recipientElement);
319  }
320 
321  QString questionString = tr("Are you sure you want to send?");
322  questionString.append("<br /><span style='font-size:10pt;'>");
323  questionString.append(tr("Please, review your transaction."));
324  questionString.append("</span>%1");
325 
326  if(txFee > 0)
327  {
328  // append fee string if a fee is required
329  questionString.append("<hr /><b>");
330  questionString.append(tr("Transaction fee"));
331  questionString.append("</b>");
332 
333  // append transaction size
334  questionString.append(" (" + QString::number((double)currentTransaction.getTransactionSize() / 1000) + " kB): ");
335 
336  // append transaction fee value
337  questionString.append("<span style='color:#aa0000; font-weight:bold;'>");
338  questionString.append(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), txFee));
339  questionString.append("</span><br />");
340 
341  // append RBF message according to transaction's signalling
342  questionString.append("<span style='font-size:10pt; font-weight:normal;'>");
343  if (ui->optInRBF->isChecked()) {
344  questionString.append(tr("You can increase the fee later (signals Replace-By-Fee, BIP-125)."));
345  } else {
346  questionString.append(tr("Not signalling Replace-By-Fee, BIP-125."));
347  }
348  questionString.append("</span>");
349  }
350 
351  // add total amount in all subdivision units
352  questionString.append("<hr />");
353  CAmount totalAmount = currentTransaction.getTotalTransactionAmount() + txFee;
354  QStringList alternativeUnits;
356  {
357  if(u != model->getOptionsModel()->getDisplayUnit())
358  alternativeUnits.append(BitcoinUnits::formatHtmlWithUnit(u, totalAmount));
359  }
360  questionString.append(QString("<b>%1</b>: <b>%2</b>").arg(tr("Total Amount"))
362  questionString.append(QString("<br /><span style='font-size:10pt; font-weight:normal;'>(=%1)</span>")
363  .arg(alternativeUnits.join(" " + tr("or") + " ")));
364 
365  QString informative_text;
366  QString detailed_text;
367  if (formatted.size() > 1) {
368  questionString = questionString.arg("");
369  informative_text = tr("To review recipient list click \"Show Details...\"");
370  detailed_text = formatted.join("\n\n");
371  } else {
372  questionString = questionString.arg("<br /><br />" + formatted.at(0));
373  }
374 
375  SendConfirmationDialog confirmationDialog(tr("Confirm send coins"), questionString, informative_text, detailed_text, SEND_CONFIRM_DELAY, this);
376  confirmationDialog.exec();
377  QMessageBox::StandardButton retval = static_cast<QMessageBox::StandardButton>(confirmationDialog.result());
378 
379  if(retval != QMessageBox::Yes)
380  {
381  fNewRecipientAllowed = true;
382  return;
383  }
384 
385  // now send the prepared transaction
386  WalletModel::SendCoinsReturn sendStatus = model->sendCoins(currentTransaction);
387  // process sendStatus and on error generate message shown to user
388  processSendCoinsReturn(sendStatus);
389 
390  if (sendStatus.status == WalletModel::OK)
391  {
392  accept();
395  Q_EMIT coinsSent(currentTransaction.getWtx()->GetHash());
396  }
397  fNewRecipientAllowed = true;
398 }
399 
401 {
402  // Clear coin control settings
404  ui->checkBoxCoinControlChange->setChecked(false);
405  ui->lineEditCoinControlChange->clear();
407 
408  // Remove entries until only one left
409  while(ui->entries->count())
410  {
411  ui->entries->takeAt(0)->widget()->deleteLater();
412  }
413  addEntry();
414 
416 }
417 
419 {
420  clear();
421 }
422 
424 {
425  clear();
426 }
427 
429 {
430  SendCoinsEntry *entry = new SendCoinsEntry(platformStyle, this);
431  entry->setModel(model);
432  ui->entries->addWidget(entry);
437 
438  // Focus the field, so that entry can start immediately
439  entry->clear();
440  entry->setFocus();
441  ui->scrollAreaWidgetContents->resize(ui->scrollAreaWidgetContents->sizeHint());
442  qApp->processEvents();
443  QScrollBar* bar = ui->scrollArea->verticalScrollBar();
444  if(bar)
445  bar->setSliderPosition(bar->maximum());
446 
448  return entry;
449 }
450 
452 {
453  setupTabChain(nullptr);
455 }
456 
458 {
459  entry->hide();
460 
461  // If the last entry is about to be removed add an empty one
462  if (ui->entries->count() == 1)
463  addEntry();
464 
465  entry->deleteLater();
466 
468 }
469 
470 QWidget *SendCoinsDialog::setupTabChain(QWidget *prev)
471 {
472  for(int i = 0; i < ui->entries->count(); ++i)
473  {
474  SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
475  if(entry)
476  {
477  prev = entry->setupTabChain(prev);
478  }
479  }
480  QWidget::setTabOrder(prev, ui->sendButton);
481  QWidget::setTabOrder(ui->sendButton, ui->clearButton);
482  QWidget::setTabOrder(ui->clearButton, ui->addButton);
483  return ui->addButton;
484 }
485 
486 void SendCoinsDialog::setAddress(const QString &address)
487 {
488  SendCoinsEntry *entry = nullptr;
489  // Replace the first entry if it is still unused
490  if(ui->entries->count() == 1)
491  {
492  SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
493  if(first->isClear())
494  {
495  entry = first;
496  }
497  }
498  if(!entry)
499  {
500  entry = addEntry();
501  }
502 
503  entry->setAddress(address);
504 }
505 
507 {
509  return;
510 
511  SendCoinsEntry *entry = nullptr;
512  // Replace the first entry if it is still unused
513  if(ui->entries->count() == 1)
514  {
515  SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
516  if(first->isClear())
517  {
518  entry = first;
519  }
520  }
521  if(!entry)
522  {
523  entry = addEntry();
524  }
525 
526  entry->setValue(rv);
528 }
529 
531 {
532  // Just paste the entry, all pre-checks
533  // are done in paymentserver.cpp.
534  pasteEntry(rv);
535  return true;
536 }
537 
539 {
540  if(model && model->getOptionsModel())
541  {
542  ui->labelBalance->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), balances.balance));
543  }
544 }
545 
547 {
549  ui->customFee->setDisplayUnit(model->getOptionsModel()->getDisplayUnit());
551 }
552 
553 void SendCoinsDialog::processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg)
554 {
555  QPair<QString, CClientUIInterface::MessageBoxFlags> msgParams;
556  // Default to a warning message, override if error message is needed
557  msgParams.second = CClientUIInterface::MSG_WARNING;
558 
559  // This comment is specific to SendCoinsDialog usage of WalletModel::SendCoinsReturn.
560  // WalletModel::TransactionCommitFailed is used only in WalletModel::sendCoins()
561  // all others are used only in WalletModel::prepareTransaction()
562  switch(sendCoinsReturn.status)
563  {
565  msgParams.first = tr("The recipient address is not valid. Please recheck.");
566  break;
568  msgParams.first = tr("The amount to pay must be larger than 0.");
569  break;
571  msgParams.first = tr("The amount exceeds your balance.");
572  break;
574  msgParams.first = tr("The total exceeds your balance when the %1 transaction fee is included.").arg(msgArg);
575  break;
577  msgParams.first = tr("Duplicate address found: addresses should only be used once each.");
578  break;
580  msgParams.first = tr("Transaction creation failed!");
581  msgParams.second = CClientUIInterface::MSG_ERROR;
582  break;
584  msgParams.first = tr("The transaction was rejected with the following reason: %1").arg(sendCoinsReturn.reasonCommitFailed);
585  msgParams.second = CClientUIInterface::MSG_ERROR;
586  break;
588  msgParams.first = tr("A fee higher than %1 is considered an absurdly high fee.").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), model->wallet().getDefaultMaxTxFee()));
589  break;
591  msgParams.first = tr("Payment request expired.");
592  msgParams.second = CClientUIInterface::MSG_ERROR;
593  break;
594  // included to prevent a compiler warning.
595  case WalletModel::OK:
596  default:
597  return;
598  }
599 
600  Q_EMIT message(tr("Send Coins"), msgParams.first, msgParams.second);
601 }
602 
604 {
605  ui->labelFeeMinimized->setVisible(fMinimize);
606  ui->buttonChooseFee ->setVisible(fMinimize);
607  ui->buttonMinimizeFee->setVisible(!fMinimize);
608  ui->frameFeeSelection->setVisible(!fMinimize);
609  ui->horizontalLayoutSmartFee->setContentsMargins(0, (fMinimize ? 0 : 6), 0, 0);
610  fFeeMinimized = fMinimize;
611 }
612 
614 {
615  minimizeFeeSection(false);
616 }
617 
619 {
621  minimizeFeeSection(true);
622 }
623 
625 {
626  // Get CCoinControl instance if CoinControl is enabled or create a new one.
627  CCoinControl coin_control;
629  coin_control = *CoinControlDialog::coinControl();
630  }
631 
632  // Calculate available amount to send.
633  CAmount amount = model->wallet().getAvailableBalance(coin_control);
634  for (int i = 0; i < ui->entries->count(); ++i) {
635  SendCoinsEntry* e = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
636  if (e && !e->isHidden() && e != entry) {
637  amount -= e->getValue().amount;
638  }
639  }
640 
641  if (amount > 0) {
643  entry->setAmount(amount);
644  } else {
645  entry->setAmount(0);
646  }
647 }
648 
650 {
651  ui->confTargetSelector ->setEnabled(ui->radioSmartFee->isChecked());
652  ui->labelSmartFee ->setEnabled(ui->radioSmartFee->isChecked());
653  ui->labelSmartFee2 ->setEnabled(ui->radioSmartFee->isChecked());
654  ui->labelSmartFee3 ->setEnabled(ui->radioSmartFee->isChecked());
655  ui->labelFeeEstimation ->setEnabled(ui->radioSmartFee->isChecked());
656  ui->labelCustomFeeWarning ->setEnabled(ui->radioCustomFee->isChecked());
657  ui->labelCustomPerKilobyte ->setEnabled(ui->radioCustomFee->isChecked());
658  ui->customFee ->setEnabled(ui->radioCustomFee->isChecked());
659 }
660 
662 {
663  if(!model || !model->getOptionsModel())
664  return;
665 
666  if (ui->radioSmartFee->isChecked())
667  ui->labelFeeMinimized->setText(ui->labelSmartFee->text());
668  else {
669  ui->labelFeeMinimized->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), ui->customFee->value()) + "/kB");
670  }
671 }
672 
674 {
675  if (ui->radioCustomFee->isChecked()) {
676  ctrl.m_feerate = CFeeRate(ui->customFee->value());
677  } else {
678  ctrl.m_feerate.reset();
679  }
680  // Avoid using global defaults when sending money from the GUI
681  // Either custom fee will be used or if not selected, the confirmation target from dropdown box
682  ctrl.m_confirm_target = getConfTargetForIndex(ui->confTargetSelector->currentIndex());
683  ctrl.m_signal_bip125_rbf = ui->optInRBF->isChecked();
684 }
685 
687 {
688  if(!model || !model->getOptionsModel())
689  return;
690  CCoinControl coin_control;
691  updateCoinControlState(coin_control);
692  coin_control.m_feerate.reset(); // Explicitly use only fee estimation rate for smart fee labels
693  int returned_target;
694  FeeReason reason;
695  CFeeRate feeRate = CFeeRate(model->wallet().getMinimumFee(1000, coin_control, &returned_target, &reason));
696 
697  ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), feeRate.GetFeePerK()) + "/kB");
698 
699  if (reason == FeeReason::FALLBACK) {
700  ui->labelSmartFee2->show(); // (Smart fee not initialized yet. This usually takes a few blocks...)
701  ui->labelFeeEstimation->setText("");
702  ui->fallbackFeeWarningLabel->setVisible(true);
703  int lightness = ui->fallbackFeeWarningLabel->palette().color(QPalette::WindowText).lightness();
704  QColor warning_colour(255 - (lightness / 5), 176 - (lightness / 3), 48 - (lightness / 14));
705  ui->fallbackFeeWarningLabel->setStyleSheet("QLabel { color: " + warning_colour.name() + "; }");
706  ui->fallbackFeeWarningLabel->setIndent(QFontMetrics(ui->fallbackFeeWarningLabel->font()).width("x"));
707  }
708  else
709  {
710  ui->labelSmartFee2->hide();
711  ui->labelFeeEstimation->setText(tr("Estimated to begin confirmation within %n block(s).", "", returned_target));
712  ui->fallbackFeeWarningLabel->setVisible(false);
713  }
714 
716 }
717 
718 // Coin Control: copy label "Quantity" to clipboard
720 {
721  GUIUtil::setClipboard(ui->labelCoinControlQuantity->text());
722 }
723 
724 // Coin Control: copy label "Amount" to clipboard
726 {
727  GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left(ui->labelCoinControlAmount->text().indexOf(" ")));
728 }
729 
730 // Coin Control: copy label "Fee" to clipboard
732 {
733  GUIUtil::setClipboard(ui->labelCoinControlFee->text().left(ui->labelCoinControlFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
734 }
735 
736 // Coin Control: copy label "After fee" to clipboard
738 {
739  GUIUtil::setClipboard(ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
740 }
741 
742 // Coin Control: copy label "Bytes" to clipboard
744 {
745  GUIUtil::setClipboard(ui->labelCoinControlBytes->text().replace(ASYMP_UTF8, ""));
746 }
747 
748 // Coin Control: copy label "Dust" to clipboard
750 {
751  GUIUtil::setClipboard(ui->labelCoinControlLowOutput->text());
752 }
753 
754 // Coin Control: copy label "Change" to clipboard
756 {
757  GUIUtil::setClipboard(ui->labelCoinControlChange->text().left(ui->labelCoinControlChange->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
758 }
759 
760 // Coin Control: settings menu - coin control enabled/disabled by user
762 {
763  ui->frameCoinControl->setVisible(checked);
764 
765  if (!checked && model) // coin control features disabled
767 
769 }
770 
771 // Coin Control: button inputs -> show actual coin control dialog
773 {
775  dlg.setModel(model);
776  dlg.exec();
778 }
779 
780 // Coin Control: checkbox custom change address
782 {
783  if (state == Qt::Unchecked)
784  {
786  ui->labelCoinControlChangeLabel->clear();
787  }
788  else
789  // use this to re-validate an already entered address
790  coinControlChangeEdited(ui->lineEditCoinControlChange->text());
791 
792  ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked));
793 }
794 
795 // Coin Control: custom change address changed
797 {
798  if (model && model->getAddressTableModel())
799  {
800  // Default to no change address until verified
802  ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:red;}");
803 
804  const CTxDestination dest = DecodeDestination(text.toStdString());
805 
806  if (text.isEmpty()) // Nothing entered
807  {
808  ui->labelCoinControlChangeLabel->setText("");
809  }
810  else if (!IsValidDestination(dest)) // Invalid address
811  {
812  ui->labelCoinControlChangeLabel->setText(tr("Warning: Invalid Bitcoin address"));
813  }
814  else // Valid address
815  {
816  if (!model->wallet().isSpendable(dest)) {
817  ui->labelCoinControlChangeLabel->setText(tr("Warning: Unknown change address"));
818 
819  // confirmation dialog
820  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?"),
821  QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel);
822 
823  if(btnRetVal == QMessageBox::Yes)
825  else
826  {
827  ui->lineEditCoinControlChange->setText("");
828  ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
829  ui->labelCoinControlChangeLabel->setText("");
830  }
831  }
832  else // Known change address
833  {
834  ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
835 
836  // Query label
837  QString associatedLabel = model->getAddressTableModel()->labelForAddress(text);
838  if (!associatedLabel.isEmpty())
839  ui->labelCoinControlChangeLabel->setText(associatedLabel);
840  else
841  ui->labelCoinControlChangeLabel->setText(tr("(no label)"));
842 
844  }
845  }
846  }
847 }
848 
849 // Coin Control: update labels
851 {
852  if (!model || !model->getOptionsModel())
853  return;
854 
856 
857  // set pay amounts
860 
861  for(int i = 0; i < ui->entries->count(); ++i)
862  {
863  SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
864  if(entry && !entry->isHidden())
865  {
866  SendCoinsRecipient rcp = entry->getValue();
868  if (rcp.fSubtractFeeFromAmount)
870  }
871  }
872 
873  if (CoinControlDialog::coinControl()->HasSelected())
874  {
875  // actual coin control calculation
877 
878  // show coin control stats
879  ui->labelCoinControlAutomaticallySelected->hide();
880  ui->widgetCoinControl->show();
881  }
882  else
883  {
884  // hide coin control stats
885  ui->labelCoinControlAutomaticallySelected->show();
886  ui->widgetCoinControl->hide();
887  ui->labelCoinControlInsuffFunds->hide();
888  }
889 }
890 
891 SendConfirmationDialog::SendConfirmationDialog(const QString& title, const QString& text, const QString& informative_text, const QString& detailed_text, int _secDelay, QWidget* parent)
892  : QMessageBox(parent), secDelay(_secDelay)
893 {
894  setIcon(QMessageBox::Question);
895  setWindowTitle(title); // On macOS, the window title is ignored (as required by the macOS Guidelines).
896  setText(text);
897  setInformativeText(informative_text);
898  setDetailedText(detailed_text);
899  setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel);
900  setDefaultButton(QMessageBox::Cancel);
901  yesButton = button(QMessageBox::Yes);
902  updateYesButton();
903  connect(&countDownTimer, &QTimer::timeout, this, &SendConfirmationDialog::countDown);
904 }
905 
907 {
908  updateYesButton();
909  countDownTimer.start(1000);
910  return QMessageBox::exec();
911 }
912 
914 {
915  secDelay--;
916  updateYesButton();
917 
918  if(secDelay <= 0)
919  {
920  countDownTimer.stop();
921  }
922 }
923 
925 {
926  if(secDelay > 0)
927  {
928  yesButton->setEnabled(false);
929  yesButton->setText(tr("Yes") + " (" + QString::number(secDelay) + ")");
930  }
931  else
932  {
933  yesButton->setEnabled(true);
934  yesButton->setText(tr("Yes"));
935  }
936 }
void removeEntry(SendCoinsEntry *entry)
Unit
Bitcoin units.
Definition: bitcoinunits.h:57
interfaces::Wallet & wallet() const
Definition: walletmodel.h:223
void setValue(const SendCoinsRecipient &value)
void payAmountChanged()
void updateFeeMinimizedLabel()
constexpr CAmount DEFAULT_PAY_TX_FEE
-paytxfee default
Definition: wallet.h:64
boost::optional< unsigned int > m_confirm_target
Override the default confirmation target if set.
Definition: coincontrol.h:35
void setFocus()
void on_buttonChooseFee_clicked()
SendCoinsRecipient getValue()
static const std::array< int, 9 > confTargets
UnlockContext requestUnlock()
void coinControlClipboardQuantity()
static QString formatHtmlWithUnit(int unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=separatorStandard)
Format as HTML string (with unit)
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:325
#define SEND_CONFIRM_DELAY
SendCoinsReturn sendCoins(WalletModelTransaction &transaction)
QList< SendCoinsRecipient > getRecipients() const
void coinControlFeaturesChanged(bool)
void updateCoinControlState(CCoinControl &ctrl)
QIcon SingleColorIcon(const QString &filename) const
Colorize an icon (given filename) with the icon color.
bool handlePaymentRequest(const SendCoinsRecipient &recipient)
AddressTableModel * getAddressTableModel()
#define ASYMP_UTF8
virtual bool isSpendable(const CTxDestination &dest)=0
Return whether wallet has private key.
A single entry in the dialog for sending bitcoins.
Coin Control Features.
Definition: coincontrol.h:19
boost::optional< CFeeRate > m_feerate
Override the wallet&#39;s m_pay_tx_fee if set.
Definition: coincontrol.h:33
int getDisplayUnit() const
Definition: optionsmodel.h:74
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://...
int64_t CAmount
Amount in satoshis (Can be negative)
Definition: amount.h:12
SendConfirmationDialog(const QString &title, const QString &text, const QString &informative_text="", const QString &detailed_text="", int secDelay=SEND_CONFIRM_DELAY, QWidget *parent=nullptr)
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
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)
void setAddress(const QString &address)
void coinControlClipboardChange()
Collection of wallet balances.
Definition: wallet.h:311
void useAvailableBalance(SendCoinsEntry *entry)
void setClientModel(ClientModel *clientModel)
void setClipboard(const QString &str)
Definition: guiutil.cpp:785
ClientModel * clientModel
void SetNull()
Definition: coincontrol.cpp:9
static secp256k1_context * ctx
Definition: tests.c:46
CTxDestination destChange
Custom change destination, if not set an address is generated.
Definition: coincontrol.h:23
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.
void coinControlChangeEdited(const QString &)
static void updateLabels(WalletModel *, QDialog *)
FeeReason
Definition: fees.h:36
void numBlocksChanged(int count, const QDateTime &blockDate, double nVerificationProgress, bool header)
QString getWalletName() const
interfaces::Node & node() const
Definition: walletmodel.h:222
void removeEntry(SendCoinsEntry *entry)
void displayUnitChanged(int unit)
Model for Bitcoin network client.
Definition: clientmodel.h:44
void coinControlUpdateLabels()
boost::optional< bool > m_signal_bip125_rbf
Override the wallet&#39;s m_signal_rbf if set.
Definition: coincontrol.h:37
void UnSelectAll()
Definition: coincontrol.h:76
void setModel(WalletModel *model)
int getConfTargetForIndex(int index)
SendCoinsDialog(const PlatformStyle *platformStyle, QWidget *parent=nullptr)
bool getCoinControlFeatures() const
Definition: optionsmodel.h:77
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:216
void subtractFeeFromAmountChanged()
void setModel(WalletModel *model)
static bool fSubtractFeeFromAmount
int getIndexForConfTarget(int target)
const CChainParams & Params()
Return the currently selected parameters.
Interface to Bitcoin wallet from Qt view code.
Definition: walletmodel.h:125
void setAmount(const CAmount &amount)
void setModel(WalletModel *model)
void processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg=QString())
Fee rate in satoshis per kilobyte: CAmount / kB.
Definition: feerate.h:19
Data model for a walletmodel transaction.
void coinControlClipboardBytes()
void useAvailableBalance(SendCoinsEntry *entry)
virtual unsigned int getConfirmTarget()=0
Get tx confirm target.
bool fSubtractFeeFromAmount
Definition: walletmodel.h:83
const PlatformStyle * platformStyle
CAmount getTotalTransactionAmount() const
QString formatNiceTimeOffset(qint64 secs)
Definition: guiutil.cpp:866
static QString formatWithUnit(int unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=separatorStandard)
Format as string (with unit)
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:139
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:41
void balanceChanged(const interfaces::WalletBalances &balances)
bool getImagesOnButtons() const
Definition: platformstyle.h:21
void coinControlButtonClicked()
void coinControlClipboardFee()
OptionsModel * getOptionsModel()
void coinControlChangeChecked(int)
static CCoinControl * coinControl()