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