Dash Core Source Documentation (0.16.0.1)

Find detailed information regarding the Dash Core source code.

sendcoinsdialog.cpp
Go to the documentation of this file.
1 // Copyright (c) 2011-2015 The Bitcoin Core developers
2 // Copyright (c) 2014-2020 The Dash Core developers
3 // Distributed under the MIT software license, see the accompanying
4 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
5 
6 #include <qt/sendcoinsdialog.h>
7 #include <qt/forms/ui_sendcoinsdialog.h>
8 
9 #include <qt/addresstablemodel.h>
10 #include <qt/bitcoinunits.h>
11 #include <qt/clientmodel.h>
12 #include <qt/coincontroldialog.h>
13 #include <qt/guiutil.h>
14 #include <qt/optionsmodel.h>
15 #include <qt/sendcoinsentry.h>
16 
17 #include <base58.h>
18 #include <wallet/coincontrol.h>
19 #include <validation.h> // mempool and minRelayTxFee
20 #include <ui_interface.h>
21 #include <txmempool.h>
22 #include <policy/fees.h>
23 #include <wallet/fees.h>
24 
27 
28 #include <QFontMetrics>
29 #include <QScrollBar>
30 #include <QSettings>
31 #include <QTextDocument>
32 
33 #define SEND_CONFIRM_DELAY 3
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(bool _fPrivateSend, QWidget* parent) :
55  QDialog(parent),
56  ui(new Ui::SendCoinsDialog),
57  clientModel(0),
58  model(0),
59  fNewRecipientAllowed(true),
60  fFeeMinimized(true),
61  fPrivateSend(_fPrivateSend)
62 {
63  ui->setupUi(this);
64 
65  GUIUtil::setFont({ui->labelCoinControlFeatures,
66  ui->labelCoinControlInsuffFunds,
67  ui->labelCoinControlQuantityText,
68  ui->labelCoinControlBytesText,
69  ui->labelCoinControlAmountText,
70  ui->labelCoinControlLowOutputText,
71  ui->labelCoinControlFeeText,
72  ui->labelCoinControlAfterFeeText,
73  ui->labelCoinControlChangeText,
74  ui->labelFeeHeadline,
75  ui->fallbackFeeWarningLabel
77 
78  GUIUtil::setFont({ui->labelBalance,
79  ui->labelBalanceText
81 
82  GUIUtil::setFont({ui->labelCoinControlFeatures
84 
85  GUIUtil::setupAddressWidget(ui->lineEditCoinControlChange, this);
86 
87  addEntry();
88 
90 
91  connect(ui->addButton, SIGNAL(clicked()), this, SLOT(addEntry()));
92  connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear()));
93 
94  // Coin Control
95  connect(ui->pushButtonCoinControl, SIGNAL(clicked()), this, SLOT(coinControlButtonClicked()));
96  connect(ui->checkBoxCoinControlChange, SIGNAL(stateChanged(int)), this, SLOT(coinControlChangeChecked(int)));
97  connect(ui->lineEditCoinControlChange, SIGNAL(textEdited(const QString &)), this, SLOT(coinControlChangeEdited(const QString &)));
98 
99  // Dash specific
100  QSettings settings;
101  //TODO remove Darksend sometime after 0.14.1
102  if (settings.contains("bUseDarkSend")) {
103  settings.setValue("bUsePrivateSend", settings.value("bUseDarkSend").toBool());
104  settings.remove("bUseDarkSend");
105  }
106  if (!settings.contains("bUsePrivateSend"))
107  settings.setValue("bUsePrivateSend", false);
108 
109  //TODO remove InstantX sometime after 0.14.1
110  if (settings.contains("bUseInstantX")) {
111  settings.remove("bUseInstantX");
112  }
113  if (settings.contains("bUseInstantSend")) {
114  settings.remove("bUseInstantSend");
115  }
116 
117  // Coin Control: clipboard actions
118  QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this);
119  QAction *clipboardAmountAction = new QAction(tr("Copy amount"), this);
120  QAction *clipboardFeeAction = new QAction(tr("Copy fee"), this);
121  QAction *clipboardAfterFeeAction = new QAction(tr("Copy after fee"), this);
122  QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this);
123  QAction *clipboardLowOutputAction = new QAction(tr("Copy dust"), this);
124  QAction *clipboardChangeAction = new QAction(tr("Copy change"), this);
125  connect(clipboardQuantityAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardQuantity()));
126  connect(clipboardAmountAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardAmount()));
127  connect(clipboardFeeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardFee()));
128  connect(clipboardAfterFeeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardAfterFee()));
129  connect(clipboardBytesAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardBytes()));
130  connect(clipboardLowOutputAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardLowOutput()));
131  connect(clipboardChangeAction, SIGNAL(triggered()), this, SLOT(coinControlClipboardChange()));
132  ui->labelCoinControlQuantity->addAction(clipboardQuantityAction);
133  ui->labelCoinControlAmount->addAction(clipboardAmountAction);
134  ui->labelCoinControlFee->addAction(clipboardFeeAction);
135  ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction);
136  ui->labelCoinControlBytes->addAction(clipboardBytesAction);
137  ui->labelCoinControlLowOutput->addAction(clipboardLowOutputAction);
138  ui->labelCoinControlChange->addAction(clipboardChangeAction);
139 
140  // init transaction fee section
141  if (!settings.contains("fFeeSectionMinimized"))
142  settings.setValue("fFeeSectionMinimized", true);
143  if (!settings.contains("nFeeRadio") && settings.contains("nTransactionFee") && settings.value("nTransactionFee").toLongLong() > 0) // compatibility
144  settings.setValue("nFeeRadio", 1); // custom
145  if (!settings.contains("nFeeRadio"))
146  settings.setValue("nFeeRadio", 0); // recommended
147  if (!settings.contains("nSmartFeeSliderPosition"))
148  settings.setValue("nSmartFeeSliderPosition", 0);
149  if (!settings.contains("nTransactionFee"))
150  settings.setValue("nTransactionFee", (qint64)DEFAULT_TRANSACTION_FEE);
151  if (!settings.contains("fPayOnlyMinFee"))
152  settings.setValue("fPayOnlyMinFee", false);
153 
154  ui->groupFee->setId(ui->radioSmartFee, 0);
155  ui->groupFee->setId(ui->radioCustomFee, 1);
156  ui->groupFee->button((int)std::max(0, std::min(1, settings.value("nFeeRadio").toInt())))->setChecked(true);
157  ui->customFee->setValue(settings.value("nTransactionFee").toLongLong());
158  ui->checkBoxMinimumFee->setChecked(settings.value("fPayOnlyMinFee").toBool());
159  minimizeFeeSection(settings.value("fFeeSectionMinimized").toBool());
160 
161  if (fPrivateSend) {
162  ui->sendButton->setText("PrivateS&end");
163  ui->sendButton->setToolTip(tr("Confirm the PrivateSend action"));
164  } else {
165  ui->sendButton->setText(tr("S&end"));
166  ui->sendButton->setToolTip(tr("Confirm the send action"));
167  }
168 }
169 
171 {
172  this->clientModel = _clientModel;
173 
174  if (_clientModel) {
175  connect(_clientModel, SIGNAL(numBlocksChanged(int,QDateTime,QString,double,bool)), this, SLOT(updateSmartFeeLabel()));
176  }
177 }
178 
180 {
181  this->model = _model;
182 
183  if(_model && _model->getOptionsModel())
184  {
185  for(int i = 0; i < ui->entries->count(); ++i)
186  {
187  SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
188  if(entry)
189  {
190  entry->setModel(_model);
191  }
192  }
193 
194  setBalance(_model->getBalance(), _model->getUnconfirmedBalance(), _model->getImmatureBalance(), _model->getAnonymizedBalance(),
195  _model->getWatchBalance(), _model->getWatchUnconfirmedBalance(), _model->getWatchImmatureBalance());
196  connect(_model, SIGNAL(balanceChanged(CAmount,CAmount,CAmount,CAmount,CAmount,CAmount,CAmount)), this, SLOT(setBalance(CAmount,CAmount,CAmount,CAmount,CAmount,CAmount,CAmount)));
197  connect(_model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit()));
199 
200  // Coin Control
201  connect(_model->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(coinControlUpdateLabels()));
202  connect(_model->getOptionsModel(), SIGNAL(coinControlFeaturesChanged(bool)), this, SLOT(coinControlFeatureChanged(bool)));
203  ui->frameCoinControl->setVisible(_model->getOptionsModel()->getCoinControlFeatures());
205 
206  // fee section
207  for (const int n : confTargets) {
208  ui->confTargetSelector->addItem(tr("%1 (%2 blocks)").arg(GUIUtil::formatNiceTimeOffset(n*Params().GetConsensus().nPowTargetSpacing)).arg(n));
209  }
210  connect(ui->confTargetSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(updateSmartFeeLabel()));
211  connect(ui->confTargetSelector, SIGNAL(currentIndexChanged(int)), this, SLOT(coinControlUpdateLabels()));
212  connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(updateFeeSectionControls()));
213  connect(ui->groupFee, SIGNAL(buttonClicked(int)), this, SLOT(coinControlUpdateLabels()));
214  connect(ui->customFee, SIGNAL(valueChanged()), this, SLOT(coinControlUpdateLabels()));
215  connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(setMinimumFee()));
216  connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(updateFeeSectionControls()));
217  connect(ui->checkBoxMinimumFee, SIGNAL(stateChanged(int)), this, SLOT(coinControlUpdateLabels()));
218 
222 
223  // set the smartfee-sliders default value (wallets default conf.target or last stored value)
224  QSettings settings;
225  if (settings.value("nSmartFeeSliderPosition").toInt() != 0) {
226  // migrate nSmartFeeSliderPosition to nConfTarget
227  // nConfTarget is available since 0.15 (replaced nSmartFeeSliderPosition)
228  int nConfirmTarget = 25 - settings.value("nSmartFeeSliderPosition").toInt(); // 25 == old slider range
229  settings.setValue("nConfTarget", nConfirmTarget);
230  settings.remove("nSmartFeeSliderPosition");
231  }
232  if (settings.value("nConfTarget").toInt() == 0)
233  ui->confTargetSelector->setCurrentIndex(getIndexForConfTarget(model->getDefaultConfirmTarget()));
234  else
235  ui->confTargetSelector->setCurrentIndex(getIndexForConfTarget(settings.value("nConfTarget").toInt()));
236  }
237 }
238 
240 {
241  QSettings settings;
242  settings.setValue("fFeeSectionMinimized", fFeeMinimized);
243  settings.setValue("nFeeRadio", ui->groupFee->checkedId());
244  settings.setValue("nConfTarget", getConfTargetForIndex(ui->confTargetSelector->currentIndex()));
245  settings.setValue("nTransactionFee", (qint64)ui->customFee->value());
246  settings.setValue("fPayOnlyMinFee", ui->checkBoxMinimumFee->isChecked());
247 
248  delete ui;
249 }
250 
252 {
253  if(!model || !model->getOptionsModel())
254  return;
255 
256  QList<SendCoinsRecipient> recipients;
257  bool valid = true;
258 
259  for(int i = 0; i < ui->entries->count(); ++i)
260  {
261  SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
262  if(entry)
263  {
264  if(entry->validate())
265  {
266  recipients.append(entry->getValue());
267  }
268  else
269  {
270  valid = false;
271  }
272  }
273  }
274 
275  if(!valid || recipients.isEmpty())
276  {
277  return;
278  }
279 
280  fNewRecipientAllowed = false;
281  // request unlock only if was locked or unlocked for mixing:
282  // this way we let users unlock by walletpassphrase or by menu
283  // and make many transactions while unlocking through this dialog
284  // will call relock
286  if(encStatus == model->Locked || encStatus == model->UnlockedForMixingOnly)
287  {
289  if(!ctx.isValid())
290  {
291  // Unlock wallet was cancelled
292  fNewRecipientAllowed = true;
293  return;
294  }
295  send(recipients);
296  return;
297  }
298  // already unlocked or not encrypted at all
299  send(recipients);
300 }
301 
302 void SendCoinsDialog::send(QList<SendCoinsRecipient> recipients)
303 {
304  // prepare transaction for getting txFee earlier
305  WalletModelTransaction currentTransaction(recipients);
306  WalletModel::SendCoinsReturn prepareStatus;
307 
308  // Always use a CCoinControl instance, use the CoinControlDialog instance if CoinControl has been enabled
309  CCoinControl ctrl;
312 
314 
316 
317  prepareStatus = model->prepareTransaction(currentTransaction, ctrl);
318 
319  // process prepareStatus and on error generate message shown to user
320  processSendCoinsReturn(prepareStatus,
322 
323  if(prepareStatus.status != WalletModel::OK) {
324  fNewRecipientAllowed = true;
325  return;
326  }
327 
328  // Format confirmation message
329  QStringList formatted;
330  for (const SendCoinsRecipient &rcp : currentTransaction.getRecipients())
331  {
332  // generate bold amount string
333  QString amount = "<b>" + BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
334  amount.append("</b> ");
335 
336  // generate monospace address string
337  QString address = "<span style='font-family: monospace;'>" + rcp.address;
338  address.append("</span>");
339 
340  QString recipientElement;
341 
342  if (!rcp.paymentRequest.IsInitialized()) // normal payment
343  {
344  if(rcp.label.length() > 0) // label with address
345  {
346  recipientElement = tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.label));
347  recipientElement.append(QString(" (%1)").arg(address));
348  }
349  else // just address
350  {
351  recipientElement = tr("%1 to %2").arg(amount, address);
352  }
353  }
354  else if(!rcp.authenticatedMerchant.isEmpty()) // authenticated payment request
355  {
356  recipientElement = tr("%1 to %2").arg(amount, GUIUtil::HtmlEscape(rcp.authenticatedMerchant));
357  }
358  else // unauthenticated payment request
359  {
360  recipientElement = tr("%1 to %2").arg(amount, address);
361  }
362 
363  formatted.append(recipientElement);
364  }
365 
366  // Limit number of displayed entries
367  int messageEntries = formatted.size();
368  int displayedEntries = 0;
369  for(int i = 0; i < formatted.size(); i++){
370  if(i >= MAX_SEND_POPUP_ENTRIES){
371  formatted.removeLast();
372  i--;
373  }
374  else{
375  displayedEntries = i+1;
376  }
377  }
378 
379  QString questionString = tr("Are you sure you want to send?");
380  questionString.append("<br /><br />");
381  questionString.append(formatted.join("<br />"));
382  questionString.append("<br />");
383 
384 
385  if(ctrl.IsUsingPrivateSend()) {
386  questionString.append(tr("using") + " <b>" + tr("PrivateSend funds only") + "</b>");
387  } else {
388  questionString.append(tr("using") + " <b>" + tr("any available funds") + "</b>");
389  }
390 
391  if (displayedEntries < messageEntries) {
392  questionString.append("<br />");
393  questionString.append("<span style='" + GUIUtil::getThemedStyleQString(GUIUtil::ThemedStyle::TS_ERROR) + "'>");
394  questionString.append(tr("<b>(%1 of %2 entries displayed)</b>").arg(displayedEntries).arg(messageEntries));
395  questionString.append("</span>");
396  }
397 
398  CAmount txFee = currentTransaction.getTransactionFee();
399 
400  if(txFee > 0)
401  {
402  // append fee string if a fee is required
403  questionString.append("<hr /><span style='" + GUIUtil::getThemedStyleQString(GUIUtil::ThemedStyle::TS_ERROR) + "'>");
404  questionString.append(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), txFee));
405  questionString.append("</span> ");
406  questionString.append(tr("are added as transaction fee"));
407 
408  if (ctrl.IsUsingPrivateSend()) {
409  questionString.append(" " + tr("(PrivateSend transactions have higher fees usually due to no change output being allowed)"));
410  }
411  }
412 
413  // Show some additioinal information
414  questionString.append("<hr />");
415  // append transaction size
416  questionString.append(tr("Transaction size: %1").arg(QString::number((double)currentTransaction.getTransactionSize() / 1000)) + " kB");
417  questionString.append("<br />");
418  CFeeRate feeRate(txFee, currentTransaction.getTransactionSize());
419  questionString.append(tr("Fee rate: %1").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), feeRate.GetFeePerK())) + "/kB");
420 
421  if (ctrl.IsUsingPrivateSend()) {
422  // append number of inputs
423  questionString.append("<hr />");
424  int nInputs = currentTransaction.getTransaction()->tx->vin.size();
425  questionString.append(tr("This transaction will consume %n input(s)", "", nInputs));
426 
427  // warn about potential privacy issues when spending too many inputs at once
428  if (nInputs >= 10 && ctrl.IsUsingPrivateSend()) {
429  questionString.append("<br />");
430  questionString.append("<span style='" + GUIUtil::getThemedStyleQString(GUIUtil::ThemedStyle::TS_ERROR) + "'>");
431  questionString.append(tr("Warning: Using PrivateSend with %1 or more inputs can harm your privacy and is not recommended").arg(10));
432  questionString.append("</span> ");
433  }
434  }
435 
436  // add total amount in all subdivision units
437  questionString.append("<hr />");
438  CAmount totalAmount = currentTransaction.getTotalTransactionAmount() + txFee;
439  QStringList alternativeUnits;
441  {
442  if(u != model->getOptionsModel()->getDisplayUnit())
443  alternativeUnits.append(BitcoinUnits::formatHtmlWithUnit(u, totalAmount));
444  }
445 
446  // Show total amount + all alternative units
447  questionString.append(tr("Total Amount = <b>%1</b><br />= %2")
449  .arg(alternativeUnits.join("<br />= ")));
450 
451  // Display message box
452  SendConfirmationDialog confirmationDialog(tr("Confirm send coins"),
453  questionString, SEND_CONFIRM_DELAY, this);
454  confirmationDialog.exec();
455  QMessageBox::StandardButton retval = (QMessageBox::StandardButton)confirmationDialog.result();
456 
457  if(retval != QMessageBox::Yes)
458  {
459  fNewRecipientAllowed = true;
460  return;
461  }
462 
463  // now send the prepared transaction
464  WalletModel::SendCoinsReturn sendStatus = model->sendCoins(currentTransaction);
465  // process sendStatus and on error generate message shown to user
466  processSendCoinsReturn(sendStatus);
467 
468  if (sendStatus.status == WalletModel::OK)
469  {
470  accept();
473  }
474  fNewRecipientAllowed = true;
475 }
476 
478 {
479  // Clear coin control settings
481  ui->checkBoxCoinControlChange->setChecked(false);
482  ui->lineEditCoinControlChange->clear();
484 
485  // Remove entries until only one left
486  while(ui->entries->count())
487  {
488  ui->entries->takeAt(0)->widget()->deleteLater();
489  }
490  addEntry();
491 
493 }
494 
496 {
497  clear();
498 }
499 
501 {
502  clear();
503 }
504 
506 {
507  SendCoinsEntry* entry = new SendCoinsEntry(this);
508  entry->setModel(model);
509  ui->entries->addWidget(entry);
510  connect(entry, SIGNAL(removeEntry(SendCoinsEntry*)), this, SLOT(removeEntry(SendCoinsEntry*)));
511  connect(entry, SIGNAL(useAvailableBalance(SendCoinsEntry*)), this, SLOT(useAvailableBalance(SendCoinsEntry*)));
512  connect(entry, SIGNAL(payAmountChanged()), this, SLOT(coinControlUpdateLabels()));
513  connect(entry, SIGNAL(subtractFeeFromAmountChanged()), this, SLOT(coinControlUpdateLabels()));
514 
515  // Focus the field, so that entry can start immediately
516  entry->clear();
517  entry->setFocus();
518  ui->scrollAreaWidgetContents->resize(ui->scrollAreaWidgetContents->sizeHint());
519  qApp->processEvents();
520  QScrollBar* bar = ui->scrollArea->verticalScrollBar();
521  if(bar)
522  bar->setSliderPosition(bar->maximum());
523 
525  return entry;
526 }
527 
529 {
530  setupTabChain(0);
532 }
533 
535 {
536  entry->hide();
537 
538  // If the last entry is about to be removed add an empty one
539  if (ui->entries->count() == 1)
540  addEntry();
541 
542  entry->deleteLater();
543 
545 }
546 
547 QWidget *SendCoinsDialog::setupTabChain(QWidget *prev)
548 {
549  for(int i = 0; i < ui->entries->count(); ++i)
550  {
551  SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
552  if(entry)
553  {
554  prev = entry->setupTabChain(prev);
555  }
556  }
557  QWidget::setTabOrder(prev, ui->sendButton);
558  QWidget::setTabOrder(ui->sendButton, ui->clearButton);
559  QWidget::setTabOrder(ui->clearButton, ui->addButton);
560  return ui->addButton;
561 }
562 
563 void SendCoinsDialog::setAddress(const QString &address)
564 {
565  SendCoinsEntry *entry = 0;
566  // Replace the first entry if it is still unused
567  if(ui->entries->count() == 1)
568  {
569  SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
570  if(first->isClear())
571  {
572  entry = first;
573  }
574  }
575  if(!entry)
576  {
577  entry = addEntry();
578  }
579 
580  entry->setAddress(address);
581 }
582 
584 {
586  return;
587 
588  SendCoinsEntry *entry = 0;
589  // Replace the first entry if it is still unused
590  if(ui->entries->count() == 1)
591  {
592  SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
593  if(first->isClear())
594  {
595  entry = first;
596  }
597  }
598  if(!entry)
599  {
600  entry = addEntry();
601  }
602 
603  entry->setValue(rv);
605 }
606 
608 {
609  // Just paste the entry, all pre-checks
610  // are done in paymentserver.cpp.
611  pasteEntry(rv);
612  return true;
613 }
614 
615 void SendCoinsDialog::setBalance(const CAmount& balance, const CAmount& unconfirmedBalance, const CAmount& immatureBalance, const CAmount& anonymizedBalance,
616  const CAmount& watchBalance, const CAmount& watchUnconfirmedBalance, const CAmount& watchImmatureBalance)
617 {
618  Q_UNUSED(unconfirmedBalance);
619  Q_UNUSED(immatureBalance);
620  Q_UNUSED(anonymizedBalance);
621  Q_UNUSED(watchBalance);
622  Q_UNUSED(watchUnconfirmedBalance);
623  Q_UNUSED(watchImmatureBalance);
624 
625  if(model && model->getOptionsModel())
626  {
627  uint64_t bal = 0;
628  if (fPrivateSend) {
629  bal = anonymizedBalance;
630  } else {
631  bal = balance;
632  }
633 
634  ui->labelBalance->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), bal));
635  }
636 }
637 
639 {
643  ui->customFee->setDisplayUnit(model->getOptionsModel()->getDisplayUnit());
646 }
647 
648 void SendCoinsDialog::processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg)
649 {
650  QPair<QString, CClientUIInterface::MessageBoxFlags> msgParams;
651  // Default to a warning message, override if error message is needed
652  msgParams.second = CClientUIInterface::MSG_WARNING;
653 
654  // This comment is specific to SendCoinsDialog usage of WalletModel::SendCoinsReturn.
655  // WalletModel::TransactionCommitFailed is used only in WalletModel::sendCoins()
656  // all others are used only in WalletModel::prepareTransaction()
657  switch(sendCoinsReturn.status)
658  {
660  msgParams.first = tr("The recipient address is not valid. Please recheck.");
661  break;
663  msgParams.first = tr("The amount to pay must be larger than 0.");
664  break;
666  msgParams.first = tr("The amount exceeds your balance.");
667  break;
669  msgParams.first = tr("The total exceeds your balance when the %1 transaction fee is included.").arg(msgArg);
670  break;
672  msgParams.first = tr("Duplicate address found: addresses should only be used once each.");
673  break;
675  msgParams.first = tr("Transaction creation failed!");
676  msgParams.second = CClientUIInterface::MSG_ERROR;
677  break;
679  msgParams.first = tr("The transaction was rejected with the following reason: %1").arg(sendCoinsReturn.reasonCommitFailed);
680  msgParams.second = CClientUIInterface::MSG_ERROR;
681  break;
683  msgParams.first = tr("A fee higher than %1 is considered an absurdly high fee.").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), maxTxFee));
684  break;
686  msgParams.first = tr("Payment request expired.");
687  msgParams.second = CClientUIInterface::MSG_ERROR;
688  break;
689  // included to prevent a compiler warning.
690  case WalletModel::OK:
691  default:
692  return;
693  }
694 
695  Q_EMIT message(tr("Send Coins"), msgParams.first, msgParams.second);
696 }
697 
699 {
700  ui->labelFeeMinimized->setVisible(fMinimize);
701  ui->buttonChooseFee ->setVisible(fMinimize);
702  ui->buttonMinimizeFee->setVisible(!fMinimize);
703  ui->frameFeeSelection->setVisible(!fMinimize);
704  ui->horizontalLayoutSmartFee->setContentsMargins(0, (fMinimize ? 0 : 6), 0, 0);
705  fFeeMinimized = fMinimize;
706 }
707 
709 {
710  minimizeFeeSection(false);
711 }
712 
714 {
716  minimizeFeeSection(true);
717 }
718 
720 {
721  // Get CCoinControl instance if CoinControl is enabled or create a new one.
722  CCoinControl coin_control;
724  coin_control = *CoinControlDialog::coinControl();
725  }
726 
727  // Calculate available amount to send.
728  CAmount amount;
729  if (fPrivateSend) {
730  amount = model->getAnonymizedBalance(&coin_control);
731  } else {
732  amount = model->getBalance(&coin_control);
733  }
734 
735  for (int i = 0; i < ui->entries->count(); ++i) {
736  SendCoinsEntry* e = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
737  if (e && !e->isHidden() && e != entry) {
738  amount -= e->getValue().amount;
739  }
740  }
741 
742  if (amount > 0) {
744  entry->setAmount(amount);
745  } else {
746  entry->setAmount(0);
747  }
748 }
749 
751 {
752  ui->customFee->setValue(GetRequiredFee(1000));
753 }
754 
756 {
757  ui->confTargetSelector ->setEnabled(ui->radioSmartFee->isChecked());
758  ui->labelSmartFee ->setEnabled(ui->radioSmartFee->isChecked());
759  ui->labelSmartFee2 ->setEnabled(ui->radioSmartFee->isChecked());
760  ui->labelSmartFee3 ->setEnabled(ui->radioSmartFee->isChecked());
761  ui->labelFeeEstimation ->setEnabled(ui->radioSmartFee->isChecked());
762  ui->checkBoxMinimumFee ->setEnabled(ui->radioCustomFee->isChecked());
763  ui->labelMinFeeWarning ->setEnabled(ui->radioCustomFee->isChecked());
764  ui->labelCustomPerKilobyte ->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked());
765  ui->customFee ->setEnabled(ui->radioCustomFee->isChecked() && !ui->checkBoxMinimumFee->isChecked());
766 }
767 
769 {
770  if(!model || !model->getOptionsModel())
771  return;
772 
773  if (ui->radioSmartFee->isChecked())
774  ui->labelFeeMinimized->setText(ui->labelSmartFee->text());
775  else {
776  ui->labelFeeMinimized->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), ui->customFee->value()) + "/kB");
777  }
778 }
779 
781 {
782  if (model && model->getOptionsModel())
783  ui->checkBoxMinimumFee->setText(tr("Pay only the required fee of %1").arg(
785  );
786 }
787 
789 {
790  if (ui->radioCustomFee->isChecked()) {
791  ctrl.m_feerate = CFeeRate(ui->customFee->value());
792  } else {
793  ctrl.m_feerate.reset();
794  }
795  // Avoid using global defaults when sending money from the GUI
796  // Either custom fee will be used or if not selected, the confirmation target from dropdown box
797  ctrl.m_confirm_target = getConfTargetForIndex(ui->confTargetSelector->currentIndex());
798 }
799 
800 void SendCoinsDialog::showEvent(QShowEvent* event)
801 {
802  QWidget::showEvent(event);
803  if (!event->spontaneous()) {
805  }
806 }
807 
809 {
810  if(!model || !model->getOptionsModel())
811  return;
812  CCoinControl coin_control;
813  updateCoinControlState(coin_control);
814  coin_control.m_feerate.reset(); // Explicitly use only fee estimation rate for smart fee labels
815  FeeCalculation feeCalc;
816  CFeeRate feeRate = CFeeRate(GetMinimumFee(1000, coin_control, ::mempool, ::feeEstimator, &feeCalc));
817 
818  ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), feeRate.GetFeePerK()) + "/kB");
819 
820  if (feeCalc.reason == FeeReason::FALLBACK) {
821  ui->labelSmartFee2->show(); // (Smart fee not initialized yet. This usually takes a few blocks...)
822  ui->labelFeeEstimation->setText("");
823  ui->fallbackFeeWarningLabel->setVisible(true);
824  }
825  else
826  {
827  ui->labelSmartFee2->hide();
828  ui->labelFeeEstimation->setText(tr("Estimated to begin confirmation within %n block(s).", "", feeCalc.returnedTarget));
829  ui->fallbackFeeWarningLabel->setVisible(false);
830  }
831 
833 }
834 
835 // Coin Control: copy label "Quantity" to clipboard
837 {
838  GUIUtil::setClipboard(ui->labelCoinControlQuantity->text());
839 }
840 
841 // Coin Control: copy label "Amount" to clipboard
843 {
844  GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left(ui->labelCoinControlAmount->text().indexOf(" ")));
845 }
846 
847 // Coin Control: copy label "Fee" to clipboard
849 {
850  GUIUtil::setClipboard(ui->labelCoinControlFee->text().left(ui->labelCoinControlFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
851 }
852 
853 // Coin Control: copy label "After fee" to clipboard
855 {
856  GUIUtil::setClipboard(ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
857 }
858 
859 // Coin Control: copy label "Bytes" to clipboard
861 {
862  GUIUtil::setClipboard(ui->labelCoinControlBytes->text().replace(ASYMP_UTF8, ""));
863 }
864 
865 // Coin Control: copy label "Dust" to clipboard
867 {
868  GUIUtil::setClipboard(ui->labelCoinControlLowOutput->text());
869 }
870 
871 // Coin Control: copy label "Change" to clipboard
873 {
874  GUIUtil::setClipboard(ui->labelCoinControlChange->text().left(ui->labelCoinControlChange->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
875 }
876 
877 // Coin Control: settings menu - coin control enabled/disabled by user
879 {
880  ui->frameCoinControl->setVisible(checked);
881 
882  if (!checked && model) // coin control features disabled
884 
886 }
887 
888 // Coin Control: button inputs -> show actual coin control dialog
890 {
891  CoinControlDialog dlg(this);
892  dlg.setModel(model);
893  dlg.exec();
895 }
896 
897 // Coin Control: checkbox custom change address
899 {
900  if (state == Qt::Unchecked)
901  {
903  ui->labelCoinControlChangeLabel->clear();
904  }
905  else
906  // use this to re-validate an already entered address
907  coinControlChangeEdited(ui->lineEditCoinControlChange->text());
908 
909  ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked));
910 }
911 
912 // Coin Control: custom change address changed
914 {
915  if (model && model->getAddressTableModel())
916  {
917  // Default to no change address until verified
919  ui->labelCoinControlChangeLabel->setStyleSheet(GUIUtil::getThemedStyleQString(GUIUtil::ThemedStyle::TS_ERROR));
920 
921  const CTxDestination dest = DecodeDestination(text.toStdString());
922 
923  if (text.isEmpty()) // Nothing entered
924  {
925  ui->labelCoinControlChangeLabel->setText("");
926  }
927  else if (!IsValidDestination(dest)) // Invalid address
928  {
929  ui->labelCoinControlChangeLabel->setText(tr("Warning: Invalid Dash address"));
930  }
931  else // Valid address
932  {
933  if (!model->IsSpendable(dest)) {
934  ui->labelCoinControlChangeLabel->setText(tr("Warning: Unknown change address"));
935 
936  // confirmation dialog
937  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?"),
938  QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel);
939 
940  if(btnRetVal == QMessageBox::Yes)
942  else
943  {
944  ui->lineEditCoinControlChange->setText("");
945  ui->labelCoinControlChangeLabel->setStyleSheet(GUIUtil::getThemedStyleQString(GUIUtil::ThemedStyle::TS_PRIMARY));
946  ui->labelCoinControlChangeLabel->setText("");
947  }
948  }
949  else // Known change address
950  {
951  ui->labelCoinControlChangeLabel->setStyleSheet(GUIUtil::getThemedStyleQString(GUIUtil::ThemedStyle::TS_PRIMARY));
952 
953  // Query label
954  QString associatedLabel = model->getAddressTableModel()->labelForAddress(text);
955  if (!associatedLabel.isEmpty())
956  ui->labelCoinControlChangeLabel->setText(associatedLabel);
957  else
958  ui->labelCoinControlChangeLabel->setText(tr("(no label)"));
959 
961  }
962  }
963  }
964 }
965 
966 // Coin Control: update labels
968 {
969  if (!model || !model->getOptionsModel())
970  return;
971 
973 
974  // set pay amounts
977 
978  for(int i = 0; i < ui->entries->count(); ++i)
979  {
980  SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
981  if(entry && !entry->isHidden())
982  {
983  SendCoinsRecipient rcp = entry->getValue();
985  if (rcp.fSubtractFeeFromAmount)
987  }
988  }
989 
990  if (CoinControlDialog::coinControl()->HasSelected())
991  {
992  // actual coin control calculation
994 
995  // show coin control stats
996  ui->labelCoinControlAutomaticallySelected->hide();
997  ui->widgetCoinControl->show();
998  }
999  else
1000  {
1001  // hide coin control stats
1002  ui->labelCoinControlAutomaticallySelected->show();
1003  ui->widgetCoinControl->hide();
1004  ui->labelCoinControlInsuffFunds->hide();
1005  }
1006 }
1007 
1008 SendConfirmationDialog::SendConfirmationDialog(const QString &title, const QString &text, int _secDelay,
1009  QWidget *parent) :
1010  QMessageBox(QMessageBox::Question, title, text, QMessageBox::Yes | QMessageBox::Cancel, parent), secDelay(_secDelay)
1011 {
1013  setDefaultButton(QMessageBox::Cancel);
1014  yesButton = button(QMessageBox::Yes);
1015  updateYesButton();
1016  connect(&countDownTimer, SIGNAL(timeout()), this, SLOT(countDown()));
1017 }
1018 
1020 {
1021  updateYesButton();
1022  countDownTimer.start(1000);
1023  return QMessageBox::exec();
1024 }
1025 
1027 {
1028  secDelay--;
1029  updateYesButton();
1030 
1031  if(secDelay <= 0)
1032  {
1033  countDownTimer.stop();
1034  }
1035 }
1036 
1038 {
1039  if(secDelay > 0)
1040  {
1041  yesButton->setEnabled(false);
1042  yesButton->setText(tr("Yes") + " (" + QString::number(secDelay) + ")");
1043  }
1044  else
1045  {
1046  yesButton->setEnabled(true);
1047  yesButton->setText(tr("Yes"));
1048  }
1049 }
CTxMemPool mempool
void UsePrivateSend(bool fUsePrivateSend)
Definition: coincontrol.h:103
void removeEntry(SendCoinsEntry *entry)
boost::variant< CNoDestination, CKeyID, CScriptID > CTxDestination
A txout script template with a specific destination.
Definition: standard.h:80
Unit
Dash units.
Definition: bitcoinunits.h:58
void setValue(const SendCoinsRecipient &value)
int returnedTarget
Definition: fees.h:130
CWalletTx * getTransaction() const
void updateFeeMinimizedLabel()
void setFont(const std::vector< QWidget *> &vecWidgets, FontWeight weight, int nPointSize, bool fItalic)
Workaround to set correct font styles in all themes since there is a bug in macOS which leads to issu...
Definition: guiutil.cpp:1552
boost::optional< unsigned int > m_confirm_target
Override the default confirmation target if set.
Definition: coincontrol.h:45
void setFocus()
void on_buttonChooseFee_clicked()
SendCoinsRecipient getValue()
static const std::array< int, 9 > confTargets
CAmount maxTxFee
Absolute maximum transaction fee (in duffs) used by wallet and mempool (rejects high fee in sendrawtr...
Definition: validation.cpp:247
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)
FeeReason reason
Definition: fees.h:128
bool IsValidDestination(const CTxDestination &dest)
Check whether a CTxDestination is a CNoDestination.
Definition: standard.cpp:281
CTxDestination DecodeDestination(const std::string &str)
Definition: base58.cpp:336
SendCoinsReturn sendCoins(WalletModelTransaction &transaction)
QList< SendCoinsRecipient > getRecipients() const
void updateCoinControlState(CCoinControl &ctrl)
void setupAddressWidget(QValidatedLineEdit *widget, QWidget *parent, bool fAllowURI)
Definition: guiutil.cpp:286
false true true true
Definition: bls_dkg.cpp:176
QString HtmlEscape(const QString &str, bool fMultiLine)
Definition: guiutil.cpp:481
bool handlePaymentRequest(const SendCoinsRecipient &recipient)
static void usePrivateSend(bool fUsePrivateSend)
CAmount GetRequiredFee(unsigned int nTxBytes)
Return the minimum required fee taking into account the floating relay fee and user set minimum trans...
Definition: fees.cpp:16
AddressTableModel * getAddressTableModel()
void send(QList< SendCoinsRecipient > recipients)
#define ASYMP_UTF8
bool validate()
A single entry in the dialog for sending bitcoins.
Coin Control Features.
Definition: coincontrol.h:28
boost::optional< CFeeRate > m_feerate
Override the default payTxFee if set.
Definition: coincontrol.h:41
EncryptionStatus getEncryptionStatus() const
int getDisplayUnit() const
Definition: optionsmodel.h:82
CAmount getBalance(const CCoinControl *coinControl=nullptr) const
Definition: walletmodel.cpp:76
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
CAmount getImmatureBalance() const
CBlockPolicyEstimator feeEstimator
Definition: validation.cpp:249
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://...
void updateFonts()
Update the font of all widgets where a custom font has been set with GUIUtil::setFont.
Definition: guiutil.cpp:1563
Ui::SendCoinsDialog * ui
SendCoinsEntry * addEntry()
void clear()
void setAddress(const QString &address)
#define SEND_CONFIRM_DELAY
void coinControlClipboardChange()
CTransactionRef tx
Definition: wallet.h:210
void useAvailableBalance(SendCoinsEntry *entry)
void setClientModel(ClientModel *clientModel)
void setClipboard(const QString &str)
Definition: guiutil.cpp:1817
ClientModel * clientModel
void SetNull()
Definition: coincontrol.h:56
void showEvent(QShowEvent *event)
static secp256k1_context * ctx
Definition: tests.c:46
CTxDestination destChange
Definition: coincontrol.h:31
int getDefaultConfirmTarget() const
CAmount getWatchUnconfirmedBalance() const
QString labelForAddress(const QString &address) const
bool IsUsingPrivateSend() const
Definition: coincontrol.h:108
SendCoinsDialog(bool fPrivateSend=false, QWidget *parent=0)
SendCoinsReturn prepareTransaction(WalletModelTransaction &transaction, const CCoinControl &coinControl)
WalletModel * model
Dialog for sending bitcoins.
void coinControlChangeEdited(const QString &)
static void updateLabels(WalletModel *, QDialog *)
CAmount getAnonymizedBalance(const CCoinControl *coinControl=nullptr) const
Definition: walletmodel.cpp:92
Model for Dash network client.
Definition: clientmodel.h:42
void coinControlUpdateLabels()
void UnSelectAll()
Definition: coincontrol.h:91
void setModel(WalletModel *model)
int getConfTargetForIndex(int index)
bool getCoinControlFeatures() const
Definition: optionsmodel.h:85
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.
void setModel(WalletModel *model)
static bool fSubtractFeeFromAmount
SendConfirmationDialog(const QString &title, const QString &text, int secDelay=0, QWidget *parent=0)
UnlockContext requestUnlock(bool fForMixingOnly=false)
int getIndexForConfTarget(int target)
const CChainParams & Params()
Return the currently selected parameters.
Interface to Bitcoin wallet from Qt view code.
Definition: walletmodel.h:100
CAmount getWatchBalance() const
CAmount getUnconfirmedBalance() const
void setAmount(const CAmount &amount)
static const int MAX_SEND_POPUP_ENTRIES
QString getThemedStyleQString(ThemedStyle style)
Helper to get css style strings which are injected into rich text through qt.
Definition: guiutil.cpp:210
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()
bool fSubtractFeeFromAmount
Definition: walletmodel.h:62
static const CAmount DEFAULT_TRANSACTION_FEE
-paytxfee default
Definition: wallet.h:56
CAmount getWatchImmatureBalance() const
CAmount getTotalTransactionAmount() const
QString formatNiceTimeOffset(qint64 secs)
Definition: guiutil.cpp:1898
static QString formatWithUnit(int unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=separatorStandard)
Format as string (with unit)
void coinControlClipboardAmount()
void on_buttonMinimizeFee_clicked()
void pasteEntry(const SendCoinsRecipient &rv)
QAbstractButton * yesButton
void message(const QString &title, const QString &message, unsigned int style)
void setBalance(const CAmount &balance, const CAmount &unconfirmedBalance, const CAmount &immatureBalance, const CAmount &anonymizedBalance, const CAmount &watchOnlyBalance, const CAmount &watchUnconfBalance, const CAmount &watchImmatureBalance)
CAmount GetMinimumFee(unsigned int nTxBytes, const CCoinControl &coin_control, const CTxMemPool &pool, const CBlockPolicyEstimator &estimator, FeeCalculation *feeCalc)
Estimate the minimum fee considering user set parameters and the required fee.
Definition: fees.cpp:21
CAmount GetFeePerK() const
Return the fee in satoshis for a size of 1000 bytes.
Definition: feerate.h:41
void coinControlButtonClicked()
void coinControlClipboardFee()
OptionsModel * getOptionsModel()
bool IsSpendable(const CTxDestination &dest) const
void coinControlChangeChecked(int)
static CCoinControl * coinControl()
Released under the MIT license