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 #include <config/bitcoin-config.h> // IWYU pragma: keep
6 
7 #include <qt/sendcoinsdialog.h>
8 #include <qt/forms/ui_sendcoinsdialog.h>
9 
10 #include <qt/addresstablemodel.h>
11 #include <qt/bitcoinunits.h>
12 #include <qt/clientmodel.h>
13 #include <qt/coincontroldialog.h>
14 #include <qt/guiutil.h>
15 #include <qt/optionsmodel.h>
16 #include <qt/platformstyle.h>
17 #include <qt/sendcoinsentry.h>
18 
19 #include <chainparams.h>
20 #include <interfaces/node.h>
21 #include <key_io.h>
22 #include <node/interface_ui.h>
23 #include <policy/fees.h>
24 #include <txmempool.h>
25 #include <validation.h>
26 #include <wallet/coincontrol.h>
27 #include <wallet/fees.h>
28 #include <wallet/wallet.h>
29 
30 #include <array>
31 #include <chrono>
32 #include <fstream>
33 #include <memory>
34 
35 #include <QFontMetrics>
36 #include <QScrollBar>
37 #include <QSettings>
38 #include <QTextDocument>
39 
42 
43 static constexpr std::array confTargets{2, 4, 6, 12, 24, 48, 144, 504, 1008};
44 int getConfTargetForIndex(int index) {
45  if (index+1 > static_cast<int>(confTargets.size())) {
46  return confTargets.back();
47  }
48  if (index < 0) {
49  return confTargets[0];
50  }
51  return confTargets[index];
52 }
53 int getIndexForConfTarget(int target) {
54  for (unsigned int i = 0; i < confTargets.size(); i++) {
55  if (confTargets[i] >= target) {
56  return i;
57  }
58  }
59  return confTargets.size() - 1;
60 }
61 
62 SendCoinsDialog::SendCoinsDialog(const PlatformStyle *_platformStyle, QWidget *parent) :
63  QDialog(parent, GUIUtil::dialog_flags),
64  ui(new Ui::SendCoinsDialog),
65  m_coin_control(new CCoinControl),
66  platformStyle(_platformStyle)
67 {
68  ui->setupUi(this);
69 
70  if (!_platformStyle->getImagesOnButtons()) {
71  ui->addButton->setIcon(QIcon());
72  ui->clearButton->setIcon(QIcon());
73  ui->sendButton->setIcon(QIcon());
74  } else {
75  ui->addButton->setIcon(_platformStyle->SingleColorIcon(":/icons/add"));
76  ui->clearButton->setIcon(_platformStyle->SingleColorIcon(":/icons/remove"));
77  ui->sendButton->setIcon(_platformStyle->SingleColorIcon(":/icons/send"));
78  }
79 
80  GUIUtil::setupAddressWidget(ui->lineEditCoinControlChange, this);
81 
82  addEntry();
83 
84  connect(ui->addButton, &QPushButton::clicked, this, &SendCoinsDialog::addEntry);
85  connect(ui->clearButton, &QPushButton::clicked, this, &SendCoinsDialog::clear);
86 
87  // Coin Control
88  connect(ui->pushButtonCoinControl, &QPushButton::clicked, this, &SendCoinsDialog::coinControlButtonClicked);
89  connect(ui->checkBoxCoinControlChange, &QCheckBox::stateChanged, this, &SendCoinsDialog::coinControlChangeChecked);
90  connect(ui->lineEditCoinControlChange, &QValidatedLineEdit::textEdited, this, &SendCoinsDialog::coinControlChangeEdited);
91 
92  // Coin Control: clipboard actions
93  QAction *clipboardQuantityAction = new QAction(tr("Copy quantity"), this);
94  QAction *clipboardAmountAction = new QAction(tr("Copy amount"), this);
95  QAction *clipboardFeeAction = new QAction(tr("Copy fee"), this);
96  QAction *clipboardAfterFeeAction = new QAction(tr("Copy after fee"), this);
97  QAction *clipboardBytesAction = new QAction(tr("Copy bytes"), 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(clipboardChangeAction, &QAction::triggered, this, &SendCoinsDialog::coinControlClipboardChange);
105  ui->labelCoinControlQuantity->addAction(clipboardQuantityAction);
106  ui->labelCoinControlAmount->addAction(clipboardAmountAction);
107  ui->labelCoinControlFee->addAction(clipboardFeeAction);
108  ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction);
109  ui->labelCoinControlBytes->addAction(clipboardBytesAction);
110  ui->labelCoinControlChange->addAction(clipboardChangeAction);
111 
112  // init transaction fee section
113  QSettings settings;
114  if (!settings.contains("fFeeSectionMinimized"))
115  settings.setValue("fFeeSectionMinimized", true);
116  if (!settings.contains("nFeeRadio") && settings.contains("nTransactionFee") && settings.value("nTransactionFee").toLongLong() > 0) // compatibility
117  settings.setValue("nFeeRadio", 1); // custom
118  if (!settings.contains("nFeeRadio"))
119  settings.setValue("nFeeRadio", 0); // recommended
120  if (!settings.contains("nSmartFeeSliderPosition"))
121  settings.setValue("nSmartFeeSliderPosition", 0);
122  if (!settings.contains("nTransactionFee"))
123  settings.setValue("nTransactionFee", (qint64)DEFAULT_PAY_TX_FEE);
124  ui->groupFee->setId(ui->radioSmartFee, 0);
125  ui->groupFee->setId(ui->radioCustomFee, 1);
126  ui->groupFee->button((int)std::max(0, std::min(1, settings.value("nFeeRadio").toInt())))->setChecked(true);
127  ui->customFee->SetAllowEmpty(false);
128  ui->customFee->setValue(settings.value("nTransactionFee").toLongLong());
129  minimizeFeeSection(settings.value("fFeeSectionMinimized").toBool());
130 
131  GUIUtil::ExceptionSafeConnect(ui->sendButton, &QPushButton::clicked, this, &SendCoinsDialog::sendButtonClicked);
132 }
133 
135 {
136  this->clientModel = _clientModel;
137 
138  if (_clientModel) {
140  }
141 }
142 
144 {
145  this->model = _model;
146 
147  if(_model && _model->getOptionsModel())
148  {
149  for(int i = 0; i < ui->entries->count(); ++i)
150  {
151  SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
152  if(entry)
153  {
154  entry->setModel(_model);
155  }
156  }
157 
160  refreshBalance();
161 
162  // Coin Control
165  ui->frameCoinControl->setVisible(_model->getOptionsModel()->getCoinControlFeatures());
167 
168  // fee section
169  for (const int n : confTargets) {
170  ui->confTargetSelector->addItem(tr("%1 (%2 blocks)").arg(GUIUtil::formatNiceTimeOffset(n*Params().GetConsensus().nPowTargetSpacing)).arg(n));
171  }
172  connect(ui->confTargetSelector, qOverload<int>(&QComboBox::currentIndexChanged), this, &SendCoinsDialog::updateSmartFeeLabel);
173  connect(ui->confTargetSelector, qOverload<int>(&QComboBox::currentIndexChanged), this, &SendCoinsDialog::coinControlUpdateLabels);
174 
175 #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
176  connect(ui->groupFee, &QButtonGroup::idClicked, this, &SendCoinsDialog::updateFeeSectionControls);
177  connect(ui->groupFee, &QButtonGroup::idClicked, this, &SendCoinsDialog::coinControlUpdateLabels);
178 #else
179  connect(ui->groupFee, qOverload<int>(&QButtonGroup::buttonClicked), this, &SendCoinsDialog::updateFeeSectionControls);
180  connect(ui->groupFee, qOverload<int>(&QButtonGroup::buttonClicked), this, &SendCoinsDialog::coinControlUpdateLabels);
181 #endif
182 
184  connect(ui->optInRBF, &QCheckBox::stateChanged, this, &SendCoinsDialog::updateSmartFeeLabel);
185  connect(ui->optInRBF, &QCheckBox::stateChanged, this, &SendCoinsDialog::coinControlUpdateLabels);
186  CAmount requiredFee = model->wallet().getRequiredFee(1000);
187  ui->customFee->SetMinValue(requiredFee);
188  if (ui->customFee->value() < requiredFee) {
189  ui->customFee->setValue(requiredFee);
190  }
191  ui->customFee->setSingleStep(requiredFee);
194 
195  // set default rbf checkbox state
196  ui->optInRBF->setCheckState(Qt::Checked);
197 
198  if (model->wallet().hasExternalSigner()) {
199  //: "device" usually means a hardware wallet.
200  ui->sendButton->setText(tr("Sign on device"));
201  if (model->getOptionsModel()->hasSigner()) {
202  ui->sendButton->setEnabled(true);
203  ui->sendButton->setToolTip(tr("Connect your hardware wallet first."));
204  } else {
205  ui->sendButton->setEnabled(false);
206  //: "External signer" means using devices such as hardware wallets.
207  ui->sendButton->setToolTip(tr("Set external signer script path in Options -> Wallet"));
208  }
209  } else if (model->wallet().privateKeysDisabled()) {
210  ui->sendButton->setText(tr("Cr&eate Unsigned"));
211  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));
212  }
213 
214  // set the smartfee-sliders default value (wallets default conf.target or last stored value)
215  QSettings settings;
216  if (settings.value("nSmartFeeSliderPosition").toInt() != 0) {
217  // migrate nSmartFeeSliderPosition to nConfTarget
218  // nConfTarget is available since 0.15 (replaced nSmartFeeSliderPosition)
219  int nConfirmTarget = 25 - settings.value("nSmartFeeSliderPosition").toInt(); // 25 == old slider range
220  settings.setValue("nConfTarget", nConfirmTarget);
221  settings.remove("nSmartFeeSliderPosition");
222  }
223  if (settings.value("nConfTarget").toInt() == 0)
224  ui->confTargetSelector->setCurrentIndex(getIndexForConfTarget(model->wallet().getConfirmTarget()));
225  else
226  ui->confTargetSelector->setCurrentIndex(getIndexForConfTarget(settings.value("nConfTarget").toInt()));
227  }
228 }
229 
231 {
232  QSettings settings;
233  settings.setValue("fFeeSectionMinimized", fFeeMinimized);
234  settings.setValue("nFeeRadio", ui->groupFee->checkedId());
235  settings.setValue("nConfTarget", getConfTargetForIndex(ui->confTargetSelector->currentIndex()));
236  settings.setValue("nTransactionFee", (qint64)ui->customFee->value());
237 
238  delete ui;
239 }
240 
241 bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informative_text, QString& detailed_text)
242 {
243  QList<SendCoinsRecipient> recipients;
244  bool valid = true;
245 
246  for(int i = 0; i < ui->entries->count(); ++i)
247  {
248  SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
249  if(entry)
250  {
251  if(entry->validate(model->node()))
252  {
253  recipients.append(entry->getValue());
254  }
255  else if (valid)
256  {
257  ui->scrollArea->ensureWidgetVisible(entry);
258  valid = false;
259  }
260  }
261  }
262 
263  if(!valid || recipients.isEmpty())
264  {
265  return false;
266  }
267 
268  fNewRecipientAllowed = false;
270  if(!ctx.isValid())
271  {
272  // Unlock wallet was cancelled
273  fNewRecipientAllowed = true;
274  return false;
275  }
276 
277  // prepare transaction for getting txFee earlier
278  m_current_transaction = std::make_unique<WalletModelTransaction>(recipients);
279  WalletModel::SendCoinsReturn prepareStatus;
280 
282 
283  CCoinControl coin_control = *m_coin_control;
284  coin_control.m_allow_other_inputs = !coin_control.HasSelected(); // future, could introduce a checkbox to customize this value.
285  prepareStatus = model->prepareTransaction(*m_current_transaction, coin_control);
286 
287  // process prepareStatus and on error generate message shown to user
288  processSendCoinsReturn(prepareStatus,
290 
291  if(prepareStatus.status != WalletModel::OK) {
292  fNewRecipientAllowed = true;
293  return false;
294  }
295 
296  CAmount txFee = m_current_transaction->getTransactionFee();
297  QStringList formatted;
298  for (const SendCoinsRecipient &rcp : m_current_transaction->getRecipients())
299  {
300  // generate amount string with wallet name in case of multiwallet
301  QString amount = BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
302  if (model->isMultiwallet()) {
303  amount = tr("%1 from wallet '%2'").arg(amount, GUIUtil::HtmlEscape(model->getWalletName()));
304  }
305 
306  // generate address string
307  QString address = rcp.address;
308 
309  QString recipientElement;
310 
311  {
312  if(rcp.label.length() > 0) // label with address
313  {
314  recipientElement.append(tr("%1 to '%2'").arg(amount, GUIUtil::HtmlEscape(rcp.label)));
315  recipientElement.append(QString(" (%1)").arg(address));
316  }
317  else // just address
318  {
319  recipientElement.append(tr("%1 to %2").arg(amount, address));
320  }
321  }
322  formatted.append(recipientElement);
323  }
324 
325  /*: Message displayed when attempting to create a transaction. Cautionary text to prompt the user to verify
326  that the displayed transaction details represent the transaction the user intends to create. */
327  question_string.append(tr("Do you want to create this transaction?"));
328  question_string.append("<br /><span style='font-size:10pt;'>");
330  /*: Text to inform a user attempting to create a transaction of their current options. At this stage,
331  a user can only create a PSBT. This string is displayed when private keys are disabled and an external
332  signer is not available. */
333  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));
334  } else if (model->getOptionsModel()->getEnablePSBTControls()) {
335  /*: Text to inform a user attempting to create a transaction of their current options. At this stage,
336  a user can send their transaction or create a PSBT. This string is displayed when both private keys
337  and PSBT controls are enabled. */
338  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));
339  } else {
340  /*: Text to prompt a user to review the details of the transaction they are attempting to send. */
341  question_string.append(tr("Please, review your transaction."));
342  }
343  question_string.append("</span>%1");
344 
345  if(txFee > 0)
346  {
347  // append fee string if a fee is required
348  question_string.append("<hr /><b>");
349  question_string.append(tr("Transaction fee"));
350  question_string.append("</b>");
351 
352  // append transaction size
353  //: When reviewing a newly created PSBT (via Send flow), the transaction fee is shown, with "virtual size" of the transaction displayed for context
354  question_string.append(" (" + tr("%1 kvB", "PSBT transaction creation").arg((double)m_current_transaction->getTransactionSize() / 1000, 0, 'g', 3) + "): ");
355 
356  // append transaction fee value
357  question_string.append("<span style='color:#aa0000; font-weight:bold;'>");
358  question_string.append(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), txFee));
359  question_string.append("</span><br />");
360 
361  // append RBF message according to transaction's signalling
362  question_string.append("<span style='font-size:10pt; font-weight:normal;'>");
363  if (ui->optInRBF->isChecked()) {
364  question_string.append(tr("You can increase the fee later (signals Replace-By-Fee, BIP-125)."));
365  } else {
366  question_string.append(tr("Not signalling Replace-By-Fee, BIP-125."));
367  }
368  question_string.append("</span>");
369  }
370 
371  // add total amount in all subdivision units
372  question_string.append("<hr />");
373  CAmount totalAmount = m_current_transaction->getTotalTransactionAmount() + txFee;
374  QStringList alternativeUnits;
375  for (const BitcoinUnit u : BitcoinUnits::availableUnits()) {
376  if(u != model->getOptionsModel()->getDisplayUnit())
377  alternativeUnits.append(BitcoinUnits::formatHtmlWithUnit(u, totalAmount));
378  }
379  question_string.append(QString("<b>%1</b>: <b>%2</b>").arg(tr("Total Amount"))
381  question_string.append(QString("<br /><span style='font-size:10pt; font-weight:normal;'>(=%1)</span>")
382  .arg(alternativeUnits.join(" " + tr("or") + " ")));
383 
384  if (formatted.size() > 1) {
385  question_string = question_string.arg("");
386  informative_text = tr("To review recipient list click \"Show Details…\"");
387  detailed_text = formatted.join("\n\n");
388  } else {
389  question_string = question_string.arg("<br /><br />" + formatted.at(0));
390  }
391 
392  return true;
393 }
394 
396 {
397  // Serialize the PSBT
398  DataStream ssTx{};
399  ssTx << psbtx;
400  GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str());
401  QMessageBox msgBox(this);
402  //: Caption of "PSBT has been copied" messagebox
403  msgBox.setText(tr("Unsigned Transaction", "PSBT copied"));
404  msgBox.setInformativeText(tr("The PSBT has been copied to the clipboard. You can also save it."));
405  msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard);
406  msgBox.setDefaultButton(QMessageBox::Discard);
407  msgBox.setObjectName("psbt_copied_message");
408  switch (msgBox.exec()) {
409  case QMessageBox::Save: {
410  QString selectedFilter;
411  QString fileNameSuggestion = "";
412  bool first = true;
413  for (const SendCoinsRecipient &rcp : m_current_transaction->getRecipients()) {
414  if (!first) {
415  fileNameSuggestion.append(" - ");
416  }
417  QString labelOrAddress = rcp.label.isEmpty() ? rcp.address : rcp.label;
418  QString amount = BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);
419  fileNameSuggestion.append(labelOrAddress + "-" + amount);
420  first = false;
421  }
422  fileNameSuggestion.append(".psbt");
423  QString filename = GUIUtil::getSaveFileName(this,
424  tr("Save Transaction Data"), fileNameSuggestion,
425  //: Expanded name of the binary PSBT file format. See: BIP 174.
426  tr("Partially Signed Transaction (Binary)") + QLatin1String(" (*.psbt)"), &selectedFilter);
427  if (filename.isEmpty()) {
428  return;
429  }
430  std::ofstream out{filename.toLocal8Bit().data(), std::ofstream::out | std::ofstream::binary};
431  out << ssTx.str();
432  out.close();
433  //: Popup message when a PSBT has been saved to a file
434  Q_EMIT message(tr("PSBT saved"), tr("PSBT saved to disk"), CClientUIInterface::MSG_INFORMATION);
435  break;
436  }
437  case QMessageBox::Discard:
438  break;
439  default:
440  assert(false);
441  } // msgBox.exec()
442 }
443 
445  TransactionError err;
446  try {
447  err = model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/true, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete);
448  } catch (const std::runtime_error& e) {
449  QMessageBox::critical(nullptr, tr("Sign failed"), e.what());
450  return false;
451  }
453  //: "External signer" means using devices such as hardware wallets.
454  const QString msg = tr("External signer not found");
455  QMessageBox::critical(nullptr, msg, msg);
456  return false;
457  }
459  //: "External signer" means using devices such as hardware wallets.
460  const QString msg = tr("External signer failure");
461  QMessageBox::critical(nullptr, msg, msg);
462  return false;
463  }
464  if (err != TransactionError::OK) {
465  tfm::format(std::cerr, "Failed to sign PSBT");
467  return false;
468  }
469  // fillPSBT does not always properly finalize
470  complete = FinalizeAndExtractPSBT(psbtx, mtx);
471  return true;
472 }
473 
474 void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
475 {
476  if(!model || !model->getOptionsModel())
477  return;
478 
479  QString question_string, informative_text, detailed_text;
480  if (!PrepareSendText(question_string, informative_text, detailed_text)) return;
482 
483  const QString confirmation = tr("Confirm send coins");
484  const bool enable_send{!model->wallet().privateKeysDisabled() || model->wallet().hasExternalSigner()};
485  const bool always_show_unsigned{model->getOptionsModel()->getEnablePSBTControls()};
486  auto confirmationDialog = new SendConfirmationDialog(confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, enable_send, always_show_unsigned, this);
487  confirmationDialog->setAttribute(Qt::WA_DeleteOnClose);
488  // TODO: Replace QDialog::exec() with safer QDialog::show().
489  const auto retval = static_cast<QMessageBox::StandardButton>(confirmationDialog->exec());
490 
491  if(retval != QMessageBox::Yes && retval != QMessageBox::Save)
492  {
493  fNewRecipientAllowed = true;
494  return;
495  }
496 
497  bool send_failure = false;
498  if (retval == QMessageBox::Save) {
499  // "Create Unsigned" clicked
501  PartiallySignedTransaction psbtx(mtx);
502  bool complete = false;
503  // Fill without signing
504  TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete);
505  assert(!complete);
507 
508  // Copy PSBT to clipboard and offer to save
509  presentPSBT(psbtx);
510  } else {
511  // "Send" clicked
513  bool broadcast = true;
514  if (model->wallet().hasExternalSigner()) {
516  PartiallySignedTransaction psbtx(mtx);
517  bool complete = false;
518  // Always fill without signing first. This prevents an external signer
519  // from being called prematurely and is not expensive.
520  TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, /*n_signed=*/nullptr, psbtx, complete);
521  assert(!complete);
523  send_failure = !signWithExternalSigner(psbtx, mtx, complete);
524  // Don't broadcast when user rejects it on the device or there's a failure:
525  broadcast = complete && !send_failure;
526  if (!send_failure) {
527  // A transaction signed with an external signer is not always complete,
528  // e.g. in a multisig wallet.
529  if (complete) {
530  // Prepare transaction for broadcast transaction if complete
531  const CTransactionRef tx = MakeTransactionRef(mtx);
532  m_current_transaction->setWtx(tx);
533  } else {
534  presentPSBT(psbtx);
535  }
536  }
537  }
538 
539  // Broadcast the transaction, unless an external signer was used and it
540  // failed, or more signatures are needed.
541  if (broadcast) {
542  // now send the prepared transaction
544  Q_EMIT coinsSent(m_current_transaction->getWtx()->GetHash());
545  }
546  }
547  if (!send_failure) {
548  accept();
549  m_coin_control->UnSelectAll();
551  }
552  fNewRecipientAllowed = true;
553  m_current_transaction.reset();
554 }
555 
557 {
558  m_current_transaction.reset();
559 
560  // Clear coin control settings
561  m_coin_control->UnSelectAll();
562  ui->checkBoxCoinControlChange->setChecked(false);
563  ui->lineEditCoinControlChange->clear();
565 
566  // Remove entries until only one left
567  while(ui->entries->count())
568  {
569  ui->entries->takeAt(0)->widget()->deleteLater();
570  }
571  addEntry();
572 
574 }
575 
577 {
578  clear();
579 }
580 
582 {
583  clear();
584 }
585 
587 {
588  SendCoinsEntry *entry = new SendCoinsEntry(platformStyle, this);
589  entry->setModel(model);
590  ui->entries->addWidget(entry);
595 
596  // Focus the field, so that entry can start immediately
597  entry->clear();
598  entry->setFocus();
599  ui->scrollAreaWidgetContents->resize(ui->scrollAreaWidgetContents->sizeHint());
600 
601  // Scroll to the newly added entry on a QueuedConnection because Qt doesn't
602  // adjust the scroll area and scrollbar immediately when the widget is added.
603  // Invoking on a DirectConnection will only scroll to the second-to-last entry.
604  QMetaObject::invokeMethod(ui->scrollArea, [this] {
605  if (ui->scrollArea->verticalScrollBar()) {
606  ui->scrollArea->verticalScrollBar()->setValue(ui->scrollArea->verticalScrollBar()->maximum());
607  }
608  }, Qt::QueuedConnection);
609 
610  updateTabsAndLabels();
611  return entry;
612 }
613 
615 {
616  setupTabChain(nullptr);
618 }
619 
621 {
622  entry->hide();
623 
624  // If the last entry is about to be removed add an empty one
625  if (ui->entries->count() == 1)
626  addEntry();
627 
628  entry->deleteLater();
629 
631 }
632 
633 QWidget *SendCoinsDialog::setupTabChain(QWidget *prev)
634 {
635  for(int i = 0; i < ui->entries->count(); ++i)
636  {
637  SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
638  if(entry)
639  {
640  prev = entry->setupTabChain(prev);
641  }
642  }
643  QWidget::setTabOrder(prev, ui->sendButton);
644  QWidget::setTabOrder(ui->sendButton, ui->clearButton);
645  QWidget::setTabOrder(ui->clearButton, ui->addButton);
646  return ui->addButton;
647 }
648 
649 void SendCoinsDialog::setAddress(const QString &address)
650 {
651  SendCoinsEntry *entry = nullptr;
652  // Replace the first entry if it is still unused
653  if(ui->entries->count() == 1)
654  {
655  SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
656  if(first->isClear())
657  {
658  entry = first;
659  }
660  }
661  if(!entry)
662  {
663  entry = addEntry();
664  }
665 
666  entry->setAddress(address);
667 }
668 
670 {
672  return;
673 
674  SendCoinsEntry *entry = nullptr;
675  // Replace the first entry if it is still unused
676  if(ui->entries->count() == 1)
677  {
678  SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(0)->widget());
679  if(first->isClear())
680  {
681  entry = first;
682  }
683  }
684  if(!entry)
685  {
686  entry = addEntry();
687  }
688 
689  entry->setValue(rv);
691 }
692 
694 {
695  // Just paste the entry, all pre-checks
696  // are done in paymentserver.cpp.
697  pasteEntry(rv);
698  return true;
699 }
700 
702 {
703  if(model && model->getOptionsModel())
704  {
705  CAmount balance = balances.balance;
706  if (model->wallet().hasExternalSigner()) {
707  ui->labelBalanceName->setText(tr("External balance:"));
708  } else if (model->wallet().isLegacy() && model->wallet().privateKeysDisabled()) {
709  balance = balances.watch_only_balance;
710  ui->labelBalanceName->setText(tr("Watch-only balance:"));
711  }
712  ui->labelBalance->setText(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), balance));
713  }
714 }
715 
717 {
719  ui->customFee->setDisplayUnit(model->getOptionsModel()->getDisplayUnit());
721 }
722 
723 void SendCoinsDialog::processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg)
724 {
725  QPair<QString, CClientUIInterface::MessageBoxFlags> msgParams;
726  // Default to a warning message, override if error message is needed
727  msgParams.second = CClientUIInterface::MSG_WARNING;
728 
729  // This comment is specific to SendCoinsDialog usage of WalletModel::SendCoinsReturn.
730  // All status values are used only in WalletModel::prepareTransaction()
731  switch(sendCoinsReturn.status)
732  {
734  msgParams.first = tr("The recipient address is not valid. Please recheck.");
735  break;
737  msgParams.first = tr("The amount to pay must be larger than 0.");
738  break;
740  msgParams.first = tr("The amount exceeds your balance.");
741  break;
743  msgParams.first = tr("The total exceeds your balance when the %1 transaction fee is included.").arg(msgArg);
744  break;
746  msgParams.first = tr("Duplicate address found: addresses should only be used once each.");
747  break;
749  msgParams.first = tr("Transaction creation failed!");
750  msgParams.second = CClientUIInterface::MSG_ERROR;
751  break;
753  msgParams.first = tr("A fee higher than %1 is considered an absurdly high fee.").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), model->wallet().getDefaultMaxTxFee()));
754  break;
755  // included to prevent a compiler warning.
756  case WalletModel::OK:
757  default:
758  return;
759  }
760 
761  Q_EMIT message(tr("Send Coins"), msgParams.first, msgParams.second);
762 }
763 
765 {
766  ui->labelFeeMinimized->setVisible(fMinimize);
767  ui->buttonChooseFee ->setVisible(fMinimize);
768  ui->buttonMinimizeFee->setVisible(!fMinimize);
769  ui->frameFeeSelection->setVisible(!fMinimize);
770  ui->horizontalLayoutSmartFee->setContentsMargins(0, (fMinimize ? 0 : 6), 0, 0);
771  fFeeMinimized = fMinimize;
772 }
773 
775 {
776  minimizeFeeSection(false);
777 }
778 
780 {
782  minimizeFeeSection(true);
783 }
784 
786 {
787  // Include watch-only for wallets without private key
789 
790  // Same behavior as send: if we have selected coins, only obtain their available balance.
791  // Copy to avoid modifying the member's data.
792  CCoinControl coin_control = *m_coin_control;
793  coin_control.m_allow_other_inputs = !coin_control.HasSelected();
794 
795  // Calculate available amount to send.
796  CAmount amount = model->getAvailableBalance(&coin_control);
797  for (int i = 0; i < ui->entries->count(); ++i) {
798  SendCoinsEntry* e = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
799  if (e && !e->isHidden() && e != entry) {
800  amount -= e->getValue().amount;
801  }
802  }
803 
804  if (amount > 0) {
806  entry->setAmount(amount);
807  } else {
808  entry->setAmount(0);
809  }
810 }
811 
813 {
814  ui->confTargetSelector ->setEnabled(ui->radioSmartFee->isChecked());
815  ui->labelSmartFee ->setEnabled(ui->radioSmartFee->isChecked());
816  ui->labelSmartFee2 ->setEnabled(ui->radioSmartFee->isChecked());
817  ui->labelSmartFee3 ->setEnabled(ui->radioSmartFee->isChecked());
818  ui->labelFeeEstimation ->setEnabled(ui->radioSmartFee->isChecked());
819  ui->labelCustomFeeWarning ->setEnabled(ui->radioCustomFee->isChecked());
820  ui->labelCustomPerKilobyte ->setEnabled(ui->radioCustomFee->isChecked());
821  ui->customFee ->setEnabled(ui->radioCustomFee->isChecked());
822 }
823 
825 {
826  if(!model || !model->getOptionsModel())
827  return;
828 
829  if (ui->radioSmartFee->isChecked())
830  ui->labelFeeMinimized->setText(ui->labelSmartFee->text());
831  else {
832  ui->labelFeeMinimized->setText(tr("%1/kvB").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), ui->customFee->value())));
833  }
834 }
835 
837 {
838  if (ui->radioCustomFee->isChecked()) {
839  m_coin_control->m_feerate = CFeeRate(ui->customFee->value());
840  } else {
841  m_coin_control->m_feerate.reset();
842  }
843  // Avoid using global defaults when sending money from the GUI
844  // Either custom fee will be used or if not selected, the confirmation target from dropdown box
845  m_coin_control->m_confirm_target = getConfTargetForIndex(ui->confTargetSelector->currentIndex());
846  m_coin_control->m_signal_bip125_rbf = ui->optInRBF->isChecked();
847  // Include watch-only for wallets without private key
849 }
850 
851 void SendCoinsDialog::updateNumberOfBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, SyncType synctype, SynchronizationState sync_state) {
852  if (sync_state == SynchronizationState::POST_INIT) {
854  }
855 }
856 
858 {
859  if(!model || !model->getOptionsModel())
860  return;
862  m_coin_control->m_feerate.reset(); // Explicitly use only fee estimation rate for smart fee labels
863  int returned_target;
864  FeeReason reason;
865  CFeeRate feeRate = CFeeRate(model->wallet().getMinimumFee(1000, *m_coin_control, &returned_target, &reason));
866 
867  ui->labelSmartFee->setText(tr("%1/kvB").arg(BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), feeRate.GetFeePerK())));
868 
869  if (reason == FeeReason::FALLBACK) {
870  ui->labelSmartFee2->show(); // (Smart fee not initialized yet. This usually takes a few blocks...)
871  ui->labelFeeEstimation->setText("");
872  ui->fallbackFeeWarningLabel->setVisible(true);
873  int lightness = ui->fallbackFeeWarningLabel->palette().color(QPalette::WindowText).lightness();
874  QColor warning_colour(255 - (lightness / 5), 176 - (lightness / 3), 48 - (lightness / 14));
875  ui->fallbackFeeWarningLabel->setStyleSheet("QLabel { color: " + warning_colour.name() + "; }");
876  ui->fallbackFeeWarningLabel->setIndent(GUIUtil::TextWidth(QFontMetrics(ui->fallbackFeeWarningLabel->font()), "x"));
877  }
878  else
879  {
880  ui->labelSmartFee2->hide();
881  ui->labelFeeEstimation->setText(tr("Estimated to begin confirmation within %n block(s).", "", returned_target));
882  ui->fallbackFeeWarningLabel->setVisible(false);
883  }
884 
886 }
887 
888 // Coin Control: copy label "Quantity" to clipboard
890 {
891  GUIUtil::setClipboard(ui->labelCoinControlQuantity->text());
892 }
893 
894 // Coin Control: copy label "Amount" to clipboard
896 {
897  GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left(ui->labelCoinControlAmount->text().indexOf(" ")));
898 }
899 
900 // Coin Control: copy label "Fee" to clipboard
902 {
903  GUIUtil::setClipboard(ui->labelCoinControlFee->text().left(ui->labelCoinControlFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
904 }
905 
906 // Coin Control: copy label "After fee" to clipboard
908 {
909  GUIUtil::setClipboard(ui->labelCoinControlAfterFee->text().left(ui->labelCoinControlAfterFee->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
910 }
911 
912 // Coin Control: copy label "Bytes" to clipboard
914 {
915  GUIUtil::setClipboard(ui->labelCoinControlBytes->text().replace(ASYMP_UTF8, ""));
916 }
917 
918 // Coin Control: copy label "Change" to clipboard
920 {
921  GUIUtil::setClipboard(ui->labelCoinControlChange->text().left(ui->labelCoinControlChange->text().indexOf(" ")).replace(ASYMP_UTF8, ""));
922 }
923 
924 // Coin Control: settings menu - coin control enabled/disabled by user
926 {
927  ui->frameCoinControl->setVisible(checked);
928 
929  if (!checked && model) { // coin control features disabled
930  m_coin_control = std::make_unique<CCoinControl>();
931  }
932 
934 }
935 
936 // Coin Control: button inputs -> show actual coin control dialog
938 {
940  connect(dlg, &QDialog::finished, this, &SendCoinsDialog::coinControlUpdateLabels);
942 }
943 
944 // Coin Control: checkbox custom change address
946 {
947  if (state == Qt::Unchecked)
948  {
949  m_coin_control->destChange = CNoDestination();
950  ui->labelCoinControlChangeLabel->clear();
951  }
952  else
953  // use this to re-validate an already entered address
954  coinControlChangeEdited(ui->lineEditCoinControlChange->text());
955 
956  ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked));
957 }
958 
959 // Coin Control: custom change address changed
961 {
962  if (model && model->getAddressTableModel())
963  {
964  // Default to no change address until verified
965  m_coin_control->destChange = CNoDestination();
966  ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:red;}");
967 
968  const CTxDestination dest = DecodeDestination(text.toStdString());
969 
970  if (text.isEmpty()) // Nothing entered
971  {
972  ui->labelCoinControlChangeLabel->setText("");
973  }
974  else if (!IsValidDestination(dest)) // Invalid address
975  {
976  ui->labelCoinControlChangeLabel->setText(tr("Warning: Invalid Bitcoin address"));
977  }
978  else // Valid address
979  {
980  if (!model->wallet().isSpendable(dest)) {
981  ui->labelCoinControlChangeLabel->setText(tr("Warning: Unknown change address"));
982 
983  // confirmation dialog
984  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?"),
985  QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel);
986 
987  if(btnRetVal == QMessageBox::Yes)
988  m_coin_control->destChange = dest;
989  else
990  {
991  ui->lineEditCoinControlChange->setText("");
992  ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
993  ui->labelCoinControlChangeLabel->setText("");
994  }
995  }
996  else // Known change address
997  {
998  ui->labelCoinControlChangeLabel->setStyleSheet("QLabel{color:black;}");
999 
1000  // Query label
1001  QString associatedLabel = model->getAddressTableModel()->labelForAddress(text);
1002  if (!associatedLabel.isEmpty())
1003  ui->labelCoinControlChangeLabel->setText(associatedLabel);
1004  else
1005  ui->labelCoinControlChangeLabel->setText(tr("(no label)"));
1006 
1007  m_coin_control->destChange = dest;
1008  }
1009  }
1010  }
1011 }
1012 
1013 // Coin Control: update labels
1015 {
1016  if (!model || !model->getOptionsModel())
1017  return;
1018 
1020 
1021  // set pay amounts
1024 
1025  for(int i = 0; i < ui->entries->count(); ++i)
1026  {
1027  SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
1028  if(entry && !entry->isHidden())
1029  {
1030  SendCoinsRecipient rcp = entry->getValue();
1032  if (rcp.fSubtractFeeFromAmount)
1034  }
1035  }
1036 
1037  if (m_coin_control->HasSelected())
1038  {
1039  // actual coin control calculation
1041 
1042  // show coin control stats
1043  ui->labelCoinControlAutomaticallySelected->hide();
1044  ui->widgetCoinControl->show();
1045  }
1046  else
1047  {
1048  // hide coin control stats
1049  ui->labelCoinControlAutomaticallySelected->show();
1050  ui->widgetCoinControl->hide();
1051  ui->labelCoinControlInsuffFunds->hide();
1052  }
1053 }
1054 
1055 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)
1056  : QMessageBox(parent), secDelay(_secDelay), m_enable_send(enable_send)
1057 {
1058  setIcon(QMessageBox::Question);
1059  setWindowTitle(title); // On macOS, the window title is ignored (as required by the macOS Guidelines).
1060  setText(text);
1061  setInformativeText(informative_text);
1062  setDetailedText(detailed_text);
1063  setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel);
1064  if (always_show_unsigned || !enable_send) addButton(QMessageBox::Save);
1065  setDefaultButton(QMessageBox::Cancel);
1066  yesButton = button(QMessageBox::Yes);
1067  if (confirmButtonText.isEmpty()) {
1068  confirmButtonText = yesButton->text();
1069  }
1070  m_psbt_button = button(QMessageBox::Save);
1071  updateButtons();
1072  connect(&countDownTimer, &QTimer::timeout, this, &SendConfirmationDialog::countDown);
1073 }
1074 
1076 {
1077  updateButtons();
1078  countDownTimer.start(1s);
1079  return QMessageBox::exec();
1080 }
1081 
1083 {
1084  secDelay--;
1085  updateButtons();
1086 
1087  if(secDelay <= 0)
1088  {
1089  countDownTimer.stop();
1090  }
1091 }
1092 
1094 {
1095  if(secDelay > 0)
1096  {
1097  yesButton->setEnabled(false);
1098  yesButton->setText(confirmButtonText + (m_enable_send ? (" (" + QString::number(secDelay) + ")") : QString("")));
1099  if (m_psbt_button) {
1100  m_psbt_button->setEnabled(false);
1101  m_psbt_button->setText(m_psbt_button_text + " (" + QString::number(secDelay) + ")");
1102  }
1103  }
1104  else
1105  {
1106  yesButton->setEnabled(m_enable_send);
1107  yesButton->setText(confirmButtonText);
1108  if (m_psbt_button) {
1109  m_psbt_button->setEnabled(true);
1111  }
1112  }
1113 }
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:58
QString HtmlEscape(const QString &str, bool fMultiLine)
Definition: guiutil.cpp:250
void ShowModalDialogAsynchronously(QDialog *dialog)
Shows a QDialog instance asynchronously, and deletes it on close.
Definition: guiutil.cpp:1003
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:314
QString formatNiceTimeOffset(qint64 secs)
Definition: guiutil.cpp:781
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:909
void setupAddressWidget(QValidatedLineEdit *widget, QWidget *parent)
Definition: guiutil.cpp:132
void setClipboard(const QString &str)
Definition: guiutil.cpp:662
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