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