Bitcoin Core  27.99.0
P2P Digital Currency
sendcoinsdialog.cpp
Go to the documentation of this file.
1 // Copyright (c) 2011-2022 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/interface_ui.h>
25 #include <policy/fees.h>
26 #include <txmempool.h>
27 #include <validation.h>
28 #include <wallet/coincontrol.h>
29 #include <wallet/fees.h>
30 #include <wallet/wallet.h>
31 
32 #include <array>
33 #include <chrono>
34 #include <fstream>
35 #include <memory>
36 
37 #include <QFontMetrics>
38 #include <QScrollBar>
39 #include <QSettings>
40 #include <QTextDocument>
41 
44 
45 static constexpr std::array confTargets{2, 4, 6, 12, 24, 48, 144, 504, 1008};
46 int getConfTargetForIndex(int index) {
47  if (index+1 > static_cast<int>(confTargets.size())) {
48  return confTargets.back();
49  }
50  if (index < 0) {
51  return confTargets[0];
52  }
53  return confTargets[index];
54 }
55 int getIndexForConfTarget(int target) {
56  for (unsigned int i = 0; i < confTargets.size(); i++) {
57  if (confTargets[i] >= target) {
58  return i;
59  }
60  }
61  return confTargets.size() - 1;
62 }
63 
64 SendCoinsDialog::SendCoinsDialog(const PlatformStyle *_platformStyle, QWidget *parent) :
65  QDialog(parent, GUIUtil::dialog_flags),
66  ui(new Ui::SendCoinsDialog),
67  m_coin_control(new CCoinControl),
68  platformStyle(_platformStyle)
69 {
70  ui->setupUi(this);
71 
72  if (!_platformStyle->getImagesOnButtons()) {
73  ui->addButton->setIcon(QIcon());
74  ui->clearButton->setIcon(QIcon());
75  ui->sendButton->setIcon(QIcon());
76  } else {
77  ui->addButton->setIcon(_platformStyle->SingleColorIcon(":/icons/add"));
78  ui->clearButton->setIcon(_platformStyle->SingleColorIcon(":/icons/remove"));
79  ui->sendButton->setIcon(_platformStyle->SingleColorIcon(":/icons/send"));
80  }
81 
82  GUIUtil::setupAddressWidget(ui->lineEditCoinControlChange, this);
83 
84  addEntry();
85 
86  connect(ui->addButton, &QPushButton::clicked, this, &SendCoinsDialog::addEntry);
87  connect(ui->clearButton, &QPushButton::clicked, this, &SendCoinsDialog::clear);
88 
89  // Coin Control
90  connect(ui->pushButtonCoinControl, &QPushButton::clicked, this, &SendCoinsDialog::coinControlButtonClicked);
91  connect(ui->checkBoxCoinControlChange, &QCheckBox::stateChanged, this, &SendCoinsDialog::coinControlChangeChecked);
92  connect(ui->lineEditCoinControlChange, &QValidatedLineEdit::textEdited, this, &SendCoinsDialog::coinControlChangeEdited);
93 
94  // Coin Control: clipboard actions
95  QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this);
96  QAction *clipboardAmountAction = new QAction(tr("Copy amount"), this);
97  QAction *clipboardFeeAction = new QAction(tr("Copy fee"), this);
98  QAction *clipboardAfterFeeAction = new QAction(tr("Copy after fee"), this);
99  QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), this);
100  QAction *clipboardChangeAction = new QAction(tr("Copy change"), this);
101  connect(clipboardQuantityAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardQuantity);
102  connect(clipboardAmountAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardAmount);
103  connect(clipboardFeeAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardFee);
104  connect(clipboardAfterFeeAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardAfterFee);
105  connect(clipboardBytesAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardBytes);
106  connect(clipboardChangeAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardChange);
107  ui->labelCoinControlQuantity->addAction(clipboardQuantityAction);
108  ui->labelCoinControlAmount->addAction(clipboardAmountAction);
109  ui->labelCoinControlFee->addAction(clipboardFeeAction);
110  ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction);
111  ui->labelCoinControlBytes->addAction(clipboardBytesAction);
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 
162  refreshBalance();
163 
164  // Coin Control
167  ui->frameCoinControl->setVisible(_model->getOptionsModel()->getCoinControlFeatures());
169 
170  // fee section
171  for (const int n : confTargets) {
172  ui->confTargetSelector->addItem(tr("%1 (%2 blocks)").arg(GUIUtil::formatNiceTimeOffset(n*Params().GetConsensus().nPowTargetSpacing)).arg(n));
173  }
174  connect(ui->confTargetSelector, qOverload<int>(&QComboBox::currentIndexChanged), this, &SendCoinsDialog::updateSmartFeeLabel);
175  connect(ui->confTargetSelector, qOverload<int>(&QComboBox::currentIndexChanged), this, &SendCoinsDialog::coinControlUpdateLabels);
176 
177 #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
178  connect(ui->groupFee, &QButtonGroup::idClicked, this, &SendCoinsDialog::updateFeeSectionControls);
179  connect(ui->groupFee, &QButtonGroup::idClicked, this, &SendCoinsDialog::coinControlUpdateLabels);
180 #else
181  connect(ui->groupFee, qOverload<int>(&QButtonGroup::buttonClicked), this, &SendCoinsDialog::updateFeeSectionControls);
182  connect(ui->groupFee, qOverload<int>(&QButtonGroup::buttonClicked), this, &SendCoinsDialog::coinControlUpdateLabels);
183 #endif
184 
186  connect(ui->optInRBF, &QCheckBox::stateChanged, this, &SendCoinsDialog::updateSmartFeeLabel);
187  connect(ui->optInRBF, &QCheckBox::stateChanged, this, &SendCoinsDialog::coinControlUpdateLabels);
188  CAmount requiredFee = model->wallet().getRequiredFee(1000);
189  ui->customFee->SetMinValue(requiredFee);
190  if (ui->customFee->value() < requiredFee) {
191  ui->customFee->setValue(requiredFee);
192  }
193  ui->customFee->setSingleStep(requiredFee);
196 
197  // set default rbf checkbox state
198  ui->optInRBF->setCheckState(Qt::Checked);
199 
200  if (model->wallet().hasExternalSigner()) {
201  //: "device" usually means a hardware wallet.
202  ui->sendButton->setText(tr("Sign on device"));
203  if (model->getOptionsModel()->hasSigner()) {
204  ui->sendButton->setEnabled(true);
205  ui->sendButton->setToolTip(tr("Connect your hardware wallet first."));
206  } else {
207  ui->sendButton->setEnabled(false);
208  //: "External signer" means using devices such as hardware wallets.
209  ui->sendButton->setToolTip(tr("Set external signer script path in Options -> Wallet"));
210  }
211  } else if (model->wallet().privateKeysDisabled()) {
212  ui->sendButton->setText(tr("Cr&eate Unsigned"));
213  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));
214  }
215 
216  // set the smartfee-sliders default value (wallets default conf.target or last stored value)
217  QSettings settings;
218  if (settings.value("nSmartFeeSliderPosition").toInt() != 0) {
219  // migrate nSmartFeeSliderPosition to nConfTarget
220  // nConfTarget is available since 0.15 (replaced nSmartFeeSliderPosition)
221  int nConfirmTarget = 25 - settings.value("nSmartFeeSliderPosition").toInt(); // 25 == old slider range
222  settings.setValue("nConfTarget", nConfirmTarget);
223  settings.remove("nSmartFeeSliderPosition");
224  }
225  if (settings.value("nConfTarget").toInt() == 0)
226  ui->confTargetSelector->setCurrentIndex(getIndexForConfTarget(model->wallet().getConfirmTarget()));
227  else
228  ui->confTargetSelector->setCurrentIndex(getIndexForConfTarget(settings.value("nConfTarget").toInt()));
229  }
230 }
231 
233 {
234  QSettings settings;
235  settings.setValue("fFeeSectionMinimized", fFeeMinimized);
236  settings.setValue("nFeeRadio", ui->groupFee->checkedId());
237  settings.setValue("nConfTarget", getConfTargetForIndex(ui->confTargetSelector->currentIndex()));
238  settings.setValue("nTransactionFee", (qint64)ui->customFee->value());
239 
240  delete ui;
241 }
242 
243 bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informative_text, QString& detailed_text)
244 {
245  QList<SendCoinsRecipient> recipients;
246  bool valid = true;
247 
248  for(int i = 0; i < ui->entries->count(); ++i)
249  {
250  SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
251  if(entry)
252  {
253  if(entry->validate(model->node()))
254  {
255  recipients.append(entry->getValue());
256  }
257  else if (valid)
258  {
259  ui->scrollArea->ensureWidgetVisible(entry);
260  valid = false;
261  }
262  }
263  }
264 
265  if(!valid || recipients.isEmpty())
266  {
267  return false;
268  }
269 
270  fNewRecipientAllowed = false;
272  if(!ctx.isValid())
273  {
274  // Unlock wallet was cancelled
275  fNewRecipientAllowed = true;
276  return false;
277  }
278 
279  // prepare transaction for getting txFee earlier
280  m_current_transaction = std::make_unique<WalletModelTransaction>(recipients);
281  WalletModel::SendCoinsReturn prepareStatus;
282 
284 
285  CCoinControl coin_control = *m_coin_control;
286  coin_control.m_allow_other_inputs = !coin_control.HasSelected(); // future, could introduce a checkbox to customize this value.
287  prepareStatus = model->prepareTransaction(*m_current_transaction, coin_control);
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 = tr("%1 from wallet '%2'").arg(amount, 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 
327  /*: Message displayed when attempting to create a transaction. Cautionary text to prompt the user to verify
328  that the displayed transaction details represent the transaction the user intends to create. */
329  question_string.append(tr("Do you want to create this transaction?"));
330  question_string.append("<br /><span style='font-size:10pt;'>");
332  /*: Text to inform a user attempting to create a transaction of their current options. At this stage,
333  a user can only create a PSBT. This string is displayed when private keys are disabled and an external
334  signer is not available. */
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 if (model->getOptionsModel()->getEnablePSBTControls()) {
337  /*: Text to inform a user attempting to create a transaction of their current options. At this stage,
338  a user can send their transaction or create a PSBT. This string is displayed when both private keys
339  and PSBT controls are enabled. */
340  question_string.append(tr("Please, review your transaction. You can create and send this transaction or create 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));
341  } else {
342  /*: Text to prompt a user to review the details of the transaction they are attempting to send. */
343  question_string.append(tr("Please, review your transaction."));
344  }
345  question_string.append("</span>%1");
346 
347  if(txFee > 0)
348  {
349  // append fee string if a fee is required
350  question_string.append("<hr /><b>");
351  question_string.append(tr("Transaction fee"));
352  question_string.append("</b>");
353 
354  // append transaction size
355  //: When reviewing a newly created PSBT (via Send flow), the transaction fee is shown, with "virtual size" of the transaction displayed for context
356  question_string.append(" (" + tr("%1 kvB", "PSBT transaction creation").arg((double)m_current_transaction->getTransactionSize() / 1000, 0, 'g', 3) + "): ");
357 
358  // append transaction fee value
359  question_string.append("<span style='color:#aa0000; font-weight:bold;'>");
360  question_string.append(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), txFee));
361  question_string.append("</span><br />");
362 
363  // append RBF message according to transaction's signalling
364  question_string.append("<span style='font-size:10pt; font-weight:normal;'>");
365  if (ui->optInRBF->isChecked()) {
366  question_string.append(tr("You can increase the fee later (signals Replace-By-Fee, BIP-125)."));
367  } else {
368  question_string.append(tr("Not signalling Replace-By-Fee, BIP-125."));
369  }
370  question_string.append("</span>");
371  }
372 
373  // add total amount in all subdivision units
374  question_string.append("<hr />");
375  CAmount totalAmount = m_current_transaction->getTotalTransactionAmount() + txFee;
376  QStringList alternativeUnits;
377  for (const BitcoinUnit u : BitcoinUnits::availableUnits()) {
378  if(u != model->getOptionsModel()->getDisplayUnit())
379  alternativeUnits.append(BitcoinUnits::formatHtmlWithUnit(u, totalAmount));
380  }
381  question_string.append(QString("<b>%1</b>: <b>%2</b>").arg(tr("Total Amount"))
383  question_string.append(QString("<br /><span style='font-size:10pt; font-weight:normal;'>(=%1)</span>")
384  .arg(alternativeUnits.join(" " + tr("or") + " ")));
385 
386  if (formatted.size() > 1) {
387  question_string = question_string.arg("");
388  informative_text = tr("To review recipient list click \"Show Details…\"");
389  detailed_text = formatted.join("\n\n");
390  } else {
391  question_string = question_string.arg("<br /><br />" + formatted.at(0));
392  }
393 
394  return true;
395 }
396 
398 {
399  // Serialize the PSBT
400  DataStream ssTx{};
401  ssTx << psbtx;
402  GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str());
403  QMessageBox msgBox(this);
404  //: Caption of "PSBT has been copied" messagebox
405  msgBox.setText(tr("Unsigned Transaction", "PSBT copied"));
406  msgBox.setInformativeText(tr("The PSBT has been copied to the clipboard. You can also save it."));
407  msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard);
408  msgBox.setDefaultButton(QMessageBox::Discard);
409  msgBox.setObjectName("psbt_copied_message");
410  switch (msgBox.exec()) {
411  case QMessageBox::Save: {
412  QString selectedFilter;
413  QString fileNameSuggestion = "";
414  bool first = true;
415  for (const SendCoinsRecipient &rcp : m_current_transaction->getRecipients()) {
416  if (!first) {
417  fileNameSuggestion.append(" - ");
418  }
419  QString labelOrAddress = rcp.label.isEmpty() ? rcp.address : rcp.label;
420  QString amount = BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
421  fileNameSuggestion.append(labelOrAddress + "-" + amount);
422  first = false;
423  }
424  fileNameSuggestion.append(".psbt");
425  QString filename = GUIUtil::getSaveFileName(this,
426  tr("Save Transaction Data"), fileNameSuggestion,
427  //: Expanded name of the binary PSBT file format. See: BIP 174.
428  tr("Partially Signed Transaction (Binary)") + QLatin1String(" (*.psbt)"), &selectedFilter);
429  if (filename.isEmpty()) {
430  return;
431  }
432  std::ofstream out{filename.toLocal8Bit().data(), std::ofstream::out | std::ofstream::binary};
433  out << ssTx.str();
434  out.close();
435  //: Popup message when a PSBT has been saved to a file
436  Q_EMIT message(tr("PSBT saved"), tr("PSBT saved to disk"), CClientUIInterface::MSG_INFORMATION);
437  break;
438  }
439  case QMessageBox::Discard:
440  break;
441  default:
442  assert(false);
443  } // msgBox.exec()
444 }
445 
447  TransactionError err;
448  try {
449  err = model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/true, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete);
450  } catch (const std::runtime_error& e) {
451  QMessageBox::critical(nullptr, tr("Sign failed"), e.what());
452  return false;
453  }
455  //: "External signer" means using devices such as hardware wallets.
456  const QString msg = tr("External signer not found");
457  QMessageBox::critical(nullptr, msg, msg);
458  return false;
459  }
461  //: "External signer" means using devices such as hardware wallets.
462  const QString msg = tr("External signer failure");
463  QMessageBox::critical(nullptr, msg, msg);
464  return false;
465  }
466  if (err != TransactionError::OK) {
467  tfm::format(std::cerr, "Failed to sign PSBT");
469  return false;
470  }
471  // fillPSBT does not always properly finalize
472  complete = FinalizeAndExtractPSBT(psbtx, mtx);
473  return true;
474 }
475 
476 void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
477 {
478  if(!model || !model->getOptionsModel())
479  return;
480 
481  QString question_string, informative_text, detailed_text;
482  if (!PrepareSendText(question_string, informative_text, detailed_text)) return;
484 
485  const QString confirmation = tr("Confirm send coins");
486  const bool enable_send{!model->wallet().privateKeysDisabled() || model->wallet().hasExternalSigner()};
487  const bool always_show_unsigned{model->getOptionsModel()->getEnablePSBTControls()};
488  auto confirmationDialog = new SendConfirmationDialog(confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, enable_send, always_show_unsigned, this);
489  confirmationDialog->setAttribute(Qt::WA_DeleteOnClose);
490  // TODO: Replace QDialog::exec() with safer QDialog::show().
491  const auto retval = static_cast<QMessageBox::StandardButton>(confirmationDialog->exec());
492 
493  if(retval != QMessageBox::Yes && retval != QMessageBox::Save)
494  {
495  fNewRecipientAllowed = true;
496  return;
497  }
498 
499  bool send_failure = false;
500  if (retval == QMessageBox::Save) {
501  // "Create Unsigned" clicked
503  PartiallySignedTransaction psbtx(mtx);
504  bool complete = false;
505  // Fill without signing
506  TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete);
507  assert(!complete);
509 
510  // Copy PSBT to clipboard and offer to save
511  presentPSBT(psbtx);
512  } else {
513  // "Send" clicked
515  bool broadcast = true;
516  if (model->wallet().hasExternalSigner()) {
518  PartiallySignedTransaction psbtx(mtx);
519  bool complete = false;
520  // Always fill without signing first. This prevents an external signer
521  // from being called prematurely and is not expensive.
522  TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete);
523  assert(!complete);
525  send_failure = !signWithExternalSigner(psbtx, mtx, complete);
526  // Don't broadcast when user rejects it on the device or there's a failure:
527  broadcast = complete && !send_failure;
528  if (!send_failure) {
529  // A transaction signed with an external signer is not always complete,
530  // e.g. in a multisig wallet.
531  if (complete) {
532  // Prepare transaction for broadcast transaction if complete
533  const CTransactionRef tx = MakeTransactionRef(mtx);
534  m_current_transaction->setWtx(tx);
535  } else {
536  presentPSBT(psbtx);
537  }
538  }
539  }
540 
541  // Broadcast the transaction, unless an external signer was used and it
542  // failed, or more signatures are needed.
543  if (broadcast) {
544  // now send the prepared transaction
546  Q_EMIT coinsSent(m_current_transaction->getWtx()->GetHash());
547  }
548  }
549  if (!send_failure) {
550  accept();
551  m_coin_control->UnSelectAll();
553  }
554  fNewRecipientAllowed = true;
555  m_current_transaction.reset();
556 }
557 
559 {
560  m_current_transaction.reset();
561 
562  // Clear coin control settings
563  m_coin_control->UnSelectAll();
564  ui->checkBoxCoinControlChange->setChecked(false);
565  ui->lineEditCoinControlChange->clear();
567 
568  // Remove entries until only one left
569  while(ui->entries->count())
570  {
571  ui->entries->takeAt(0)->widget()->deleteLater();
572  }
573  addEntry();
574 
576 }
577 
579 {
580  clear();
581 }
582 
584 {
585  clear();
586 }
587 
589 {
590  SendCoinsEntry *entry = new SendCoinsEntry(platformStyle, this);
591  entry->setModel(model);
592  ui->entries->addWidget(entry);
597 
598  // Focus the field, so that entry can start immediately
599  entry->clear();
600  entry->setFocus();
601  ui->scrollAreaWidgetContents->resize(ui->scrollAreaWidgetContents->sizeHint());
602 
603  // Scroll to the newly added entry on a QueuedConnection because Qt doesn't
604  // adjust the scroll area and scrollbar immediately when the widget is added.
605  // Invoking on a DirectConnection will only scroll to the second-to-last entry.
606  QMetaObject::invokeMethod(ui->scrollArea, [this] {
607  if (ui->scrollArea->verticalScrollBar()) {
608  ui->scrollArea->verticalScrollBar()->setValue(ui->scrollArea->verticalScrollBar()->maximum());
609  }
610  }, Qt::QueuedConnection);
611 
612  updateTabsAndLabels();
613  return entry;
614 }
615 
617 {
618  setupTabChain(nullptr);
620 }
621 
623 {
624  entry->hide();
625 
626  // If the last entry is about to be removed add an empty one
627  if (ui->entries->count() == 1)
628  addEntry();
629 
630  entry->deleteLater();
631 
633 }
634 
635 QWidget *SendCoinsDialog::setupTabChain(QWidget *prev)
636 {
637  for(int i = 0; i < ui->entries->count(); ++i)
638  {
639  SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
640  if(entry)
641  {
642  prev = entry->setupTabChain(prev);
643  }
644  }
645  QWidget::setTabOrder(prev, ui->sendButton);
646  QWidget::setTabOrder(ui->sendButton, ui->clearButton);
647  QWidget::setTabOrder(ui->clearButton, ui->addButton);
648  return ui->addButton;
649 }
650 
651 void SendCoinsDialog::setAddress(const QString &address)
652 {
653  SendCoinsEntry *entry = nullptr;
654  // Replace the first entry if it is still unused
655  if(ui->entries->count() == 1)
656  {
657  SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
658  if(first->isClear())
659  {
660  entry = first;
661  }
662  }
663  if(!entry)
664  {
665  entry = addEntry();
666  }
667 
668  entry->setAddress(address);
669 }
670 
672 {
674  return;
675 
676  SendCoinsEntry *entry = nullptr;
677  // Replace the first entry if it is still unused
678  if(ui->entries->count() == 1)
679  {
680  SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
681  if(first->isClear())
682  {
683  entry = first;
684  }
685  }
686  if(!entry)
687  {
688  entry = addEntry();
689  }
690 
691  entry->setValue(rv);
693 }
694 
696 {
697  // Just paste the entry, all pre-checks
698  // are done in paymentserver.cpp.
699  pasteEntry(rv);
700  return true;
701 }
702 
704 {
705  if(model && model->getOptionsModel())
706  {
707  CAmount balance = balances.balance;
708  if (model->wallet().hasExternalSigner()) {
709  ui->labelBalanceName->setText(tr("External balance:"));
710  } else if (model->wallet().isLegacy() && model->wallet().privateKeysDisabled()) {
711  balance = balances.watch_only_balance;
712  ui->labelBalanceName->setText(tr("Watch-only balance:"));
713  }
714  ui->labelBalance->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), balance));
715  }
716 }
717 
719 {
721  ui->customFee->setDisplayUnit(model->getOptionsModel()->getDisplayUnit());
723 }
724 
725 void SendCoinsDialog::processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg)
726 {
727  QPair<QString, CClientUIInterface::MessageBoxFlags> msgParams;
728  // Default to a warning message, override if error message is needed
729  msgParams.second = CClientUIInterface::MSG_WARNING;
730 
731  // This comment is specific to SendCoinsDialog usage of WalletModel::SendCoinsReturn.
732  // All status values are used only in WalletModel::prepareTransaction()
733  switch(sendCoinsReturn.status)
734  {
736  msgParams.first = tr("The recipient address is not valid. Please recheck.");
737  break;
739  msgParams.first = tr("The amount to pay must be larger than 0.");
740  break;
742  msgParams.first = tr("The amount exceeds your balance.");
743  break;
745  msgParams.first = tr("The total exceeds your balance when the %1 transaction fee is included.").arg(msgArg);
746  break;
748  msgParams.first = tr("Duplicate address found: addresses should only be used once each.");
749  break;
751  msgParams.first = tr("Transaction creation failed!");
752  msgParams.second = CClientUIInterface::MSG_ERROR;
753  break;
755  msgParams.first = tr("A fee higher than %1 is considered an absurdly high fee.").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), model->wallet().getDefaultMaxTxFee()));
756  break;
757  // included to prevent a compiler warning.
758  case WalletModel::OK:
759  default:
760  return;
761  }
762 
763  Q_EMIT message(tr("Send Coins"), msgParams.first, msgParams.second);
764 }
765 
767 {
768  ui->labelFeeMinimized->setVisible(fMinimize);
769  ui->buttonChooseFee ->setVisible(fMinimize);
770  ui->buttonMinimizeFee->setVisible(!fMinimize);
771  ui->frameFeeSelection->setVisible(!fMinimize);
772  ui->horizontalLayoutSmartFee->setContentsMargins(0, (fMinimize ? 0 : 6), 0, 0);
773  fFeeMinimized = fMinimize;
774 }
775 
777 {
778  minimizeFeeSection(false);
779 }
780 
782 {
784  minimizeFeeSection(true);
785 }
786 
788 {
789  // Include watch-only for wallets without private key
791 
792  // Same behavior as send: if we have selected coins, only obtain their available balance.
793  // Copy to avoid modifying the member's data.
794  CCoinControl coin_control = *m_coin_control;
795  coin_control.m_allow_other_inputs = !coin_control.HasSelected();
796 
797  // Calculate available amount to send.
798  CAmount amount = model->getAvailableBalance(&coin_control);
799  for (int i = 0; i < ui->entries->count(); ++i) {
800  SendCoinsEntry* e = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
801  if (e && !e->isHidden() && e != entry) {
802  amount -= e->getValue().amount;
803  }
804  }
805 
806  if (amount > 0) {
808  entry->setAmount(amount);
809  } else {
810  entry->setAmount(0);
811  }
812 }
813 
815 {
816  ui->confTargetSelector ->setEnabled(ui->radioSmartFee->isChecked());
817  ui->labelSmartFee ->setEnabled(ui->radioSmartFee->isChecked());
818  ui->labelSmartFee2 ->setEnabled(ui->radioSmartFee->isChecked());
819  ui->labelSmartFee3 ->setEnabled(ui->radioSmartFee->isChecked());
820  ui->labelFeeEstimation ->setEnabled(ui->radioSmartFee->isChecked());
821  ui->labelCustomFeeWarning ->setEnabled(ui->radioCustomFee->isChecked());
822  ui->labelCustomPerKilobyte ->setEnabled(ui->radioCustomFee->isChecked());
823  ui->customFee ->setEnabled(ui->radioCustomFee->isChecked());
824 }
825 
827 {
828  if(!model || !model->getOptionsModel())
829  return;
830 
831  if (ui->radioSmartFee->isChecked())
832  ui->labelFeeMinimized->setText(ui->labelSmartFee->text());
833  else {
834  ui->labelFeeMinimized->setText(tr("%1/kvB").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), ui->customFee->value())));
835  }
836 }
837 
839 {
840  if (ui->radioCustomFee->isChecked()) {
841  m_coin_control->m_feerate = CFeeRate(ui->customFee->value());
842  } else {
843  m_coin_control->m_feerate.reset();
844  }
845  // Avoid using global defaults when sending money from the GUI
846  // Either custom fee will be used or if not selected, the confirmation target from dropdown box
847  m_coin_control->m_confirm_target = getConfTargetForIndex(ui->confTargetSelector->currentIndex());
848  m_coin_control->m_signal_bip125_rbf = ui->optInRBF->isChecked();
849  // Include watch-only for wallets without private key
851 }
852 
853 void SendCoinsDialog::updateNumberOfBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, SyncType synctype, SynchronizationState sync_state) {
854  if (sync_state == SynchronizationState::POST_INIT) {
856  }
857 }
858 
860 {
861  if(!model || !model->getOptionsModel())
862  return;
864  m_coin_control->m_feerate.reset(); // Explicitly use only fee estimation rate for smart fee labels
865  int returned_target;
866  FeeReason reason;
867  CFeeRate feeRate = CFeeRate(model->wallet().getMinimumFee(1000, *m_coin_control, &returned_target, &reason));
868 
869  ui->labelSmartFee->setText(tr("%1/kvB").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), feeRate.GetFeePerK())));
870 
871  if (reason == FeeReason::FALLBACK) {
872  ui->labelSmartFee2->show(); // (Smart fee not initialized yet. This usually takes a few blocks...)
873  ui->labelFeeEstimation->setText("");
874  ui->fallbackFeeWarningLabel->setVisible(true);
875  int lightness = ui->fallbackFeeWarningLabel->palette().color(QPalette::WindowText).lightness();
876  QColor warning_colour(255 - (lightness / 5), 176 - (lightness / 3), 48 - (lightness / 14));
877  ui->fallbackFeeWarningLabel->setStyleSheet("QLabel { color: " + warning_colour.name() + "; }");
878  ui->fallbackFeeWarningLabel->setIndent(GUIUtil::TextWidth(QFontMetrics(ui->fallbackFeeWarningLabel->font()), "x"));
879  }
880  else
881  {
882  ui->labelSmartFee2->hide();
883  ui->labelFeeEstimation->setText(tr("Estimated to begin confirmation within %n block(s).", "", returned_target));
884  ui->fallbackFeeWarningLabel->setVisible(false);
885  }
886 
888 }
889 
890 // Coin Control: copy label "Quantity" to clipboard
892 {
893  GUIUtil::setClipboard(ui->labelCoinControlQuantity->text());
894 }
895 
896 // Coin Control: copy label "Amount" to clipboard
898 {
899  GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left(ui->labelCoinControlAmount->text().indexOf(" ")));
900 }
901 
902 // Coin Control: copy label "Fee" to clipboard
904 {
905  GUIUtil::setClipboard(ui->labelCoinControlFee->text().left(ui->labelCoinControlFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
906 }
907 
908 // Coin Control: copy label "After fee" to clipboard
910 {
911  GUIUtil::setClipboard(ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
912 }
913 
914 // Coin Control: copy label "Bytes" to clipboard
916 {
917  GUIUtil::setClipboard(ui->labelCoinControlBytes->text().replace(ASYMP_UTF8, ""));
918 }
919 
920 // Coin Control: copy label "Change" to clipboard
922 {
923  GUIUtil::setClipboard(ui->labelCoinControlChange->text().left(ui->labelCoinControlChange->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
924 }
925 
926 // Coin Control: settings menu - coin control enabled/disabled by user
928 {
929  ui->frameCoinControl->setVisible(checked);
930 
931  if (!checked && model) { // coin control features disabled
932  m_coin_control = std::make_unique<CCoinControl>();
933  }
934 
936 }
937 
938 // Coin Control: button inputs -> show actual coin control dialog
940 {
942  connect(dlg, &QDialog::finished, this, &SendCoinsDialog::coinControlUpdateLabels);
944 }
945 
946 // Coin Control: checkbox custom change address
948 {
949  if (state == Qt::Unchecked)
950  {
951  m_coin_control->destChange = CNoDestination();
952  ui->labelCoinControlChangeLabel->clear();
953  }
954  else
955  // use this to re-validate an already entered address
956  coinControlChangeEdited(ui->lineEditCoinControlChange->text());
957 
958  ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked));
959 }
960 
961 // Coin Control: custom change address changed
963 {
964  if (model && model->getAddressTableModel())
965  {
966  // Default to no change address until verified
967  m_coin_control->destChange = CNoDestination();
968  ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:red;}");
969 
970  const CTxDestination dest = DecodeDestination(text.toStdString());
971 
972  if (text.isEmpty()) // Nothing entered
973  {
974  ui->labelCoinControlChangeLabel->setText("");
975  }
976  else if (!IsValidDestination(dest)) // Invalid address
977  {
978  ui->labelCoinControlChangeLabel->setText(tr("Warning: Invalid Bitcoin address"));
979  }
980  else // Valid address
981  {
982  if (!model->wallet().isSpendable(dest)) {
983  ui->labelCoinControlChangeLabel->setText(tr("Warning: Unknown change address"));
984 
985  // confirmation dialog
986  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?"),
987  QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel);
988 
989  if(btnRetVal == QMessageBox::Yes)
990  m_coin_control->destChange = dest;
991  else
992  {
993  ui->lineEditCoinControlChange->setText("");
994  ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
995  ui->labelCoinControlChangeLabel->setText("");
996  }
997  }
998  else // Known change address
999  {
1000  ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
1001 
1002  // Query label
1003  QString associatedLabel = model->getAddressTableModel()->labelForAddress(text);
1004  if (!associatedLabel.isEmpty())
1005  ui->labelCoinControlChangeLabel->setText(associatedLabel);
1006  else
1007  ui->labelCoinControlChangeLabel->setText(tr("(no label)"));
1008 
1009  m_coin_control->destChange = dest;
1010  }
1011  }
1012  }
1013 }
1014 
1015 // Coin Control: update labels
1017 {
1018  if (!model || !model->getOptionsModel())
1019  return;
1020 
1022 
1023  // set pay amounts
1026 
1027  for(int i = 0; i < ui->entries->count(); ++i)
1028  {
1029  SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
1030  if(entry && !entry->isHidden())
1031  {
1032  SendCoinsRecipient rcp = entry->getValue();
1034  if (rcp.fSubtractFeeFromAmount)
1036  }
1037  }
1038 
1039  if (m_coin_control->HasSelected())
1040  {
1041  // actual coin control calculation
1043 
1044  // show coin control stats
1045  ui->labelCoinControlAutomaticallySelected->hide();
1046  ui->widgetCoinControl->show();
1047  }
1048  else
1049  {
1050  // hide coin control stats
1051  ui->labelCoinControlAutomaticallySelected->show();
1052  ui->widgetCoinControl->hide();
1053  ui->labelCoinControlInsuffFunds->hide();
1054  }
1055 }
1056 
1057 SendConfirmationDialog::SendConfirmationDialog(const QString& title, const QString& text, const QString& informative_text, const QString& detailed_text, int _secDelay, bool enable_send, bool always_show_unsigned, QWidget* parent)
1058  : QMessageBox(parent), secDelay(_secDelay), m_enable_send(enable_send)
1059 {
1060  setIcon(QMessageBox::Question);
1061  setWindowTitle(title); // On macOS, the window title is ignored (as required by the macOS Guidelines).
1062  setText(text);
1063  setInformativeText(informative_text);
1064  setDetailedText(detailed_text);
1065  setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel);
1066  if (always_show_unsigned || !enable_send) addButton(QMessageBox::Save);
1067  setDefaultButton(QMessageBox::Cancel);
1068  yesButton = button(QMessageBox::Yes);
1069  if (confirmButtonText.isEmpty()) {
1070  confirmButtonText = yesButton->text();
1071  }
1072  m_psbt_button = button(QMessageBox::Save);
1073  updateButtons();
1074  connect(&countDownTimer, &QTimer::timeout, this, &SendConfirmationDialog::countDown);
1075 }
1076 
1078 {
1079  updateButtons();
1080  countDownTimer.start(1s);
1081  return QMessageBox::exec();
1082 }
1083 
1085 {
1086  secDelay--;
1087  updateButtons();
1088 
1089  if(secDelay <= 0)
1090  {
1091  countDownTimer.stop();
1092  }
1093 }
1094 
1096 {
1097  if(secDelay > 0)
1098  {
1099  yesButton->setEnabled(false);
1100  yesButton->setText(confirmButtonText + (m_enable_send ? (" (" + QString::number(secDelay) + ")") : QString("")));
1101  if (m_psbt_button) {
1102  m_psbt_button->setEnabled(false);
1103  m_psbt_button->setText(m_psbt_button_text + " (" + QString::number(secDelay) + ")");
1104  }
1105  }
1106  else
1107  {
1108  yesButton->setEnabled(m_enable_send);
1109  yesButton->setText(confirmButtonText);
1110  if (m_psbt_button) {
1111  m_psbt_button->setEnabled(true);
1113  }
1114  }
1115 }
bool IsValidDestination(const CTxDestination &dest)
Check whether a CTxDestination corresponds to one with an address.
std::variant< CNoDestination, PubKeyDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessV1Taproot, WitnessUnknown > CTxDestination
A txout script categorized into standard templates.
Definition: addresstype.h:131
int64_t CAmount
Amount in satoshis (Can be negative)
Definition: amount.h:12
#define PACKAGE_NAME
const CChainParams & Params()
Return the currently selected parameters.
QString labelForAddress(const QString &address) const
Look up label for address in address book, if not found return empty string.
static QString formatHtmlWithUnit(Unit unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD)
Format as HTML string (with unit)
static QList< Unit > availableUnits()
Get list of units, for drop-down box.
static QString formatWithUnit(Unit unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD)
Format as string (with unit)
Unit
Bitcoin units.
Definition: bitcoinunits.h:42
@ MSG_INFORMATION
Predefined combinations for certain default usage cases.
Definition: interface_ui.h:65
Fee rate in satoshis per kilovirtualbyte: CAmount / kvB.
Definition: feerate.h:33
CAmount GetFeePerK() const
Return the fee in satoshis for a vsize of 1000 vbytes.
Definition: feerate.h:65
Model for Bitcoin network client.
Definition: clientmodel.h:54
void numBlocksChanged(int count, const QDateTime &blockDate, double nVerificationProgress, SyncType header, SynchronizationState sync_state)
static QList< CAmount > payAmounts
static void updateLabels(wallet::CCoinControl &m_coin_control, WalletModel *, QDialog *)
static bool fSubtractFeeFromAmount
Double ended buffer combining vector and stream-like interfaces.
Definition: streams.h:147
bool getCoinControlFeatures() const
Definition: optionsmodel.h:107
bool getEnablePSBTControls() const
Definition: optionsmodel.h:109
void coinControlFeaturesChanged(bool)
void displayUnitChanged(BitcoinUnit unit)
BitcoinUnit getDisplayUnit() const
Definition: optionsmodel.h:104
bool hasSigner()
Whether -signer was set or not.
QIcon SingleColorIcon(const QString &filename) const
Colorize an icon (given filename) with the icon color.
bool getImagesOnButtons() const
Definition: platformstyle.h:21
Dialog for sending bitcoins.
void useAvailableBalance(SendCoinsEntry *entry)
WalletModel * model
void presentPSBT(PartiallySignedTransaction &psbt)
ClientModel * clientModel
void coinControlChangeEdited(const QString &)
void coinControlChangeChecked(int)
void coinControlClipboardFee()
Ui::SendCoinsDialog * ui
void on_buttonChooseFee_clicked()
void processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg=QString())
void setClientModel(ClientModel *clientModel)
void updateFeeSectionControls()
SendCoinsEntry * addEntry()
void updateNumberOfBlocks(int count, const QDateTime &blockDate, double nVerificationProgress, SyncType synctype, SynchronizationState sync_state)
void pasteEntry(const SendCoinsRecipient &rv)
void updateFeeMinimizedLabel()
void accept() override
const PlatformStyle * platformStyle
std::unique_ptr< wallet::CCoinControl > m_coin_control
void coinControlClipboardQuantity()
void coinControlButtonClicked()
void coinControlClipboardAfterFee()
bool signWithExternalSigner(PartiallySignedTransaction &psbt, CMutableTransaction &mtx, bool &complete)
QWidget * setupTabChain(QWidget *prev)
Set up the tab chain manually, as Qt messes up the tab chain by default in some cases (issue https://...
bool PrepareSendText(QString &question_string, QString &informative_text, QString &detailed_text)
void sendButtonClicked(bool checked)
void setModel(WalletModel *model)
bool handlePaymentRequest(const SendCoinsRecipient &recipient)
void setBalance(const interfaces::WalletBalances &balances)
void coinControlClipboardAmount()
void setAddress(const QString &address)
void coinControlClipboardChange()
std::unique_ptr< WalletModelTransaction > m_current_transaction
void removeEntry(SendCoinsEntry *entry)
void reject() override
void coinControlClipboardBytes()
void message(const QString &title, const QString &message, unsigned int style)
SendCoinsDialog(const PlatformStyle *platformStyle, QWidget *parent=nullptr)
void coinsSent(const uint256 &txid)
void on_buttonMinimizeFee_clicked()
void coinControlFeatureChanged(bool)
void minimizeFeeSection(bool fMinimize)
A single entry in the dialog for sending bitcoins.
void setFocus()
void setAddress(const QString &address)
bool isClear()
Return whether the entry is still empty and unedited.
void subtractFeeFromAmountChanged()
void useAvailableBalance(SendCoinsEntry *entry)
void setValue(const SendCoinsRecipient &value)
void setModel(WalletModel *model)
void removeEntry(SendCoinsEntry *entry)
void payAmountChanged()
void setAmount(const CAmount &amount)
QWidget * setupTabChain(QWidget *prev)
Set up the tab chain manually, as Qt messes up the tab chain by default in some cases (issue https://...
void clear()
bool validate(interfaces::Node &node)
void checkSubtractFeeFromAmount()
SendCoinsRecipient getValue()
SendConfirmationDialog(const QString &title, const QString &text, const QString &informative_text="", const QString &detailed_text="", int secDelay=SEND_CONFIRM_DELAY, bool enable_send=true, bool always_show_unsigned=true, QWidget *parent=nullptr)
QAbstractButton * m_psbt_button
QAbstractButton * yesButton
Interface to Bitcoin wallet from Qt view code.
Definition: walletmodel.h:48
AddressTableModel * getAddressTableModel() const
SendCoinsReturn prepareTransaction(WalletModelTransaction &transaction, const wallet::CCoinControl &coinControl)
void sendCoins(WalletModelTransaction &transaction)
CAmount getAvailableBalance(const wallet::CCoinControl *control)
bool isMultiwallet() const
interfaces::Wallet & wallet() const
Definition: walletmodel.h:138
OptionsModel * getOptionsModel() const
interfaces::Node & node() const
Definition: walletmodel.h:137
UnlockContext requestUnlock()
void balanceChanged(const interfaces::WalletBalances &balances)
interfaces::WalletBalances getCachedBalance() const
QString getWalletName() const
@ AmountWithFeeExceedsBalance
Definition: walletmodel.h:61
@ TransactionCreationFailed
Definition: walletmodel.h:63
@ AmountExceedsBalance
Definition: walletmodel.h:60
@ DuplicateAddress
Definition: walletmodel.h:62
virtual bool isLegacy()=0
Return whether is a legacy wallet.
virtual TransactionError fillPSBT(int sighash_type, bool sign, bool bip32derivs, size_t *n_signed, PartiallySignedTransaction &psbtx, bool &complete)=0
Fill PSBT.
virtual CAmount getRequiredFee(unsigned int tx_bytes)=0
Get required fee.
virtual unsigned int getConfirmTarget()=0
Get tx confirm target.
virtual bool hasExternalSigner()=0
virtual CAmount getDefaultMaxTxFee()=0
Get max tx fee.
virtual bool isSpendable(const CTxDestination &dest)=0
Return whether wallet has private key.
virtual bool privateKeysDisabled()=0
virtual CAmount getMinimumFee(unsigned int tx_bytes, const wallet::CCoinControl &coin_control, int *returned_target, FeeReason *reason)=0
Get minimum fee.
Coin Control Features.
Definition: coincontrol.h:81
bool HasSelected() const
Returns true if there are pre-selected inputs.
Definition: coincontrol.cpp:15
bool m_allow_other_inputs
If true, the selection process can add extra unselected inputs from the wallet while requires all sel...
Definition: coincontrol.h:91
SyncType
Definition: clientmodel.h:39
#define ASYMP_UTF8
TransactionError
Definition: error.h:22
@ SIGHASH_ALL
Definition: interpreter.h:30
CTxDestination DecodeDestination(const std::string &str, std::string &error_msg, std::vector< int > *error_locations)
Definition: key_io.cpp:292
Utility functions used by the Bitcoin Qt UI.
Definition: bitcoingui.h:60
QString HtmlEscape(const QString &str, bool fMultiLine)
Definition: guiutil.cpp:248
void ShowModalDialogAsynchronously(QDialog *dialog)
Shows a QDialog instance asynchronously, and deletes it on close.
Definition: guiutil.cpp:1001
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:312
QString formatNiceTimeOffset(qint64 secs)
Definition: guiutil.cpp:779
constexpr auto dialog_flags
Definition: guiutil.h:60
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....
Definition: guiutil.h:391
int TextWidth(const QFontMetrics &fm, const QString &text)
Returns the distance in pixels appropriate for drawing a subsequent character after text.
Definition: guiutil.cpp:907
void setupAddressWidget(QValidatedLineEdit *widget, QWidget *parent)
Definition: guiutil.cpp:130
void setClipboard(const QString &str)
Definition: guiutil.cpp:660
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:1060
constexpr CAmount DEFAULT_PAY_TX_FEE
-paytxfee default
Definition: wallet.h:105
FeeReason
Definition: fees.h:60
static CTransactionRef MakeTransactionRef(Tx &&txIn)
Definition: transaction.h:424
std::shared_ptr< const CTransaction > CTransactionRef
Definition: transaction.h:423
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:495
int getConfTargetForIndex(int index)
int getIndexForConfTarget(int target)
static constexpr std::array confTargets
#define SEND_CONFIRM_DELAY
A mutable version of CTransaction.
Definition: transaction.h:378
A version of CTransaction with the PSBT format.
Definition: psbt.h:947
Collection of wallet balances.
Definition: wallet.h:372
static int count
std::string EncodeBase64(Span< const unsigned char > input)
assert(!tx.IsCoinBase())
SynchronizationState
Current sync state passed to tip changed callbacks.
Definition: validation.h:80