/ src / qt / sendcoinsdialog.cpp
sendcoinsdialog.cpp
   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)
   6  #include <config/bitcoin-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  
  42  using wallet::CCoinControl;
  43  using wallet::DEFAULT_PAY_TX_FEE;
  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  
 136  void SendCoinsDialog::setClientModel(ClientModel *_clientModel)
 137  {
 138      this->clientModel = _clientModel;
 139  
 140      if (_clientModel) {
 141          connect(_clientModel, &ClientModel::numBlocksChanged, this, &SendCoinsDialog::updateNumberOfBlocks);
 142      }
 143  }
 144  
 145  void SendCoinsDialog::setModel(WalletModel *_model)
 146  {
 147      this->model = _model;
 148  
 149      if(_model && _model->getOptionsModel())
 150      {
 151          for(int i = 0; i < ui->entries->count(); ++i)
 152          {
 153              SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(ui->entries->itemAt(i)->widget());
 154              if(entry)
 155              {
 156                  entry->setModel(_model);
 157              }
 158          }
 159  
 160          connect(_model, &WalletModel::balanceChanged, this, &SendCoinsDialog::setBalance);
 161          connect(_model->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &SendCoinsDialog::refreshBalance);
 162          refreshBalance();
 163  
 164          // Coin Control
 165          connect(_model->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &SendCoinsDialog::coinControlUpdateLabels);
 166          connect(_model->getOptionsModel(), &OptionsModel::coinControlFeaturesChanged, this, &SendCoinsDialog::coinControlFeatureChanged);
 167          ui->frameCoinControl->setVisible(_model->getOptionsModel()->getCoinControlFeatures());
 168          coinControlUpdateLabels();
 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  
 185          connect(ui->customFee, &BitcoinAmountField::valueChanged, this, &SendCoinsDialog::coinControlUpdateLabels);
 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);
 194          updateFeeSectionControls();
 195          updateSmartFeeLabel();
 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  
 232  SendCoinsDialog::~SendCoinsDialog()
 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;
 271      WalletModel::UnlockContext ctx(model->requestUnlock());
 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  
 283      updateCoinControlState();
 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,
 291          BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), m_current_transaction->getTransactionFee()));
 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;'>");
 331      if (model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner()) {
 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"))
 382          .arg(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), totalAmount)));
 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  
 397  void SendCoinsDialog::presentPSBT(PartiallySignedTransaction& psbtx)
 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  
 446  bool SendCoinsDialog::signWithExternalSigner(PartiallySignedTransaction& psbtx, CMutableTransaction& mtx, bool& complete) {
 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      }
 454      if (err == TransactionError::EXTERNAL_SIGNER_NOT_FOUND) {
 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      }
 460      if (err == TransactionError::EXTERNAL_SIGNER_FAILED) {
 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");
 468          processSendCoinsReturn(WalletModel::TransactionCreationFailed);
 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;
 483      assert(m_current_transaction);
 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
 502          CMutableTransaction mtx = CMutableTransaction{*(m_current_transaction->getWtx())};
 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);
 508          assert(err == TransactionError::OK);
 509  
 510          // Copy PSBT to clipboard and offer to save
 511          presentPSBT(psbtx);
 512      } else {
 513          // "Send" clicked
 514          assert(!model->wallet().privateKeysDisabled() || model->wallet().hasExternalSigner());
 515          bool broadcast = true;
 516          if (model->wallet().hasExternalSigner()) {
 517              CMutableTransaction mtx = CMutableTransaction{*(m_current_transaction->getWtx())};
 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);
 524              assert(err == TransactionError::OK);
 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
 545              model->sendCoins(*m_current_transaction);
 546              Q_EMIT coinsSent(m_current_transaction->getWtx()->GetHash());
 547          }
 548      }
 549      if (!send_failure) {
 550          accept();
 551          m_coin_control->UnSelectAll();
 552          coinControlUpdateLabels();
 553      }
 554      fNewRecipientAllowed = true;
 555      m_current_transaction.reset();
 556  }
 557  
 558  void SendCoinsDialog::clear()
 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();
 566      coinControlUpdateLabels();
 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  
 575      updateTabsAndLabels();
 576  }
 577  
 578  void SendCoinsDialog::reject()
 579  {
 580      clear();
 581  }
 582  
 583  void SendCoinsDialog::accept()
 584  {
 585      clear();
 586  }
 587  
 588  SendCoinsEntry *SendCoinsDialog::addEntry()
 589  {
 590      SendCoinsEntry *entry = new SendCoinsEntry(platformStyle, this);
 591      entry->setModel(model);
 592      ui->entries->addWidget(entry);
 593      connect(entry, &SendCoinsEntry::removeEntry, this, &SendCoinsDialog::removeEntry);
 594      connect(entry, &SendCoinsEntry::useAvailableBalance, this, &SendCoinsDialog::useAvailableBalance);
 595      connect(entry, &SendCoinsEntry::payAmountChanged, this, &SendCoinsDialog::coinControlUpdateLabels);
 596      connect(entry, &SendCoinsEntry::subtractFeeFromAmountChanged, this, &SendCoinsDialog::coinControlUpdateLabels);
 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  
 616  void SendCoinsDialog::updateTabsAndLabels()
 617  {
 618      setupTabChain(nullptr);
 619      coinControlUpdateLabels();
 620  }
 621  
 622  void SendCoinsDialog::removeEntry(SendCoinsEntry* entry)
 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  
 632      updateTabsAndLabels();
 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  
 671  void SendCoinsDialog::pasteEntry(const SendCoinsRecipient &rv)
 672  {
 673      if(!fNewRecipientAllowed)
 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);
 692      updateTabsAndLabels();
 693  }
 694  
 695  bool SendCoinsDialog::handlePaymentRequest(const SendCoinsRecipient &rv)
 696  {
 697      // Just paste the entry, all pre-checks
 698      // are done in paymentserver.cpp.
 699      pasteEntry(rv);
 700      return true;
 701  }
 702  
 703  void SendCoinsDialog::setBalance(const interfaces::WalletBalances& balances)
 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  
 718  void SendCoinsDialog::refreshBalance()
 719  {
 720      setBalance(model->getCachedBalance());
 721      ui->customFee->setDisplayUnit(model->getOptionsModel()->getDisplayUnit());
 722      updateSmartFeeLabel();
 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      {
 735      case WalletModel::InvalidAddress:
 736          msgParams.first = tr("The recipient address is not valid. Please recheck.");
 737          break;
 738      case WalletModel::InvalidAmount:
 739          msgParams.first = tr("The amount to pay must be larger than 0.");
 740          break;
 741      case WalletModel::AmountExceedsBalance:
 742          msgParams.first = tr("The amount exceeds your balance.");
 743          break;
 744      case WalletModel::AmountWithFeeExceedsBalance:
 745          msgParams.first = tr("The total exceeds your balance when the %1 transaction fee is included.").arg(msgArg);
 746          break;
 747      case WalletModel::DuplicateAddress:
 748          msgParams.first = tr("Duplicate address found: addresses should only be used once each.");
 749          break;
 750      case WalletModel::TransactionCreationFailed:
 751          msgParams.first = tr("Transaction creation failed!");
 752          msgParams.second = CClientUIInterface::MSG_ERROR;
 753          break;
 754      case WalletModel::AbsurdFee:
 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  
 766  void SendCoinsDialog::minimizeFeeSection(bool fMinimize)
 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  
 776  void SendCoinsDialog::on_buttonChooseFee_clicked()
 777  {
 778      minimizeFeeSection(false);
 779  }
 780  
 781  void SendCoinsDialog::on_buttonMinimizeFee_clicked()
 782  {
 783      updateFeeMinimizedLabel();
 784      minimizeFeeSection(true);
 785  }
 786  
 787  void SendCoinsDialog::useAvailableBalance(SendCoinsEntry* entry)
 788  {
 789      // Include watch-only for wallets without private key
 790      m_coin_control->fAllowWatchOnly = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner();
 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) {
 807        entry->checkSubtractFeeFromAmount();
 808        entry->setAmount(amount);
 809      } else {
 810        entry->setAmount(0);
 811      }
 812  }
 813  
 814  void SendCoinsDialog::updateFeeSectionControls()
 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  
 826  void SendCoinsDialog::updateFeeMinimizedLabel()
 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  
 838  void SendCoinsDialog::updateCoinControlState()
 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
 850      m_coin_control->fAllowWatchOnly = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner();
 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) {
 855          updateSmartFeeLabel();
 856      }
 857  }
 858  
 859  void SendCoinsDialog::updateSmartFeeLabel()
 860  {
 861      if(!model || !model->getOptionsModel())
 862          return;
 863      updateCoinControlState();
 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  
 887      updateFeeMinimizedLabel();
 888  }
 889  
 890  // Coin Control: copy label "Quantity" to clipboard
 891  void SendCoinsDialog::coinControlClipboardQuantity()
 892  {
 893      GUIUtil::setClipboard(ui->labelCoinControlQuantity->text());
 894  }
 895  
 896  // Coin Control: copy label "Amount" to clipboard
 897  void SendCoinsDialog::coinControlClipboardAmount()
 898  {
 899      GUIUtil::setClipboard(ui->labelCoinControlAmount->text().left(ui->labelCoinControlAmount->text().indexOf(" ")));
 900  }
 901  
 902  // Coin Control: copy label "Fee" to clipboard
 903  void SendCoinsDialog::coinControlClipboardFee()
 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
 909  void SendCoinsDialog::coinControlClipboardAfterFee()
 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
 915  void SendCoinsDialog::coinControlClipboardBytes()
 916  {
 917      GUIUtil::setClipboard(ui->labelCoinControlBytes->text().replace(ASYMP_UTF8, ""));
 918  }
 919  
 920  // Coin Control: copy label "Change" to clipboard
 921  void SendCoinsDialog::coinControlClipboardChange()
 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
 927  void SendCoinsDialog::coinControlFeatureChanged(bool checked)
 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  
 935      coinControlUpdateLabels();
 936  }
 937  
 938  // Coin Control: button inputs -> show actual coin control dialog
 939  void SendCoinsDialog::coinControlButtonClicked()
 940  {
 941      auto dlg = new CoinControlDialog(*m_coin_control, model, platformStyle);
 942      connect(dlg, &QDialog::finished, this, &SendCoinsDialog::coinControlUpdateLabels);
 943      GUIUtil::ShowModalDialogAsynchronously(dlg);
 944  }
 945  
 946  // Coin Control: checkbox custom change address
 947  void SendCoinsDialog::coinControlChangeChecked(int state)
 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
 962  void SendCoinsDialog::coinControlChangeEdited(const QString& text)
 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
1016  void SendCoinsDialog::coinControlUpdateLabels()
1017  {
1018      if (!model || !model->getOptionsModel())
1019          return;
1020  
1021      updateCoinControlState();
1022  
1023      // set pay amounts
1024      CoinControlDialog::payAmounts.clear();
1025      CoinControlDialog::fSubtractFeeFromAmount = false;
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();
1033              CoinControlDialog::payAmounts.append(rcp.amount);
1034              if (rcp.fSubtractFeeFromAmount)
1035                  CoinControlDialog::fSubtractFeeFromAmount = true;
1036          }
1037      }
1038  
1039      if (m_coin_control->HasSelected())
1040      {
1041          // actual coin control calculation
1042          CoinControlDialog::updateLabels(*m_coin_control, model, this);
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  
1077  int SendConfirmationDialog::exec()
1078  {
1079      updateButtons();
1080      countDownTimer.start(1s);
1081      return QMessageBox::exec();
1082  }
1083  
1084  void SendConfirmationDialog::countDown()
1085  {
1086      secDelay--;
1087      updateButtons();
1088  
1089      if(secDelay <= 0)
1090      {
1091          countDownTimer.stop();
1092      }
1093  }
1094  
1095  void SendConfirmationDialog::updateButtons()
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);
1112              m_psbt_button->setText(m_psbt_button_text);
1113          }
1114      }
1115  }