walletmodel.cpp raw

   1  // Copyright (c) 2011-2022 The Limenka 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 <qt/walletmodel.h>
   6  
   7  #include <qt/addresstablemodel.h>
   8  #include <qt/clientmodel.h>
   9  #include <qt/guiconstants.h>
  10  #include <qt/guiutil.h>
  11  #include <qt/optionsmodel.h>
  12  #include <qt/paymentserver.h>
  13  #include <qt/psbtoperationsdialog.h>
  14  #include <qt/recentrequeststablemodel.h>
  15  #include <qt/sendcoinsdialog.h>
  16  #include <qt/transactiontablemodel.h>
  17  
  18  #include <common/args.h> // for GetBoolArg
  19  #include <consensus/amount.h>
  20  #include <interfaces/handler.h>
  21  #include <interfaces/node.h>
  22  #include <key_io.h>
  23  #include <node/interface_ui.h>
  24  #include <node/types.h>
  25  #include <psbt.h>
  26  #include <util/rbf.h>
  27  #include <util/translation.h>
  28  #include <wallet/coincontrol.h>
  29  #include <wallet/wallet.h> // for CRecipient
  30  
  31  #include <stdint.h>
  32  #include <functional>
  33  
  34  #include <QDebug>
  35  #include <QMessageBox>
  36  #include <QSet>
  37  #include <QTimer>
  38  
  39  using wallet::CCoinControl;
  40  using wallet::CRecipient;
  41  using wallet::DEFAULT_DISABLE_WALLET;
  42  
  43  WalletModel::WalletModel(std::unique_ptr<interfaces::Wallet> wallet, ClientModel& client_model, const PlatformStyle *platformStyle, QObject *parent) :
  44      QObject(parent),
  45      m_wallet(std::move(wallet)),
  46      m_client_model(&client_model),
  47      m_node(client_model.node()),
  48      optionsModel(client_model.getOptionsModel()),
  49      timer(new QTimer(this))
  50  {
  51      fHaveWatchOnly = m_wallet->haveWatchOnly();
  52      addressTableModel = new AddressTableModel(this);
  53      transactionTableModel = new TransactionTableModel(platformStyle, this);
  54      recentRequestsTableModel = new RecentRequestsTableModel(this);
  55  
  56      subscribeToCoreSignals();
  57  }
  58  
  59  WalletModel::~WalletModel()
  60  {
  61      unsubscribeFromCoreSignals();
  62  }
  63  
  64  void WalletModel::startPollBalance()
  65  {
  66      // Update the cached balance right away, so every view can make use of it,
  67      // so them don't need to waste resources recalculating it.
  68      pollBalanceChanged();
  69  
  70      // This timer will be fired repeatedly to update the balance
  71      // Since the QTimer::timeout is a private signal, it cannot be used
  72      // in the GUIUtil::ExceptionSafeConnect directly.
  73      connect(timer, &QTimer::timeout, this, &WalletModel::timerTimeout);
  74      GUIUtil::ExceptionSafeConnect(this, &WalletModel::timerTimeout, this, &WalletModel::pollBalanceChanged);
  75      timer->start(MODEL_UPDATE_DELAY);
  76  }
  77  
  78  void WalletModel::setClientModel(ClientModel* client_model)
  79  {
  80      m_client_model = client_model;
  81      if (!m_client_model) timer->stop();
  82  }
  83  
  84  void WalletModel::updateStatus()
  85  {
  86      EncryptionStatus newEncryptionStatus = getEncryptionStatus();
  87  
  88      if(cachedEncryptionStatus != newEncryptionStatus) {
  89          Q_EMIT encryptionStatusChanged();
  90      }
  91  }
  92  
  93  void WalletModel::pollBalanceChanged()
  94  {
  95      // Avoid recomputing wallet balances unless a TransactionChanged or
  96      // BlockTip notification was received.
  97      if (!fForceCheckBalanceChanged && m_cached_last_update_tip == getLastBlockProcessed()) return;
  98  
  99      // Try to get balances and return early if locks can't be acquired. This
 100      // avoids the GUI from getting stuck on periodical polls if the core is
 101      // holding the locks for a longer time - for example, during a wallet
 102      // rescan.
 103      interfaces::WalletBalances new_balances;
 104      uint256 block_hash;
 105      if (!m_wallet->tryGetBalances(new_balances, block_hash)) {
 106          return;
 107      }
 108  
 109      if (fForceCheckBalanceChanged || block_hash != m_cached_last_update_tip) {
 110          fForceCheckBalanceChanged = false;
 111  
 112          // Balance and number of transactions might have changed
 113          m_cached_last_update_tip = block_hash;
 114  
 115          checkBalanceChanged(new_balances);
 116          if(transactionTableModel)
 117              transactionTableModel->updateConfirmations();
 118      }
 119  }
 120  
 121  void WalletModel::checkBalanceChanged(const interfaces::WalletBalances& new_balances)
 122  {
 123      if (new_balances.balanceChanged(m_cached_balances)) {
 124          m_cached_balances = new_balances;
 125          Q_EMIT balanceChanged(new_balances);
 126      }
 127  }
 128  
 129  QString WalletModel::getPreciseBalance()
 130  {
 131      const std::string precise = m_wallet->getPreciseBalance();
 132      return QString::fromStdString(precise);
 133  }
 134  
 135  interfaces::WalletBalances WalletModel::getCachedBalance() const
 136  {
 137      return m_cached_balances;
 138  }
 139  
 140  void WalletModel::updateTransaction()
 141  {
 142      // Balance and number of transactions might have changed
 143      fForceCheckBalanceChanged = true;
 144  }
 145  
 146  void WalletModel::updateAddressBook(const QString &address, const QString &label,
 147          bool isMine, wallet::AddressPurpose purpose, int status)
 148  {
 149      if(addressTableModel)
 150          addressTableModel->updateEntry(address, label, isMine, purpose, status);
 151  }
 152  
 153  void WalletModel::updateWatchOnlyFlag(bool fHaveWatchonly)
 154  {
 155      fHaveWatchOnly = fHaveWatchonly;
 156      Q_EMIT notifyWatchonlyChanged(fHaveWatchonly);
 157  }
 158  
 159  bool WalletModel::validateAddress(const QString& address) const
 160  {
 161      return IsValidDestinationString(address.toStdString());
 162  }
 163  
 164  bool WalletModel::checkAddressForUsage(const std::vector<std::string>& addresses) const
 165  {
 166      return m_wallet->checkAddressForUsage(addresses);
 167  }
 168  
 169  bool WalletModel::findAddressUsage(const QStringList& addresses, std::function<void(const QString&, const interfaces::WalletTx&, uint32_t)> callback) const
 170  {
 171      std::vector<std::string> std_addresses;
 172      for (const auto& address : addresses) {
 173          std_addresses.push_back(address.toStdString());
 174      }
 175      return m_wallet->findAddressUsage(std_addresses, [&callback](const std::string& address, const interfaces::WalletTx& wtx, uint32_t output_index){
 176          callback(QString::fromStdString(address), wtx, output_index);
 177      });
 178  }
 179  
 180  WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction &transaction, const CCoinControl& coinControl)
 181  {
 182      CAmount total = 0;
 183      bool fSubtractFeeFromAmount = false;
 184      QList<SendCoinsRecipient> recipients = transaction.getRecipients();
 185      std::vector<CRecipient> vecSend;
 186  
 187      if(recipients.empty())
 188      {
 189          return OK;
 190      }
 191  
 192      QSet<QString> setAddress; // Used to detect duplicates
 193      int nAddresses = 0;
 194  
 195      // Confidential (lm2) recipient: route through the stealth payment path.
 196      // Only a single recipient is supported; the amount is checked against
 197      // the confidential balance and no on-chain fee estimate is produced
 198      // (the explicit fee is chosen at send time).
 199      const bool stealth_recipient =
 200          recipients.size() == 1 &&
 201          std::holds_alternative<WitnessV4StealthAddress>(
 202              DecodeDestination(recipients.front().address.toStdString()));
 203  
 204      // Pre-check input data for validity
 205      for (const SendCoinsRecipient &rcp : recipients)
 206      {
 207          if (rcp.fSubtractFeeFromAmount)
 208              fSubtractFeeFromAmount = true;
 209          {   // User-entered limenka address / amount:
 210              if(!validateAddress(rcp.address))
 211              {
 212                  return InvalidAddress;
 213              }
 214              if(rcp.amount <= 0)
 215              {
 216                  return InvalidAmount;
 217              }
 218              setAddress.insert(rcp.address);
 219              ++nAddresses;
 220  
 221              if (stealth_recipient) {
 222                  // Stealth payments spend confidential outputs; skip the
 223                  // transparent transaction preparation below.
 224                  const CAmount need = rcp.amount * ATTOSATS_PER_SATOSHI +
 225                                       ATTOSATS_PER_SATOSHI; // 1 sat fee floor
 226                  if (m_wallet->getConfidentialBalance() < need) {
 227                      return AmountExceedsBalance;
 228                  }
 229                  total += rcp.amount;
 230                  continue;
 231              }
 232  
 233              CRecipient recipient{DecodeDestination(rcp.address.toStdString()), rcp.amount, rcp.fSubtractFeeFromAmount};
 234              vecSend.push_back(recipient);
 235  
 236              total += rcp.amount;
 237          }
 238      }
 239      if (stealth_recipient) {
 240          // Nothing further to prepare: the stealth send happens in
 241          // sendCoins.  Report success with no on-chain fee.
 242          transaction.setTransactionFee(0);
 243          return OK;
 244      }
 245      if(setAddress.size() != nAddresses)
 246      {
 247          return DuplicateAddress;
 248      }
 249  
 250      // If no coin was manually selected, use the cached balance
 251      // Future: can merge this call with 'createTransaction'.
 252      CAmount nBalance = getAvailableBalance(&coinControl);
 253  
 254      if(total > nBalance)
 255      {
 256          return AmountExceedsBalance;
 257      }
 258  
 259      try {
 260          CAmount nFeeRequired = 0;
 261          int nChangePosRet = -1;
 262  
 263          auto& newTx = transaction.getWtx();
 264          const auto& res = m_wallet->createTransaction(vecSend, coinControl, /*sign=*/!wallet().privateKeysDisabled(), nChangePosRet, nFeeRequired);
 265          newTx = res ? *res : nullptr;
 266          transaction.setTransactionFee(nFeeRequired);
 267          if (fSubtractFeeFromAmount && newTx)
 268              transaction.reassignAmounts(nChangePosRet);
 269  
 270          if(!newTx)
 271          {
 272              if(!fSubtractFeeFromAmount && (total + nFeeRequired) > nBalance)
 273              {
 274                  return SendCoinsReturn(AmountWithFeeExceedsBalance);
 275              }
 276              Q_EMIT message(tr("Send Coins"), QString::fromStdString(util::ErrorString(res).translated),
 277                  CClientUIInterface::MSG_ERROR);
 278              return TransactionCreationFailed;
 279          }
 280  
 281          // Reject absurdly high fee. (This can never happen because the
 282          // wallet never creates transactions with fee greater than
 283          // m_default_max_tx_fee. This merely a belt-and-suspenders check).
 284          if (nFeeRequired > m_wallet->getDefaultMaxTxFee()) {
 285              return AbsurdFee;
 286          }
 287      } catch (const std::runtime_error& err) {
 288          // Something unexpected happened, instruct user to report this bug.
 289          Q_EMIT message(tr("Send Coins"), QString::fromStdString(err.what()),
 290                         CClientUIInterface::MSG_ERROR);
 291          return TransactionCreationFailed;
 292      }
 293  
 294      return SendCoinsReturn(OK);
 295  }
 296  
 297  void WalletModel::sendCoins(WalletModelTransaction& transaction)
 298  {
 299      QByteArray transaction_array; /* store serialized transaction */
 300  
 301      {
 302          std::vector<std::pair<std::string, std::string>> vOrderForm;
 303          for (const SendCoinsRecipient &rcp : transaction.getRecipients())
 304          {
 305              if (!rcp.message.isEmpty()) // Message from normal limenka:URI (limenka:123...?message=example)
 306                  vOrderForm.emplace_back("Message", rcp.message.toStdString());
 307          }
 308  
 309          auto& newTx = transaction.getWtx();
 310          wallet().commitTransaction(newTx, /*value_map=*/{}, std::move(vOrderForm));
 311  
 312          DataStream ssTx;
 313          ssTx << TX_WITH_WITNESS(*newTx);
 314          transaction_array.append((const char*)ssTx.data(), ssTx.size());
 315      }
 316  
 317      // Add addresses / update labels that we've sent to the address book,
 318      // and emit coinsSent signal for each recipient
 319      for (const SendCoinsRecipient &rcp : transaction.getRecipients())
 320      {
 321          {
 322              std::string strAddress = rcp.address.toStdString();
 323              CTxDestination dest = DecodeDestination(strAddress);
 324              std::string strLabel = rcp.label.toStdString();
 325              {
 326                  // Check if we have a new address or an updated label
 327                  std::string name;
 328                  if (!m_wallet->getAddress(
 329                       dest, &name, /* is_mine= */ nullptr, /* purpose= */ nullptr))
 330                  {
 331                      m_wallet->setAddressBook(dest, strLabel, wallet::AddressPurpose::SEND);
 332                  }
 333                  else if (name != strLabel)
 334                  {
 335                      m_wallet->setAddressBook(dest, strLabel, {}); // {} means don't change purpose
 336                  }
 337              }
 338          }
 339          Q_EMIT coinsSent(this, rcp, transaction_array);
 340      }
 341  
 342      checkBalanceChanged(m_wallet->getBalances()); // update balance immediately, otherwise there could be a short noticeable delay until pollBalanceChanged hits
 343  }
 344  
 345  OptionsModel* WalletModel::getOptionsModel() const
 346  {
 347      return optionsModel;
 348  }
 349  
 350  AddressTableModel* WalletModel::getAddressTableModel() const
 351  {
 352      return addressTableModel;
 353  }
 354  
 355  TransactionTableModel* WalletModel::getTransactionTableModel() const
 356  {
 357      return transactionTableModel;
 358  }
 359  
 360  RecentRequestsTableModel* WalletModel::getRecentRequestsTableModel() const
 361  {
 362      return recentRequestsTableModel;
 363  }
 364  
 365  WalletModel::EncryptionStatus WalletModel::getEncryptionStatus() const
 366  {
 367      if(!m_wallet->isCrypted())
 368      {
 369          // A previous bug allowed for watchonly wallets to be encrypted (encryption keys set, but nothing is actually encrypted).
 370          // To avoid misrepresenting the encryption status of such wallets, we only return NoKeys for watchonly wallets that are unencrypted.
 371          if (m_wallet->privateKeysDisabled()) {
 372              return NoKeys;
 373          }
 374          return Unencrypted;
 375      }
 376      else if(m_wallet->isLocked())
 377      {
 378          return Locked;
 379      }
 380      else
 381      {
 382          return Unlocked;
 383      }
 384  }
 385  
 386  bool WalletModel::setWalletEncrypted(const SecureString& passphrase)
 387  {
 388      return m_wallet->encryptWallet(passphrase);
 389  }
 390  
 391  bool WalletModel::setWalletLocked(bool locked, const SecureString &passPhrase)
 392  {
 393      if(locked)
 394      {
 395          // Lock
 396          return m_wallet->lock();
 397      }
 398      else
 399      {
 400          // Unlock
 401          return m_wallet->unlock(passPhrase);
 402      }
 403  }
 404  
 405  bool WalletModel::changePassphrase(const SecureString &oldPass, const SecureString &newPass)
 406  {
 407      m_wallet->lock(); // Make sure wallet is locked before attempting pass change
 408      return m_wallet->changeWalletPassphrase(oldPass, newPass);
 409  }
 410  
 411  // Handlers for core signals
 412  static void NotifyUnload(WalletModel* walletModel)
 413  {
 414      qDebug() << "NotifyUnload";
 415      bool invoked = QMetaObject::invokeMethod(walletModel, "unload");
 416      assert(invoked);
 417  }
 418  
 419  static void NotifyKeyStoreStatusChanged(WalletModel *walletmodel)
 420  {
 421      qDebug() << "NotifyKeyStoreStatusChanged";
 422      bool invoked = QMetaObject::invokeMethod(walletmodel, "updateStatus", Qt::QueuedConnection);
 423      assert(invoked);
 424  }
 425  
 426  static void NotifyAddressBookChanged(WalletModel *walletmodel,
 427          const CTxDestination &address, const std::string &label, bool isMine,
 428          wallet::AddressPurpose purpose, ChangeType status)
 429  {
 430      QString strAddress = QString::fromStdString(EncodeDestination(address));
 431      QString strLabel = QString::fromStdString(label);
 432  
 433      qDebug() << "NotifyAddressBookChanged: " + strAddress + " " + strLabel + " isMine=" + QString::number(isMine) + " purpose=" + QString::number(static_cast<uint8_t>(purpose)) + " status=" + QString::number(status);
 434      bool invoked = QMetaObject::invokeMethod(walletmodel, "updateAddressBook",
 435                                Q_ARG(QString, strAddress),
 436                                Q_ARG(QString, strLabel),
 437                                Q_ARG(bool, isMine),
 438                                Q_ARG(wallet::AddressPurpose, purpose),
 439                                Q_ARG(int, status));
 440      assert(invoked);
 441  }
 442  
 443  static void NotifyTransactionChanged(WalletModel *walletmodel, const uint256 &hash, ChangeType status)
 444  {
 445      Q_UNUSED(hash);
 446      Q_UNUSED(status);
 447      bool invoked = QMetaObject::invokeMethod(walletmodel, "updateTransaction", Qt::QueuedConnection);
 448      assert(invoked);
 449  }
 450  
 451  static void ShowProgress(WalletModel *walletmodel, const std::string &title, int nProgress)
 452  {
 453      // emits signal "showProgress"
 454      bool invoked = QMetaObject::invokeMethod(walletmodel, "showProgress", Qt::QueuedConnection,
 455                                Q_ARG(QString, QString::fromStdString(title)),
 456                                Q_ARG(int, nProgress));
 457      assert(invoked);
 458  }
 459  
 460  static void NotifyWatchonlyChanged(WalletModel *walletmodel, bool fHaveWatchonly)
 461  {
 462      bool invoked = QMetaObject::invokeMethod(walletmodel, "updateWatchOnlyFlag", Qt::QueuedConnection,
 463                                Q_ARG(bool, fHaveWatchonly));
 464      assert(invoked);
 465  }
 466  
 467  static void NotifyCanGetAddressesChanged(WalletModel* walletmodel)
 468  {
 469      bool invoked = QMetaObject::invokeMethod(walletmodel, "canGetAddressesChanged");
 470      assert(invoked);
 471  }
 472  
 473  void WalletModel::subscribeToCoreSignals()
 474  {
 475      // Connect signals to wallet
 476      m_handler_unload = m_wallet->handleUnload(std::bind(&NotifyUnload, this));
 477      m_handler_status_changed = m_wallet->handleStatusChanged(std::bind(&NotifyKeyStoreStatusChanged, this));
 478      m_handler_address_book_changed = m_wallet->handleAddressBookChanged(std::bind(NotifyAddressBookChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5));
 479      m_handler_transaction_changed = m_wallet->handleTransactionChanged(std::bind(NotifyTransactionChanged, this, std::placeholders::_1, std::placeholders::_2));
 480      m_handler_show_progress = m_wallet->handleShowProgress(std::bind(ShowProgress, this, std::placeholders::_1, std::placeholders::_2));
 481      m_handler_watch_only_changed = m_wallet->handleWatchOnlyChanged(std::bind(NotifyWatchonlyChanged, this, std::placeholders::_1));
 482      m_handler_can_get_addrs_changed = m_wallet->handleCanGetAddressesChanged(std::bind(NotifyCanGetAddressesChanged, this));
 483  }
 484  
 485  void WalletModel::unsubscribeFromCoreSignals()
 486  {
 487      // Disconnect signals from wallet
 488      m_handler_unload->disconnect();
 489      m_handler_status_changed->disconnect();
 490      m_handler_address_book_changed->disconnect();
 491      m_handler_transaction_changed->disconnect();
 492      m_handler_show_progress->disconnect();
 493      m_handler_watch_only_changed->disconnect();
 494      m_handler_can_get_addrs_changed->disconnect();
 495  }
 496  
 497  // WalletModel::UnlockContext implementation
 498  WalletModel::UnlockContext WalletModel::requestUnlock()
 499  {
 500      // Bugs in earlier versions may have resulted in wallets with private keys disabled to become "encrypted"
 501      // (encryption keys are present, but not actually doing anything).
 502      // To avoid issues with such wallets, check if the wallet has private keys disabled, and if so, return a context
 503      // that indicates the wallet is not encrypted.
 504      if (m_wallet->privateKeysDisabled()) {
 505          return UnlockContext(this, /*valid=*/true, /*relock=*/false);
 506      }
 507      bool was_locked = getEncryptionStatus() == Locked;
 508      if(was_locked)
 509      {
 510          // Request UI to unlock wallet
 511          Q_EMIT requireUnlock();
 512      }
 513      // If wallet is still locked, unlock was failed or cancelled, mark context as invalid
 514      bool valid = getEncryptionStatus() != Locked;
 515  
 516      return UnlockContext(this, valid, was_locked);
 517  }
 518  
 519  WalletModel::UnlockContext::UnlockContext(WalletModel *_wallet, bool _valid, bool _relock):
 520          wallet(_wallet),
 521          valid(_valid),
 522          relock(_relock)
 523  {
 524  }
 525  
 526  WalletModel::UnlockContext::~UnlockContext()
 527  {
 528      if(valid && relock)
 529      {
 530          wallet->setWalletLocked(true);
 531      }
 532  }
 533  
 534  bool WalletModel::bumpFee(uint256 hash, uint256& new_hash)
 535  {
 536      const CTransactionRef old_tx = m_wallet->getTx(hash);
 537  
 538      CCoinControl coin_control;
 539      coin_control.m_signal_bip125_rbf = true;
 540      std::vector<bilingual_str> errors;
 541      CAmount old_fee;
 542      CAmount new_fee;
 543      CMutableTransaction mtx;
 544      if (!m_wallet->createBumpTransaction(hash, coin_control, errors, old_fee, new_fee, mtx)) {
 545          QMessageBox::critical(nullptr, tr("Fee bump error"), tr("Increasing transaction fee failed") + "<br />(" +
 546              (errors.size() ? QString::fromStdString(errors[0].translated) : "") +")");
 547          return false;
 548      }
 549  
 550      // allow a user based fee verification
 551      /*: Asks a user if they would like to manually increase the fee of a transaction that has already been created. */
 552      const LimenkaUnit display_unit = getOptionsModel()->getDisplayUnit();
 553      const QFont font_for_money = getOptionsModel()->getFontForMoney(display_unit);
 554      QString questionString = tr("Do you want to increase the fee?");
 555      questionString.append("<br />");
 556      questionString.append("<table style=\"text-align: left;\">");
 557      questionString.append("<tr><td>");
 558      questionString.append(tr("Current fee:"));
 559      questionString.append("</td><td>");
 560      questionString.append(LimenkaUnits::formatHtmlWithUnit(font_for_money, display_unit, old_fee));
 561      questionString.append("</td></tr><tr><td>");
 562      questionString.append(tr("Increase:"));
 563      questionString.append("</td><td>");
 564      questionString.append(LimenkaUnits::formatHtmlWithUnit(font_for_money, display_unit, new_fee - old_fee));
 565      questionString.append("</td></tr><tr><td>");
 566      questionString.append(tr("New fee:"));
 567      questionString.append("</td><td>");
 568      questionString.append(LimenkaUnits::formatHtmlWithUnit(font_for_money, display_unit, new_fee));
 569      questionString.append("</td></tr></table>");
 570  
 571      // Display warning in the "Confirm fee bump" window if the "Coin Control Features" option is enabled
 572      if (getOptionsModel()->getCoinControlFeatures()) {
 573          questionString.append("<br><br>");
 574          questionString.append(tr("Warning: This may pay the additional fee by reducing change outputs or adding inputs, when necessary. It may add a new change output if one does not already exist. These changes may potentially leak privacy."));
 575      }
 576  
 577      if (!SignalsOptInRBF(*old_tx)) {
 578          questionString.append(QStringLiteral("<br><br>"));
 579          questionString.append(tr("Warning: The old transaction did not enable BIP 125 replace-by-fee. You can still attempt to bump the fee, but it may encounter delays."));
 580      }
 581  
 582      const bool enable_send{!wallet().privateKeysDisabled() || wallet().hasExternalSigner()};
 583      const bool always_show_unsigned{getOptionsModel()->getEnablePSBTControls()};
 584      auto confirmationDialog = new SendConfirmationDialog(tr("Confirm fee bump"), questionString, "", "", SEND_CONFIRM_DELAY, enable_send, always_show_unsigned, nullptr);
 585      confirmationDialog->m_delete_on_close = true;
 586      // TODO: Replace QDialog::exec() with safer QDialog::show().
 587      const auto retval = static_cast<QMessageBox::StandardButton>(confirmationDialog->exec());
 588  
 589      // cancel sign&broadcast if user doesn't want to bump the fee
 590      if (retval != QMessageBox::Yes && retval != QMessageBox::Save) {
 591          return false;
 592      }
 593  
 594      // Short-circuit if we are returning a bumped transaction PSBT to clipboard
 595      if (retval == QMessageBox::Save) {
 596          // "Create Unsigned" clicked
 597          PartiallySignedTransaction psbtx(mtx);
 598          bool complete = false;
 599          const auto err{wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, nullptr, psbtx, complete)};
 600          if (err || complete) {
 601              QMessageBox::critical(nullptr, tr("Fee bump error"), tr("Can't draft transaction."));
 602              return false;
 603          }
 604          auto dlg = new PSBTOperationsDialog(nullptr, this, m_client_model);
 605          dlg->openWithPSBT(psbtx);
 606          GUIUtil::ShowModalDialogAsynchronously(dlg, Qt::NonModal);
 607  #if 0
 608          // Serialize the PSBT
 609          DataStream ssTx{};
 610          ssTx << psbtx;
 611          GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str());
 612          Q_EMIT message(tr("PSBT copied"), tr("Fee-bump PSBT copied to clipboard"), CClientUIInterface::MSG_INFORMATION | CClientUIInterface::MODAL);
 613  #endif
 614          return true;
 615      }
 616  
 617      WalletModel::UnlockContext ctx(requestUnlock());
 618      if (!ctx.isValid()) {
 619          return false;
 620      }
 621  
 622      assert(!m_wallet->privateKeysDisabled() || wallet().hasExternalSigner());
 623  
 624      // sign bumped transaction
 625      if (!m_wallet->signBumpTransaction(mtx)) {
 626          QMessageBox::critical(nullptr, tr("Fee bump error"), tr("Can't sign transaction."));
 627          return false;
 628      }
 629      // commit the bumped transaction
 630      if(!m_wallet->commitBumpTransaction(hash, std::move(mtx), errors, new_hash)) {
 631          QMessageBox::critical(nullptr, tr("Fee bump error"), tr("Could not commit transaction") + "<br />(" +
 632              QString::fromStdString(errors[0].translated)+")");
 633          return false;
 634      }
 635      return true;
 636  }
 637  
 638  void WalletModel::displayAddress(std::string sAddress) const
 639  {
 640      CTxDestination dest = DecodeDestination(sAddress);
 641      try {
 642          util::Result<void> result = m_wallet->displayAddress(dest);
 643          if (!result) {
 644              QMessageBox::warning(nullptr, tr("Signer error"), QString::fromStdString(util::ErrorString(result).translated));
 645          }
 646      } catch (const std::runtime_error& e) {
 647          QMessageBox::critical(nullptr, tr("Can't display address"), e.what());
 648      }
 649  }
 650  
 651  bool WalletModel::isWalletEnabled()
 652  {
 653     return !gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET);
 654  }
 655  
 656  QString WalletModel::getWalletName() const
 657  {
 658      return QString::fromStdString(m_wallet->getWalletName());
 659  }
 660  
 661  QString WalletModel::getDisplayName() const
 662  {
 663      return GUIUtil::WalletDisplayName(getWalletName());
 664  }
 665  
 666  bool WalletModel::isMultiwallet() const
 667  {
 668      return m_node.walletLoader().getWallets().size() > 1;
 669  }
 670  
 671  void WalletModel::refresh(bool pk_hash_only)
 672  {
 673      addressTableModel = new AddressTableModel(this, pk_hash_only);
 674  }
 675  
 676  uint256 WalletModel::getLastBlockProcessed() const
 677  {
 678      return m_client_model ? m_client_model->getBestBlockHash() : uint256{};
 679  }
 680  
 681  CAmount WalletModel::getAvailableBalance(const CCoinControl* control)
 682  {
 683      // No selected coins, return the cached balance
 684      if (!control || !control->HasSelected()) {
 685          const interfaces::WalletBalances& balances = getCachedBalance();
 686          CAmount available_balance = balances.balance;
 687          // if wallet private keys are disabled, this is a watch-only wallet
 688          // so, let's include the watch-only balance.
 689          if (balances.have_watch_only && m_wallet->privateKeysDisabled()) {
 690              available_balance += balances.watch_only_balance;
 691          }
 692          return available_balance;
 693      }
 694      // Fetch balance from the wallet, taking into account the selected coins
 695      return wallet().getAvailableBalance(*control);
 696  }
 697  
 698  LimenkaAddressUnusedInWalletValidator::LimenkaAddressUnusedInWalletValidator(const WalletModel& wallet_model, QObject *parent) :
 699      QValidator(parent),
 700      m_wallet_model(wallet_model)
 701  {
 702  }
 703  
 704  QValidator::State LimenkaAddressUnusedInWalletValidator::validate(QString &input, int &pos) const
 705  {
 706      Q_UNUSED(pos);
 707      if (m_wallet_model.checkAddressForUsage(std::vector<std::string>{input.toStdString()})) {
 708          return QValidator::Invalid;
 709      }
 710      return QValidator::Acceptable;
 711  }
 712