Bitcoin Core  22.0.0
P2P Digital Currency
sendcoinsdialog.cpp
Go to the documentation of this file.
1 // Copyright (c) 2011-2020 The Bitcoin Core developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 
5 #if defined(HAVE_CONFIG_H)
7 #endif
8 
9 #include <qt/sendcoinsdialog.h>
10 #include <qt/forms/ui_sendcoinsdialog.h>
11 
12 #include <qt/addresstablemodel.h>
13 #include <qt/bitcoinunits.h>
14 #include <qt/clientmodel.h>
15 #include <qt/coincontroldialog.h>
16 #include <qt/guiutil.h>
17 #include <qt/optionsmodel.h>
18 #include <qt/platformstyle.h>
19 #include <qt/sendcoinsentry.h>
20 
21 #include <chainparams.h>
22 #include <interfaces/node.h>
23 #include <key_io.h>
24 #include <node/ui_interface.h>
25 #include <policy/fees.h>
26 #include <txmempool.h>
27 #include <wallet/coincontrol.h>
28 #include <wallet/fees.h>
29 #include <wallet/wallet.h>
30 
31 #include <validation.h>
32 
33 #include <QFontMetrics>
34 #include <QScrollBar>
35 #include <QSettings>
36 #include <QTextDocument>
37 
38 static constexpr std::array confTargets{2, 4, 6, 12, 24, 48, 144, 504, 1008};
39 int getConfTargetForIndex(int index) {
40  if (index+1 > static_cast<int>(confTargets.size())) {
41  return confTargets.back();
42  }
43  if (index < 0) {
44  return confTargets[0];
45  }
46  return confTargets[index];
47 }
48 int getIndexForConfTarget(int target) {
49  for (unsigned int i = 0; i < confTargets.size(); i++) {
50  if (confTargets[i] >= target) {
51  return i;
52  }
53  }
54  return confTargets.size() - 1;
55 }
56 
57 SendCoinsDialog::SendCoinsDialog(const PlatformStyle *_platformStyle, QWidget *parent) :
58  QDialog(parent, GUIUtil::dialog_flags),
59  ui(new Ui::SendCoinsDialog),
60  clientModel(nullptr),
61  model(nullptr),
62  m_coin_control(new CCoinControl),
63  fNewRecipientAllowed(true),
64  fFeeMinimized(true),
65  platformStyle(_platformStyle)
66 {
67  ui->setupUi(this);
68 
69  if (!_platformStyle->getImagesOnButtons()) {
70  ui->addButton->setIcon(QIcon());
71  ui->clearButton->setIcon(QIcon());
72  ui->sendButton->setIcon(QIcon());
73  } else {
74  ui->addButton->setIcon(_platformStyle->SingleColorIcon(":/icons/add"));
75  ui->clearButton->setIcon(_platformStyle->SingleColorIcon(":/icons/remove"));
76  ui->sendButton->setIcon(_platformStyle->SingleColorIcon(":/icons/send"));
77  }
78 
79  GUIUtil::setupAddressWidget(ui->lineEditCoinControlChange, this);
80 
81  addEntry();
82 
83  connect(ui->addButton, &QPushButton::clicked, this, &SendCoinsDialog::addEntry);
84  connect(ui->clearButton, &QPushButton::clicked, this, &SendCoinsDialog::clear);
85 
86  // Coin Control
87  connect(ui->pushButtonCoinControl, &QPushButton::clicked, this, &SendCoinsDialog::coinControlButtonClicked);
88  connect(ui->checkBoxCoinControlChange, &QCheckBox::stateChanged, this, &SendCoinsDialog::coinControlChangeChecked);
89  connect(ui->lineEditCoinControlChange, &QValidatedLineEdit::textEdited, this, &SendCoinsDialog::coinControlChangeEdited);
90 
91  // Coin Control: clipboard actions
92  QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this);
93  QAction *clipboardAmountAction = new QAction(tr("Copy amount"), this);
94  QAction *clipboardFeeAction = new QAction(tr("Copy fee"), this);
95  QAction *clipboardAfterFeeAction = new QAction(tr("Copy after fee"), this);
96  QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this);
97  QAction *clipboardLowOutputAction = new QAction(tr("Copy dust"), this);
98  QAction *clipboardChangeAction = new QAction(tr("Copy change"), this);
99  connect(clipboardQuantityAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardQuantity);
100  connect(clipboardAmountAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardAmount);
101  connect(clipboardFeeAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardFee);
102  connect(clipboardAfterFeeAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardAfterFee);
103  connect(clipboardBytesAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardBytes);
104  connect(clipboardLowOutputAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardLowOutput);
105  connect(clipboardChangeAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardChange);
106  ui->labelCoinControlQuantity->addAction(clipboardQuantityAction);
107  ui->labelCoinControlAmount->addAction(clipboardAmountAction);
108  ui->labelCoinControlFee->addAction(clipboardFeeAction);
109  ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction);
110  ui->labelCoinControlBytes->addAction(clipboardBytesAction);
111  ui->labelCoinControlLowOutput->addAction(clipboardLowOutputAction);
112  ui->labelCoinControlChange->addAction(clipboardChangeAction);
113 
114  // init transaction fee section
115  QSettings settings;
116  if (!settings.contains("fFeeSectionMinimized"))
117  settings.setValue("fFeeSectionMinimized", true);
118  if (!settings.contains("nFeeRadio") && settings.contains("nTransactionFee") && settings.value("nTransactionFee").toLongLong() > 0) // compatibility
119  settings.setValue("nFeeRadio", 1); // custom
120  if (!settings.contains("nFeeRadio"))
121  settings.setValue("nFeeRadio", 0); // recommended
122  if (!settings.contains("nSmartFeeSliderPosition"))
123  settings.setValue("nSmartFeeSliderPosition", 0);
124  if (!settings.contains("nTransactionFee"))
125  settings.setValue("nTransactionFee", (qint64)DEFAULT_PAY_TX_FEE);
126  ui->groupFee->setId(ui->radioSmartFee, 0);
127  ui->groupFee->setId(ui->radioCustomFee, 1);
128  ui->groupFee->button((int)std::max(0, std::min(1, settings.value("nFeeRadio").toInt())))->setChecked(true);
129  ui->customFee->SetAllowEmpty(false);
130  ui->customFee->setValue(settings.value("nTransactionFee").toLongLong());
131  minimizeFeeSection(settings.value("fFeeSectionMinimized").toBool());
132 
133  GUIUtil::ExceptionSafeConnect(ui->sendButton, &QPushButton::clicked, this, &SendCoinsDialog::sendButtonClicked);
134 }
135 
137 {
138  this->clientModel = _clientModel;
139 
140  if (_clientModel) {
142  }
143 }
144 
146 {
147  this->model = _model;
148 
149  if(_model && _model->getOptionsModel())
150  {
151  for(int i = 0; i < ui->entries->count(); ++i)
152  {
153  SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
154  if(entry)
155  {
156  entry->setModel(_model);
157  }
158  }
159 
160  interfaces::WalletBalances balances = _model->wallet().getBalances();
161  setBalance(balances);
165 
166  // Coin Control
169  ui->frameCoinControl->setVisible(_model->getOptionsModel()->getCoinControlFeatures());
171 
172  // fee section
173  for (const int n : confTargets) {
174  ui->confTargetSelector->addItem(tr("%1 (%2 blocks)").arg(GUIUtil::formatNiceTimeOffset(n*Params().GetConsensus().nPowTargetSpacing)).arg(n));
175  }
176  connect(ui->confTargetSelector, qOverload<int>(&QComboBox::currentIndexChanged), this, &SendCoinsDialog::updateSmartFeeLabel);
177  connect(ui->confTargetSelector, qOverload<int>(&QComboBox::currentIndexChanged), this, &SendCoinsDialog::coinControlUpdateLabels);
178 
179 #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
180  connect(ui->groupFee, &QButtonGroup::idClicked, this, &SendCoinsDialog::updateFeeSectionControls);
181  connect(ui->groupFee, &QButtonGroup::idClicked, this, &SendCoinsDialog::coinControlUpdateLabels);
182 #else
183  connect(ui->groupFee, qOverload<int>(&QButtonGroup::buttonClicked), this, &SendCoinsDialog::updateFeeSectionControls);
184  connect(ui->groupFee, qOverload<int>(&QButtonGroup::buttonClicked), this, &SendCoinsDialog::coinControlUpdateLabels);
185 #endif
186 
188  connect(ui->optInRBF, &QCheckBox::stateChanged, this, &SendCoinsDialog::updateSmartFeeLabel);
189  connect(ui->optInRBF, &QCheckBox::stateChanged, this, &SendCoinsDialog::coinControlUpdateLabels);
190  CAmount requiredFee = model->wallet().getRequiredFee(1000);
191  ui->customFee->SetMinValue(requiredFee);
192  if (ui->customFee->value() < requiredFee) {
193  ui->customFee->setValue(requiredFee);
194  }
195  ui->customFee->setSingleStep(requiredFee);
198 
199  // set default rbf checkbox state
200  ui->optInRBF->setCheckState(Qt::Checked);
201 
202  if (model->wallet().hasExternalSigner()) {
203  //: "device" usually means a hardware wallet
204  ui->sendButton->setText(tr("Sign on device"));
205  if (gArgs.GetArg("-signer", "") != "") {
206  ui->sendButton->setEnabled(true);
207  ui->sendButton->setToolTip(tr("Connect your hardware wallet first."));
208  } else {
209  ui->sendButton->setEnabled(false);
210  //: "External signer" means using devices such as hardware wallets.
211  ui->sendButton->setToolTip(tr("Set external signer script path in Options -> Wallet"));
212  }
213  } else if (model->wallet().privateKeysDisabled()) {
214  ui->sendButton->setText(tr("Cr&eate Unsigned"));
215  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));
216  }
217 
218  // set the smartfee-sliders default value (wallets default conf.target or last stored value)
219  QSettings settings;
220  if (settings.value("nSmartFeeSliderPosition").toInt() != 0) {
221  // migrate nSmartFeeSliderPosition to nConfTarget
222  // nConfTarget is available since 0.15 (replaced nSmartFeeSliderPosition)
223  int nConfirmTarget = 25 - settings.value("nSmartFeeSliderPosition").toInt(); // 25 == old slider range
224  settings.setValue("nConfTarget", nConfirmTarget);
225  settings.remove("nSmartFeeSliderPosition");
226  }
227  if (settings.value("nConfTarget").toInt() == 0)
228  ui->confTargetSelector->setCurrentIndex(getIndexForConfTarget(model->wallet().getConfirmTarget()));
229  else
230  ui->confTargetSelector->setCurrentIndex(getIndexForConfTarget(settings.value("nConfTarget").toInt()));
231  }
232 }
233 
235 {
236  QSettings settings;
237  settings.setValue("fFeeSectionMinimized", fFeeMinimized);
238  settings.setValue("nFeeRadio", ui->groupFee->checkedId());
239  settings.setValue("nConfTarget", getConfTargetForIndex(ui->confTargetSelector->currentIndex()));
240  settings.setValue("nTransactionFee", (qint64)ui->customFee->value());
241 
242  delete ui;
243 }
244 
245 bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informative_text, QString& detailed_text)
246 {
247  QList<SendCoinsRecipient> recipients;
248  bool valid = true;
249 
250  for(int i = 0; i < ui->entries->count(); ++i)
251  {
252  SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
253  if(entry)
254  {
255  if(entry->validate(model->node()))
256  {
257  recipients.append(entry->getValue());
258  }
259  else if (valid)
260  {
261  ui->scrollArea->ensureWidgetVisible(entry);
262  valid = false;
263  }
264  }
265  }
266 
267  if(!valid || recipients.isEmpty())
268  {
269  return false;
270  }
271 
272  fNewRecipientAllowed = false;
274  if(!ctx.isValid())
275  {
276  // Unlock wallet was cancelled
277  fNewRecipientAllowed = true;
278  return false;
279  }
280 
281  // prepare transaction for getting txFee earlier
282  m_current_transaction = std::make_unique<WalletModelTransaction>(recipients);
283  WalletModel::SendCoinsReturn prepareStatus;
284 
286 
288 
289  // process prepareStatus and on error generate message shown to user
290  processSendCoinsReturn(prepareStatus,
292 
293  if(prepareStatus.status != WalletModel::OK) {
294  fNewRecipientAllowed = true;
295  return false;
296  }
297 
298  CAmount txFee = m_current_transaction->getTransactionFee();
299  QStringList formatted;
300  for (const SendCoinsRecipient &rcp : m_current_transaction->getRecipients())
301  {
302  // generate amount string with wallet name in case of multiwallet
303  QString amount = BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
304  if (model->isMultiwallet()) {
305  amount.append(tr(" from wallet '%1'").arg(GUIUtil::HtmlEscape(model->getWalletName())));
306  }
307 
308  // generate address string
309  QString address = rcp.address;
310 
311  QString recipientElement;
312 
313  {
314  if(rcp.label.length() > 0) // label with address
315  {
316  recipientElement.append(tr("%1 to '%2'").arg(amount, GUIUtil::HtmlEscape(rcp.label)));
317  recipientElement.append(QString(" (%1)").arg(address));
318  }
319  else // just address
320  {
321  recipientElement.append(tr("%1 to %2").arg(amount, address));
322  }
323  }
324  formatted.append(recipientElement);
325  }
326 
328  question_string.append(tr("Do you want to draft this transaction?"));
329  } else {
330  question_string.append(tr("Are you sure you want to send?"));
331  }
332 
333  question_string.append("<br /><span style='font-size:10pt;'>");
335  question_string.append(tr("Please, review your transaction proposal. This will produce a Partially Signed Bitcoin Transaction (PSBT) which you can save or copy and then sign with e.g. an offline %1 wallet, or a PSBT-compatible hardware wallet.").arg(PACKAGE_NAME));
336  } else {
337  question_string.append(tr("Please, review your transaction."));
338  }
339  question_string.append("</span>%1");
340 
341  if(txFee > 0)
342  {
343  // append fee string if a fee is required
344  question_string.append("<hr /><b>");
345  question_string.append(tr("Transaction fee"));
346  question_string.append("</b>");
347 
348  // append transaction size
349  question_string.append(" (" + QString::number((double)m_current_transaction->getTransactionSize() / 1000) + " kB): ");
350 
351  // append transaction fee value
352  question_string.append("<span style='color:#aa0000; font-weight:bold;'>");
353  question_string.append(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), txFee));
354  question_string.append("</span><br />");
355 
356  // append RBF message according to transaction's signalling
357  question_string.append("<span style='font-size:10pt; font-weight:normal;'>");
358  if (ui->optInRBF->isChecked()) {
359  question_string.append(tr("You can increase the fee later (signals Replace-By-Fee, BIP-125)."));
360  } else {
361  question_string.append(tr("Not signalling Replace-By-Fee, BIP-125."));
362  }
363  question_string.append("</span>");
364  }
365 
366  // add total amount in all subdivision units
367  question_string.append("<hr />");
368  CAmount totalAmount = m_current_transaction->getTotalTransactionAmount() + txFee;
369  QStringList alternativeUnits;
371  {
372  if(u != model->getOptionsModel()->getDisplayUnit())
373  alternativeUnits.append(BitcoinUnits::formatHtmlWithUnit(u, totalAmount));
374  }
375  question_string.append(QString("<b>%1</b>: <b>%2</b>").arg(tr("Total Amount"))
377  question_string.append(QString("<br /><span style='font-size:10pt; font-weight:normal;'>(=%1)</span>")
378  .arg(alternativeUnits.join(" " + tr("or") + " ")));
379 
380  if (formatted.size() > 1) {
381  question_string = question_string.arg("");
382  informative_text = tr("To review recipient list click \"Show Details…\"");
383  detailed_text = formatted.join("\n\n");
384  } else {
385  question_string = question_string.arg("<br /><br />" + formatted.at(0));
386  }
387 
388  return true;
389 }
390 
391 void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
392 {
393  if(!model || !model->getOptionsModel())
394  return;
395 
396  QString question_string, informative_text, detailed_text;
397  if (!PrepareSendText(question_string, informative_text, detailed_text)) return;
399 
400  const QString confirmation = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner() ? tr("Confirm transaction proposal") : tr("Confirm send coins");
401  const QString confirmButtonText = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner() ? tr("Create Unsigned") : tr("Sign and send");
402  SendConfirmationDialog confirmationDialog(confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, confirmButtonText, this);
403  confirmationDialog.exec();
404  QMessageBox::StandardButton retval = static_cast<QMessageBox::StandardButton>(confirmationDialog.result());
405 
406  if(retval != QMessageBox::Yes)
407  {
408  fNewRecipientAllowed = true;
409  return;
410  }
411 
412  bool send_failure = false;
413  if (model->wallet().privateKeysDisabled()) {
415  PartiallySignedTransaction psbtx(mtx);
416  bool complete = false;
417  // Always fill without signing first. This prevents an external signer
418  // from being called prematurely and is not expensive.
419  TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, nullptr, psbtx, complete);
420  assert(!complete);
422  if (model->wallet().hasExternalSigner()) {
423  try {
424  err = model->wallet().fillPSBT(SIGHASH_ALL, true /* sign */, true /* bip32derivs */, nullptr, psbtx, complete);
425  } catch (const std::runtime_error& e) {
426  QMessageBox::critical(nullptr, tr("Sign failed"), e.what());
427  send_failure = true;
428  return;
429  }
431  //: "External signer" means using devices such as hardware wallets.
432  QMessageBox::critical(nullptr, tr("External signer not found"), "External signer not found");
433  send_failure = true;
434  return;
435  }
437  //: "External signer" means using devices such as hardware wallets.
438  QMessageBox::critical(nullptr, tr("External signer failure"), "External signer failure");
439  send_failure = true;
440  return;
441  }
442  if (err != TransactionError::OK) {
443  tfm::format(std::cerr, "Failed to sign PSBT");
445  send_failure = true;
446  return;
447  }
448  // fillPSBT does not always properly finalize
449  complete = FinalizeAndExtractPSBT(psbtx, mtx);
450  }
451 
452  // Broadcast transaction if complete (even with an external signer this
453  // is not always the case, e.g. in a multisig wallet).
454  if (complete) {
455  const CTransactionRef tx = MakeTransactionRef(mtx);
456  m_current_transaction->setWtx(tx);
458  // process sendStatus and on error generate message shown to user
459  processSendCoinsReturn(sendStatus);
460 
461  if (sendStatus.status == WalletModel::OK) {
462  Q_EMIT coinsSent(m_current_transaction->getWtx()->GetHash());
463  } else {
464  send_failure = true;
465  }
466  return;
467  }
468 
469  // Copy PSBT to clipboard and offer to save
470  assert(!complete);
471  // Serialize the PSBT
473  ssTx << psbtx;
474  GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str());
475  QMessageBox msgBox;
476  msgBox.setText("Unsigned Transaction");
477  msgBox.setInformativeText("The PSBT has been copied to the clipboard. You can also save it.");
478  msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard);
479  msgBox.setDefaultButton(QMessageBox::Discard);
480  switch (msgBox.exec()) {
481  case QMessageBox::Save: {
482  QString selectedFilter;
483  QString fileNameSuggestion = "";
484  bool first = true;
485  for (const SendCoinsRecipient &rcp : m_current_transaction->getRecipients()) {
486  if (!first) {
487  fileNameSuggestion.append(" - ");
488  }
489  QString labelOrAddress = rcp.label.isEmpty() ? rcp.address : rcp.label;
490  QString amount = BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
491  fileNameSuggestion.append(labelOrAddress + "-" + amount);
492  first = false;
493  }
494  fileNameSuggestion.append(".psbt");
495  QString filename = GUIUtil::getSaveFileName(this,
496  tr("Save Transaction Data"), fileNameSuggestion,
497  //: Expanded name of the binary PSBT file format. See: BIP 174.
498  tr("Partially Signed Transaction (Binary)") + QLatin1String(" (*.psbt)"), &selectedFilter);
499  if (filename.isEmpty()) {
500  return;
501  }
502  std::ofstream out(filename.toLocal8Bit().data(), std::ofstream::out | std::ofstream::binary);
503  out << ssTx.str();
504  out.close();
505  Q_EMIT message(tr("PSBT saved"), "PSBT saved to disk", CClientUIInterface::MSG_INFORMATION);
506  break;
507  }
508  case QMessageBox::Discard:
509  break;
510  default:
511  assert(false);
512  } // msgBox.exec()
513  } else {
514  // now send the prepared transaction
516  // process sendStatus and on error generate message shown to user
517  processSendCoinsReturn(sendStatus);
518 
519  if (sendStatus.status == WalletModel::OK) {
520  Q_EMIT coinsSent(m_current_transaction->getWtx()->GetHash());
521  } else {
522  send_failure = true;
523  }
524  }
525  if (!send_failure) {
526  accept();
527  m_coin_control->UnSelectAll();
529  }
530  fNewRecipientAllowed = true;
531  m_current_transaction.reset();
532 }
533 
535 {
536  m_current_transaction.reset();
537 
538  // Clear coin control settings
539  m_coin_control->UnSelectAll();
540  ui->checkBoxCoinControlChange->setChecked(false);
541  ui->lineEditCoinControlChange->clear();
543 
544  // Remove entries until only one left
545  while(ui->entries->count())
546  {
547  ui->entries->takeAt(0)->widget()->deleteLater();
548  }
549  addEntry();
550 
552 }
553 
555 {
556  clear();
557 }
558 
560 {
561  clear();
562 }
563 
565 {
566  SendCoinsEntry *entry = new SendCoinsEntry(platformStyle, this);
567  entry->setModel(model);
568  ui->entries->addWidget(entry);
573 
574  // Focus the field, so that entry can start immediately
575  entry->clear();
576  entry->setFocus();
577  ui->scrollAreaWidgetContents->resize(ui->scrollAreaWidgetContents->sizeHint());
578  qApp->processEvents();
579  QScrollBar* bar = ui->scrollArea->verticalScrollBar();
580  if(bar)
581  bar->setSliderPosition(bar->maximum());
582 
584  return entry;
585 }
586 
588 {
589  setupTabChain(nullptr);
591 }
592 
594 {
595  entry->hide();
596 
597  // If the last entry is about to be removed add an empty one
598  if (ui->entries->count() == 1)
599  addEntry();
600 
601  entry->deleteLater();
602 
604 }
605 
606 QWidget *SendCoinsDialog::setupTabChain(QWidget *prev)
607 {
608  for(int i = 0; i < ui->entries->count(); ++i)
609  {
610  SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
611  if(entry)
612  {
613  prev = entry->setupTabChain(prev);
614  }
615  }
616  QWidget::setTabOrder(prev, ui->sendButton);
617  QWidget::setTabOrder(ui->sendButton, ui->clearButton);
618  QWidget::setTabOrder(ui->clearButton, ui->addButton);
619  return ui->addButton;
620 }
621 
622 void SendCoinsDialog::setAddress(const QString &address)
623 {
624  SendCoinsEntry *entry = nullptr;
625  // Replace the first entry if it is still unused
626  if(ui->entries->count() == 1)
627  {
628  SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
629  if(first->isClear())
630  {
631  entry = first;
632  }
633  }
634  if(!entry)
635  {
636  entry = addEntry();
637  }
638 
639  entry->setAddress(address);
640 }
641 
643 {
645  return;
646 
647  SendCoinsEntry *entry = nullptr;
648  // Replace the first entry if it is still unused
649  if(ui->entries->count() == 1)
650  {
651  SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
652  if(first->isClear())
653  {
654  entry = first;
655  }
656  }
657  if(!entry)
658  {
659  entry = addEntry();
660  }
661 
662  entry->setValue(rv);
664 }
665 
667 {
668  // Just paste the entry, all pre-checks
669  // are done in paymentserver.cpp.
670  pasteEntry(rv);
671  return true;
672 }
673 
675 {
676  if(model && model->getOptionsModel())
677  {
678  CAmount balance = balances.balance;
679  if (model->wallet().hasExternalSigner()) {
680  ui->labelBalanceName->setText(tr("External balance:"));
681  } else if (model->wallet().privateKeysDisabled()) {
682  balance = balances.watch_only_balance;
683  ui->labelBalanceName->setText(tr("Watch-only balance:"));
684  }
686  }
687 }
688 
690 {
692  ui->customFee->setDisplayUnit(model->getOptionsModel()->getDisplayUnit());
694 }
695 
696 void SendCoinsDialog::processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg)
697 {
698  QPair<QString, CClientUIInterface::MessageBoxFlags> msgParams;
699  // Default to a warning message, override if error message is needed
700  msgParams.second = CClientUIInterface::MSG_WARNING;
701 
702  // This comment is specific to SendCoinsDialog usage of WalletModel::SendCoinsReturn.
703  // All status values are used only in WalletModel::prepareTransaction()
704  switch(sendCoinsReturn.status)
705  {
707  msgParams.first = tr("The recipient address is not valid. Please recheck.");
708  break;
710  msgParams.first = tr("The amount to pay must be larger than 0.");
711  break;
713  msgParams.first = tr("The amount exceeds your balance.");
714  break;
716  msgParams.first = tr("The total exceeds your balance when the %1 transaction fee is included.").arg(msgArg);
717  break;
719  msgParams.first = tr("Duplicate address found: addresses should only be used once each.");
720  break;
722  msgParams.first = tr("Transaction creation failed!");
723  msgParams.second = CClientUIInterface::MSG_ERROR;
724  break;
726  msgParams.first = tr("A fee higher than %1 is considered an absurdly high fee.").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), model->wallet().getDefaultMaxTxFee()));
727  break;
729  msgParams.first = tr("Payment request expired.");
730  msgParams.second = CClientUIInterface::MSG_ERROR;
731  break;
732  // included to prevent a compiler warning.
733  case WalletModel::OK:
734  default:
735  return;
736  }
737 
738  Q_EMIT message(tr("Send Coins"), msgParams.first, msgParams.second);
739 }
740 
742 {
743  ui->labelFeeMinimized->setVisible(fMinimize);
744  ui->buttonChooseFee ->setVisible(fMinimize);
745  ui->buttonMinimizeFee->setVisible(!fMinimize);
746  ui->frameFeeSelection->setVisible(!fMinimize);
747  ui->horizontalLayoutSmartFee->setContentsMargins(0, (fMinimize ? 0 : 6), 0, 0);
748  fFeeMinimized = fMinimize;
749 }
750 
752 {
753  minimizeFeeSection(false);
754 }
755 
757 {
759  minimizeFeeSection(true);
760 }
761 
763 {
764  // Include watch-only for wallets without private key
766 
767  // Calculate available amount to send.
769  for (int i = 0; i < ui->entries->count(); ++i) {
770  SendCoinsEntry* e = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
771  if (e && !e->isHidden() && e != entry) {
772  amount -= e->getValue().amount;
773  }
774  }
775 
776  if (amount > 0) {
778  entry->setAmount(amount);
779  } else {
780  entry->setAmount(0);
781  }
782 }
783 
785 {
786  ui->confTargetSelector ->setEnabled(ui->radioSmartFee->isChecked());
787  ui->labelSmartFee ->setEnabled(ui->radioSmartFee->isChecked());
788  ui->labelSmartFee2 ->setEnabled(ui->radioSmartFee->isChecked());
789  ui->labelSmartFee3 ->setEnabled(ui->radioSmartFee->isChecked());
790  ui->labelFeeEstimation ->setEnabled(ui->radioSmartFee->isChecked());
791  ui->labelCustomFeeWarning ->setEnabled(ui->radioCustomFee->isChecked());
792  ui->labelCustomPerKilobyte ->setEnabled(ui->radioCustomFee->isChecked());
793  ui->customFee ->setEnabled(ui->radioCustomFee->isChecked());
794 }
795 
797 {
798  if(!model || !model->getOptionsModel())
799  return;
800 
801  if (ui->radioSmartFee->isChecked())
802  ui->labelFeeMinimized->setText(ui->labelSmartFee->text());
803  else {
804  ui->labelFeeMinimized->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), ui->customFee->value()) + "/kvB");
805  }
806 }
807 
809 {
810  if (ui->radioCustomFee->isChecked()) {
811  m_coin_control->m_feerate = CFeeRate(ui->customFee->value());
812  } else {
813  m_coin_control->m_feerate.reset();
814  }
815  // Avoid using global defaults when sending money from the GUI
816  // Either custom fee will be used or if not selected, the confirmation target from dropdown box
817  m_coin_control->m_confirm_target = getConfTargetForIndex(ui->confTargetSelector->currentIndex());
818  m_coin_control->m_signal_bip125_rbf = ui->optInRBF->isChecked();
819  // Include watch-only for wallets without private key
821 }
822 
823 void SendCoinsDialog::updateNumberOfBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, bool headers, SynchronizationState sync_state) {
824  if (sync_state == SynchronizationState::POST_INIT) {
826  }
827 }
828 
830 {
831  if(!model || !model->getOptionsModel())
832  return;
834  m_coin_control->m_feerate.reset(); // Explicitly use only fee estimation rate for smart fee labels
835  int returned_target;
836  FeeReason reason;
837  CFeeRate feeRate = CFeeRate(model->wallet().getMinimumFee(1000, *m_coin_control, &returned_target, &reason));
838 
839  ui->labelSmartFee->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), feeRate.GetFeePerK()) + "/kvB");
840 
841  if (reason == FeeReason::FALLBACK) {
842  ui->labelSmartFee2->show(); // (Smart fee not initialized yet. This usually takes a few blocks...)
843  ui->labelFeeEstimation->setText("");
844  ui->fallbackFeeWarningLabel->setVisible(true);
845  int lightness = ui->fallbackFeeWarningLabel->palette().color(QPalette::WindowText).lightness();
846  QColor warning_colour(255 - (lightness / 5), 176 - (lightness / 3), 48 - (lightness / 14));
847  ui->fallbackFeeWarningLabel->setStyleSheet("QLabel { color: " + warning_colour.name() + "; }");
848  ui->fallbackFeeWarningLabel->setIndent(GUIUtil::TextWidth(QFontMetrics(ui->fallbackFeeWarningLabel->font()), "x"));
849  }
850  else
851  {
852  ui->labelSmartFee2->hide();
853  ui->labelFeeEstimation->setText(tr("Estimated to begin confirmation within %n block(s).", "", returned_target));
854  ui->fallbackFeeWarningLabel->setVisible(false);
855  }
856 
858 }
859 
860 // Coin Control: copy label "Quantity" to clipboard
862 {
863  GUIUtil::setClipboard(ui->labelCoinControlQuantity->text());
864 }
865 
866 // Coin Control: copy label "Amount" to clipboard
868 {
869  GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left(ui->labelCoinControlAmount->text().indexOf(" ")));
870 }
871 
872 // Coin Control: copy label "Fee" to clipboard
874 {
875  GUIUtil::setClipboard(ui->labelCoinControlFee->text().left(ui->labelCoinControlFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
876 }
877 
878 // Coin Control: copy label "After fee" to clipboard
880 {
881  GUIUtil::setClipboard(ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
882 }
883 
884 // Coin Control: copy label "Bytes" to clipboard
886 {
887  GUIUtil::setClipboard(ui->labelCoinControlBytes->text().replace(ASYMP_UTF8, ""));
888 }
889 
890 // Coin Control: copy label "Dust" to clipboard
892 {
893  GUIUtil::setClipboard(ui->labelCoinControlLowOutput->text());
894 }
895 
896 // Coin Control: copy label "Change" to clipboard
898 {
899  GUIUtil::setClipboard(ui->labelCoinControlChange->text().left(ui->labelCoinControlChange->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
900 }
901 
902 // Coin Control: settings menu - coin control enabled/disabled by user
904 {
905  ui->frameCoinControl->setVisible(checked);
906 
907  if (!checked && model) { // coin control features disabled
908  m_coin_control = std::make_unique<CCoinControl>();
909  }
910 
912 }
913 
914 // Coin Control: button inputs -> show actual coin control dialog
916 {
918  dlg.exec();
920 }
921 
922 // Coin Control: checkbox custom change address
924 {
925  if (state == Qt::Unchecked)
926  {
927  m_coin_control->destChange = CNoDestination();
928  ui->labelCoinControlChangeLabel->clear();
929  }
930  else
931  // use this to re-validate an already entered address
932  coinControlChangeEdited(ui->lineEditCoinControlChange->text());
933 
934  ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked));
935 }
936 
937 // Coin Control: custom change address changed
939 {
940  if (model && model->getAddressTableModel())
941  {
942  // Default to no change address until verified
943  m_coin_control->destChange = CNoDestination();
944  ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:red;}");
945 
946  const CTxDestination dest = DecodeDestination(text.toStdString());
947 
948  if (text.isEmpty()) // Nothing entered
949  {
950  ui->labelCoinControlChangeLabel->setText("");
951  }
952  else if (!IsValidDestination(dest)) // Invalid address
953  {
954  ui->labelCoinControlChangeLabel->setText(tr("Warning: Invalid Bitcoin address"));
955  }
956  else // Valid address
957  {
958  if (!model->wallet().isSpendable(dest)) {
959  ui->labelCoinControlChangeLabel->setText(tr("Warning: Unknown change address"));
960 
961  // confirmation dialog
962  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?"),
963  QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel);
964 
965  if(btnRetVal == QMessageBox::Yes)
966  m_coin_control->destChange = dest;
967  else
968  {
969  ui->lineEditCoinControlChange->setText("");
970  ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
971  ui->labelCoinControlChangeLabel->setText("");
972  }
973  }
974  else // Known change address
975  {
976  ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
977 
978  // Query label
979  QString associatedLabel = model->getAddressTableModel()->labelForAddress(text);
980  if (!associatedLabel.isEmpty())
981  ui->labelCoinControlChangeLabel->setText(associatedLabel);
982  else
983  ui->labelCoinControlChangeLabel->setText(tr("(no label)"));
984 
985  m_coin_control->destChange = dest;
986  }
987  }
988  }
989 }
990 
991 // Coin Control: update labels
993 {
994  if (!model || !model->getOptionsModel())
995  return;
996 
998 
999  // set pay amounts
1002 
1003  for(int i = 0; i < ui->entries->count(); ++i)
1004  {
1005  SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
1006  if(entry && !entry->isHidden())
1007  {
1008  SendCoinsRecipient rcp = entry->getValue();
1010  if (rcp.fSubtractFeeFromAmount)
1012  }
1013  }
1014 
1015  if (m_coin_control->HasSelected())
1016  {
1017  // actual coin control calculation
1019 
1020  // show coin control stats
1021  ui->labelCoinControlAutomaticallySelected->hide();
1022  ui->widgetCoinControl->show();
1023  }
1024  else
1025  {
1026  // hide coin control stats
1027  ui->labelCoinControlAutomaticallySelected->show();
1028  ui->widgetCoinControl->hide();
1029  ui->labelCoinControlInsuffFunds->hide();
1030  }
1031 }
1032 
1033 SendConfirmationDialog::SendConfirmationDialog(const QString& title, const QString& text, const QString& informative_text, const QString& detailed_text, int _secDelay, const QString& _confirmButtonText, QWidget* parent)
1034  : QMessageBox(parent), secDelay(_secDelay), confirmButtonText(_confirmButtonText)
1035 {
1036  setIcon(QMessageBox::Question);
1037  setWindowTitle(title); // On macOS, the window title is ignored (as required by the macOS Guidelines).
1038  setText(text);
1039  setInformativeText(informative_text);
1040  setDetailedText(detailed_text);
1041  setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel);
1042  setDefaultButton(QMessageBox::Cancel);
1043  yesButton = button(QMessageBox::Yes);
1044  if (confirmButtonText.isEmpty()) {
1045  confirmButtonText = yesButton->text();
1046  }
1047  updateYesButton();
1048  connect(&countDownTimer, &QTimer::timeout, this, &SendConfirmationDialog::countDown);
1049 }
1050 
1052 {
1053  updateYesButton();
1054  countDownTimer.start(1000);
1055  return QMessageBox::exec();
1056 }
1057 
1059 {
1060  secDelay--;
1061  updateYesButton();
1062 
1063  if(secDelay <= 0)
1064  {
1065  countDownTimer.stop();
1066  }
1067 }
1068 
1070 {
1071  if(secDelay > 0)
1072  {
1073  yesButton->setEnabled(false);
1074  yesButton->setText(confirmButtonText + " (" + QString::number(secDelay) + ")");
1075  }
1076  else
1077  {
1078  yesButton->setEnabled(true);
1079  yesButton->setText(confirmButtonText);
1080  }
1081 }
std::shared_ptr< const CTransaction > CTransactionRef
Definition: transaction.h:386
virtual bool privateKeysDisabled()=0
void removeEntry(SendCoinsEntry *entry)
void updateNumberOfBlocks(int count, const QDateTime &blockDate, double nVerificationProgress, bool headers, SynchronizationState sync_state)
Unit
Bitcoin units.
Definition: bitcoinunits.h:41
interfaces::Wallet & wallet() const
Definition: walletmodel.h:144
void setValue(const SendCoinsRecipient &value)
void payAmountChanged()
Utility functions used by the Bitcoin Qt UI.
Definition: bitcoingui.h:59
SynchronizationState
Current sync state passed to tip changed callbacks.
Definition: validation.h:102
assert(!tx.IsCoinBase())
void sendButtonClicked(bool checked)
void updateFeeMinimizedLabel()
constexpr CAmount DEFAULT_PAY_TX_FEE
-paytxfee default
Definition: wallet.h:67
void setFocus()
void on_buttonChooseFee_clicked()
SendCoinsRecipient getValue()
int TextWidth(const QFontMetrics &fm, const QString &text)
Returns the distance in pixels appropriate for drawing a subsequent character after text...
Definition: guiutil.cpp:863
void reject() override
UnlockContext requestUnlock()
void coinControlClipboardQuantity()
std::string str() const
Definition: streams.h:242
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:373
#define SEND_CONFIRM_DELAY
#define PACKAGE_NAME
SendCoinsReturn sendCoins(WalletModelTransaction &transaction)
void coinControlFeaturesChanged(bool)
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:232
bool handlePaymentRequest(const SendCoinsRecipient &recipient)
CTxDestination DecodeDestination(const std::string &str, std::string &error_msg)
Definition: key_io.cpp:261
A version of CTransaction with the PSBT format.
Definition: psbt.h:391
Double ended buffer combining vector and stream-like interfaces.
Definition: streams.h:204
void numBlocksChanged(int count, const QDateTime &blockDate, double nVerificationProgress, bool header, SynchronizationState sync_state)
fs::ofstream ofstream
Definition: fs.h:102
static constexpr std::array confTargets
AddressTableModel * getAddressTableModel()
#define ASYMP_UTF8
bool FinalizeAndExtractPSBT(PartiallySignedTransaction &psbtx, CMutableTransaction &result)
Finalizes a PSBT if possible, and extracts it to a CMutableTransaction if it could be finalized...
Definition: psbt.cpp:333
static void updateLabels(CCoinControl &m_coin_control, WalletModel *, QDialog *)
virtual bool isSpendable(const CTxDestination &dest)=0
Return whether wallet has private key.
A single entry in the dialog for sending bitcoins.
Coin Control Features.
Definition: coincontrol.h:23
int getDisplayUnit() const
Definition: optionsmodel.h:87
constexpr auto dialog_flags
Definition: guiutil.h:59
void coinControlFeatureChanged(bool)
virtual bool hasExternalSigner()=0
QWidget * setupTabChain(QWidget *prev)
Set up the tab chain manually, as Qt messes up the tab chain by default in some cases (issue https://...
std::unique_ptr< WalletModelTransaction > m_current_transaction
int64_t CAmount
Amount in satoshis (Can be negative)
Definition: amount.h:12
static QList< CAmount > payAmounts
QWidget * setupTabChain(QWidget *prev)
Set up the tab chain manually, as Qt messes up the tab chain by default in some cases (issue https://...
std::string EncodeBase64(Span< const unsigned char > input)
Ui::SendCoinsDialog * ui
SendCoinsEntry * addEntry()
void clear()
bool validate(interfaces::Node &node)
void setupAddressWidget(QValidatedLineEdit *widget, QWidget *parent)
Definition: guiutil.cpp:113
void setBalance(const interfaces::WalletBalances &balances)
static CAmount balance
void setAddress(const QString &address)
void coinControlClipboardChange()
Collection of wallet balances.
Definition: wallet.h:352
void useAvailableBalance(SendCoinsEntry *entry)
void setClientModel(ClientModel *clientModel)
void setClipboard(const QString &str)
Definition: guiutil.cpp:638
static QString formatHtmlWithUnit(int unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD)
Format as HTML string (with unit)
ClientModel * clientModel
static secp256k1_context * ctx
Definition: tests.c:42
void format(std::ostream &out, const char *fmt, const Args &... args)
Format list of arguments to the stream according to given format string.
Definition: tinyformat.h:1062
QString labelForAddress(const QString &address) const
Look up label for address in address book, if not found return empty string.
auto ExceptionSafeConnect(Sender sender, Signal signal, Receiver receiver, Slot method, Qt::ConnectionType type=Qt::AutoConnection)
A drop-in replacement of QObject::connect function (see: https://doc.qt.io/qt-5/qobject.html#connect-3), that guaranties that all exceptions are handled within the slot.
Definition: guiutil.h:386
SendCoinsReturn prepareTransaction(WalletModelTransaction &transaction, const CCoinControl &coinControl)
virtual CAmount getMinimumFee(unsigned int tx_bytes, const CCoinControl &coin_control, int *returned_target, FeeReason *reason)=0
Get minimum fee.
WalletModel * model
Dialog for sending bitcoins.
static QString formatWithUnit(int unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD)
Format as string (with unit)
void coinControlChangeEdited(const QString &)
std::variant< CNoDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessV1Taproot, WitnessUnknown > CTxDestination
A txout script template with a specific destination.
Definition: standard.h:157
FeeReason
Definition: fees.h:43
QString getWalletName() const
interfaces::Node & node() const
Definition: walletmodel.h:143
void removeEntry(SendCoinsEntry *entry)
void displayUnitChanged(int unit)
Model for Bitcoin network client.
Definition: clientmodel.h:47
void coinControlUpdateLabels()
void setModel(WalletModel *model)
int getConfTargetForIndex(int index)
SendCoinsDialog(const PlatformStyle *platformStyle, QWidget *parent=nullptr)
void accept() override
SendConfirmationDialog(const QString &title, const QString &text, const QString &informative_text="", const QString &detailed_text="", int secDelay=SEND_CONFIRM_DELAY, const QString &confirmText="", QWidget *parent=nullptr)
bool getCoinControlFeatures() const
Definition: optionsmodel.h:90
static CTransactionRef MakeTransactionRef(Tx &&txIn)
Definition: transaction.h:387
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 subtractFeeFromAmountChanged()
static bool fSubtractFeeFromAmount
bool PrepareSendText(QString &question_string, QString &informative_text, QString &detailed_text)
int getIndexForConfTarget(int target)
const CChainParams & Params()
Return the currently selected parameters.
Interface to Bitcoin wallet from Qt view code.
Definition: walletmodel.h:51
static const int PROTOCOL_VERSION
network protocol versioning
Definition: version.h:12
QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedSuffixOut)
Get save filename, mimics QFileDialog::getSaveFileName, except that it appends a default suffix when ...
Definition: guiutil.cpp:279
std::string GetArg(const std::string &strArg, const std::string &strDefault) const
Return string argument or default value.
Definition: system.cpp:588
void setAmount(const CAmount &amount)
void setModel(WalletModel *model)
void processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg=QString())
ArgsManager gArgs
Definition: system.cpp:84
TransactionError
Definition: error.h:22
Fee rate in satoshis per kilobyte: CAmount / kB.
Definition: feerate.h:29
static int count
Definition: tests.c:41
void coinControlClipboardBytes()
void useAvailableBalance(SendCoinsEntry *entry)
A mutable version of CTransaction.
Definition: transaction.h:344
virtual TransactionError fillPSBT(int sighash_type, bool sign, bool bip32derivs, size_t *n_signed, PartiallySignedTransaction &psbtx, bool &complete)=0
Fill PSBT.
virtual unsigned int getConfirmTarget()=0
Get tx confirm target.
const PlatformStyle * platformStyle
QString formatNiceTimeOffset(qint64 secs)
Definition: guiutil.cpp:735
std::unique_ptr< CCoinControl > m_coin_control
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.
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:56
void balanceChanged(const interfaces::WalletBalances &balances)
bool getImagesOnButtons() const
Definition: platformstyle.h:21
void coinControlButtonClicked()
void coinControlClipboardFee()
OptionsModel * getOptionsModel()
Predefined combinations for certain default usage cases.
Definition: ui_interface.h:66
void coinControlChangeChecked(int)