walletmodel.cpp raw

   1  // Copyright (c) 2011-present 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 <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/recentrequeststablemodel.h>
  14  #include <qt/sendcoinsdialog.h>
  15  #include <qt/transactiontablemodel.h>
  16  
  17  #include <common/args.h>
  18  #include <interfaces/handler.h>
  19  #include <interfaces/node.h>
  20  #include <key_io.h>
  21  #include <node/interface_ui.h>
  22  #include <node/types.h>
  23  #include <psbt.h>
  24  #include <util/translation.h>
  25  #include <wallet/coincontrol.h>
  26  #include <wallet/types.h>
  27  #include <wallet/wallet.h>
  28  
  29  #include <cstdint>
  30  #include <functional>
  31  #include <memory>
  32  #include <vector>
  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      addressTableModel = new AddressTableModel(this);
  52      transactionTableModel = new TransactionTableModel(platformStyle, this);
  53      recentRequestsTableModel = new RecentRequestsTableModel(this);
  54  
  55      subscribeToCoreSignals();
  56  }
  57  
  58  WalletModel::~WalletModel()
  59  {
  60      unsubscribeFromCoreSignals();
  61  }
  62  
  63  void WalletModel::startPollBalance()
  64  {
  65      // Update the cached balance right away, so every view can make use of it,
  66      // so them don't need to waste resources recalculating it.
  67      pollBalanceChanged();
  68  
  69      // This timer will be fired repeatedly to update the balance
  70      // Since the QTimer::timeout is a private signal, it cannot be used
  71      // in the GUIUtil::ExceptionSafeConnect directly.
  72      connect(timer, &QTimer::timeout, this, &WalletModel::timerTimeout);
  73      GUIUtil::ExceptionSafeConnect(this, &WalletModel::timerTimeout, this, &WalletModel::pollBalanceChanged);
  74      timer->start(MODEL_UPDATE_DELAY);
  75  }
  76  
  77  void WalletModel::setClientModel(ClientModel* client_model)
  78  {
  79      m_client_model = client_model;
  80      if (!m_client_model) timer->stop();
  81  }
  82  
  83  void WalletModel::updateStatus()
  84  {
  85      EncryptionStatus newEncryptionStatus = getEncryptionStatus();
  86  
  87      if(cachedEncryptionStatus != newEncryptionStatus) {
  88          Q_EMIT encryptionStatusChanged();
  89      }
  90  }
  91  
  92  void WalletModel::pollBalanceChanged()
  93  {
  94      // Avoid recomputing wallet balances unless a TransactionChanged or
  95      // BlockTip notification was received.
  96      if (!fForceCheckBalanceChanged && m_cached_last_update_tip == getLastBlockProcessed()) return;
  97  
  98      // Try to get balances and return early if locks can't be acquired. This
  99      // avoids the GUI from getting stuck on periodical polls if the core is
 100      // holding the locks for a longer time - for example, during a wallet
 101      // rescan.
 102      interfaces::WalletBalances new_balances;
 103      uint256 block_hash;
 104      if (!m_wallet->tryGetBalances(new_balances, block_hash)) {
 105          return;
 106      }
 107  
 108      if (fForceCheckBalanceChanged || block_hash != m_cached_last_update_tip) {
 109          fForceCheckBalanceChanged = false;
 110  
 111          // Balance and number of transactions might have changed
 112          m_cached_last_update_tip = block_hash;
 113  
 114          checkBalanceChanged(new_balances);
 115          if(transactionTableModel)
 116              transactionTableModel->updateConfirmations();
 117      }
 118  }
 119  
 120  void WalletModel::checkBalanceChanged(const interfaces::WalletBalances& new_balances)
 121  {
 122      if (new_balances.balanceChanged(m_cached_balances)) {
 123          m_cached_balances = new_balances;
 124          Q_EMIT balanceChanged(new_balances);
 125      }
 126  }
 127  
 128  interfaces::WalletBalances WalletModel::getCachedBalance() const
 129  {
 130      return m_cached_balances;
 131  }
 132  
 133  void WalletModel::updateTransaction()
 134  {
 135      // Balance and number of transactions might have changed
 136      fForceCheckBalanceChanged = true;
 137  }
 138  
 139  void WalletModel::updateAddressBook(const QString &address, const QString &label,
 140          bool isMine, wallet::AddressPurpose purpose, int status)
 141  {
 142      if(addressTableModel)
 143          addressTableModel->updateEntry(address, label, isMine, purpose, status);
 144  }
 145  
 146  bool WalletModel::validateAddress(const QString& address) const
 147  {
 148      return IsValidDestinationString(address.toStdString());
 149  }
 150  
 151  WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction &transaction, const CCoinControl& coinControl)
 152  {
 153      transaction.getWtx() = nullptr; // reset tx output
 154  
 155      CAmount total = 0;
 156      bool fSubtractFeeFromAmount = false;
 157      QList<SendCoinsRecipient> recipients = transaction.getRecipients();
 158      std::vector<CRecipient> vecSend;
 159  
 160      if(recipients.empty())
 161      {
 162          return OK;
 163      }
 164  
 165      QSet<QString> setAddress; // Used to detect duplicates
 166      int nAddresses = 0;
 167  
 168      // Pre-check input data for validity
 169      for (const SendCoinsRecipient &rcp : recipients)
 170      {
 171          if (rcp.fSubtractFeeFromAmount)
 172              fSubtractFeeFromAmount = true;
 173          {   // User-entered bitcoin address / amount:
 174              if(!validateAddress(rcp.address))
 175              {
 176                  return InvalidAddress;
 177              }
 178              if(rcp.amount <= 0)
 179              {
 180                  return InvalidAmount;
 181              }
 182              setAddress.insert(rcp.address);
 183              ++nAddresses;
 184  
 185              vecSend.emplace_back(CRecipient{DecodeDestination(rcp.address.toStdString()), rcp.amount, rcp.fSubtractFeeFromAmount});
 186  
 187              total += rcp.amount;
 188          }
 189      }
 190      if(setAddress.size() != nAddresses)
 191      {
 192          return DuplicateAddress;
 193      }
 194  
 195      // If no coin was manually selected, use the cached balance
 196      // Future: can merge this call with 'createTransaction'.
 197      CAmount nBalance = getAvailableBalance(&coinControl);
 198  
 199      if(total > nBalance)
 200      {
 201          return AmountExceedsBalance;
 202      }
 203  
 204      try {
 205          auto& newTx = transaction.getWtx();
 206          const auto& res = m_wallet->createTransaction(vecSend, coinControl, /*sign=*/!wallet().privateKeysDisabled(), /*change_pos=*/std::nullopt);
 207          if (!res) {
 208              Q_EMIT message(tr("Send Coins"), QString::fromStdString(util::ErrorString(res).translated),
 209                             CClientUIInterface::MSG_ERROR);
 210              return TransactionCreationFailed;
 211          }
 212  
 213          newTx = res->tx;
 214          CAmount nFeeRequired = res->fee;
 215          transaction.setTransactionFee(nFeeRequired);
 216          if (fSubtractFeeFromAmount && newTx) {
 217              transaction.reassignAmounts(static_cast<int>(res->change_pos.value_or(-1)));
 218          }
 219  
 220          // Reject absurdly high fee. (This can never happen because the
 221          // wallet never creates transactions with fee greater than
 222          // m_default_max_tx_fee. This merely a belt-and-suspenders check).
 223          if (nFeeRequired > m_wallet->getDefaultMaxTxFee()) {
 224              return AbsurdFee;
 225          }
 226      } catch (const std::runtime_error& err) {
 227          // Something unexpected happened, instruct user to report this bug.
 228          Q_EMIT message(tr("Send Coins"), QString::fromStdString(err.what()),
 229                         CClientUIInterface::MSG_ERROR);
 230          return TransactionCreationFailed;
 231      }
 232  
 233      return SendCoinsReturn(OK);
 234  }
 235  
 236  void WalletModel::sendCoins(WalletModelTransaction& transaction)
 237  {
 238      QByteArray transaction_array; /* store serialized transaction */
 239  
 240      {
 241          std::vector<std::string> messages;
 242          for (const SendCoinsRecipient &rcp : transaction.getRecipients())
 243          {
 244              if (!rcp.message.isEmpty()) { // Message from normal bitcoin:URI (bitcoin:123...?message=example)
 245                  messages.emplace_back(rcp.message.toStdString());
 246              }
 247          }
 248  
 249          auto& newTx = transaction.getWtx();
 250          wallet().commitTransaction(newTx, messages);
 251  
 252          DataStream ssTx;
 253          ssTx << TX_WITH_WITNESS(*newTx);
 254          transaction_array.append((const char*)ssTx.data(), ssTx.size());
 255      }
 256  
 257      // Add addresses / update labels that we've sent to the address book,
 258      // and emit coinsSent signal for each recipient
 259      for (const SendCoinsRecipient &rcp : transaction.getRecipients())
 260      {
 261          {
 262              std::string strAddress = rcp.address.toStdString();
 263              CTxDestination dest = DecodeDestination(strAddress);
 264              std::string strLabel = rcp.label.toStdString();
 265              {
 266                  // Check if we have a new address or an updated label
 267                  std::string name;
 268                  if (!m_wallet->getAddress(
 269                       dest, &name, /*purpose=*/nullptr))
 270                  {
 271                      m_wallet->setAddressBook(dest, strLabel, wallet::AddressPurpose::SEND);
 272                  }
 273                  else if (name != strLabel)
 274                  {
 275                      m_wallet->setAddressBook(dest, strLabel, {}); // {} means don't change purpose
 276                  }
 277              }
 278          }
 279          Q_EMIT coinsSent(this, rcp, transaction_array);
 280      }
 281  
 282      checkBalanceChanged(m_wallet->getBalances()); // update balance immediately, otherwise there could be a short noticeable delay until pollBalanceChanged hits
 283  }
 284  
 285  OptionsModel* WalletModel::getOptionsModel() const
 286  {
 287      return optionsModel;
 288  }
 289  
 290  AddressTableModel* WalletModel::getAddressTableModel() const
 291  {
 292      return addressTableModel;
 293  }
 294  
 295  TransactionTableModel* WalletModel::getTransactionTableModel() const
 296  {
 297      return transactionTableModel;
 298  }
 299  
 300  RecentRequestsTableModel* WalletModel::getRecentRequestsTableModel() const
 301  {
 302      return recentRequestsTableModel;
 303  }
 304  
 305  WalletModel::EncryptionStatus WalletModel::getEncryptionStatus() const
 306  {
 307      if(!m_wallet->isCrypted())
 308      {
 309          // A previous bug allowed for watchonly wallets to be encrypted (encryption keys set, but nothing is actually encrypted).
 310          // To avoid misrepresenting the encryption status of such wallets, we only return NoKeys for watchonly wallets that are unencrypted.
 311          if (m_wallet->privateKeysDisabled()) {
 312              return NoKeys;
 313          }
 314          return Unencrypted;
 315      }
 316      else if(m_wallet->isLocked())
 317      {
 318          return Locked;
 319      }
 320      else
 321      {
 322          return Unlocked;
 323      }
 324  }
 325  
 326  bool WalletModel::setWalletEncrypted(const SecureString& passphrase)
 327  {
 328      return m_wallet->encryptWallet(passphrase);
 329  }
 330  
 331  bool WalletModel::setWalletLocked(bool locked, const SecureString &passPhrase)
 332  {
 333      if(locked)
 334      {
 335          // Lock
 336          return m_wallet->lock();
 337      }
 338      else
 339      {
 340          // Unlock
 341          return m_wallet->unlock(passPhrase);
 342      }
 343  }
 344  
 345  bool WalletModel::changePassphrase(const SecureString &oldPass, const SecureString &newPass)
 346  {
 347      m_wallet->lock(); // Make sure wallet is locked before attempting pass change
 348      return m_wallet->changeWalletPassphrase(oldPass, newPass);
 349  }
 350  
 351  // Handlers for core signals
 352  static void NotifyUnload(WalletModel* walletModel)
 353  {
 354      qDebug() << "NotifyUnload";
 355      bool invoked = QMetaObject::invokeMethod(walletModel, "unload");
 356      assert(invoked);
 357  }
 358  
 359  static void NotifyKeyStoreStatusChanged(WalletModel *walletmodel)
 360  {
 361      qDebug() << "NotifyKeyStoreStatusChanged";
 362      bool invoked = QMetaObject::invokeMethod(walletmodel, "updateStatus", Qt::QueuedConnection);
 363      assert(invoked);
 364  }
 365  
 366  static void NotifyAddressBookChanged(WalletModel *walletmodel,
 367          const CTxDestination &address, const std::string &label, bool isMine,
 368          wallet::AddressPurpose purpose, ChangeType status)
 369  {
 370      QString strAddress = QString::fromStdString(EncodeDestination(address));
 371      QString strLabel = QString::fromStdString(label);
 372  
 373      qDebug() << "NotifyAddressBookChanged: " + strAddress + " " + strLabel + " isMine=" + QString::number(isMine) + " purpose=" + QString::number(static_cast<uint8_t>(purpose)) + " status=" + QString::number(status);
 374      bool invoked = QMetaObject::invokeMethod(walletmodel, "updateAddressBook",
 375                                Q_ARG(QString, strAddress),
 376                                Q_ARG(QString, strLabel),
 377                                Q_ARG(bool, isMine),
 378                                Q_ARG(wallet::AddressPurpose, purpose),
 379                                Q_ARG(int, status));
 380      assert(invoked);
 381  }
 382  
 383  static void NotifyTransactionChanged(WalletModel *walletmodel, const Txid& hash, ChangeType status)
 384  {
 385      Q_UNUSED(hash);
 386      Q_UNUSED(status);
 387      bool invoked = QMetaObject::invokeMethod(walletmodel, "updateTransaction", Qt::QueuedConnection);
 388      assert(invoked);
 389  }
 390  
 391  static void ShowProgress(WalletModel *walletmodel, const std::string &title, int nProgress)
 392  {
 393      // emits signal "showProgress"
 394      bool invoked = QMetaObject::invokeMethod(walletmodel, "showProgress", Qt::QueuedConnection,
 395                                Q_ARG(QString, QString::fromStdString(title)),
 396                                Q_ARG(int, nProgress));
 397      assert(invoked);
 398  }
 399  
 400  static void NotifyCanGetAddressesChanged(WalletModel* walletmodel)
 401  {
 402      bool invoked = QMetaObject::invokeMethod(walletmodel, "canGetAddressesChanged");
 403      assert(invoked);
 404  }
 405  
 406  void WalletModel::subscribeToCoreSignals()
 407  {
 408      // Connect signals to wallet
 409      m_handler_unload = m_wallet->handleUnload(std::bind_front(&NotifyUnload, this));
 410      m_handler_status_changed = m_wallet->handleStatusChanged(std::bind_front(&NotifyKeyStoreStatusChanged, this));
 411      m_handler_address_book_changed = m_wallet->handleAddressBookChanged(std::bind_front(NotifyAddressBookChanged, this));
 412      m_handler_transaction_changed = m_wallet->handleTransactionChanged(std::bind_front(NotifyTransactionChanged, this));
 413      m_handler_show_progress = m_wallet->handleShowProgress(std::bind_front(ShowProgress, this));
 414      m_handler_can_get_addrs_changed = m_wallet->handleCanGetAddressesChanged(std::bind_front(NotifyCanGetAddressesChanged, this));
 415  }
 416  
 417  void WalletModel::unsubscribeFromCoreSignals()
 418  {
 419      // Disconnect signals from wallet
 420      m_handler_unload->disconnect();
 421      m_handler_status_changed->disconnect();
 422      m_handler_address_book_changed->disconnect();
 423      m_handler_transaction_changed->disconnect();
 424      m_handler_show_progress->disconnect();
 425      m_handler_can_get_addrs_changed->disconnect();
 426  }
 427  
 428  // WalletModel::UnlockContext implementation
 429  WalletModel::UnlockContext WalletModel::requestUnlock()
 430  {
 431      // Bugs in earlier versions may have resulted in wallets with private keys disabled to become "encrypted"
 432      // (encryption keys are present, but not actually doing anything).
 433      // To avoid issues with such wallets, check if the wallet has private keys disabled, and if so, return a context
 434      // that indicates the wallet is not encrypted.
 435      if (m_wallet->privateKeysDisabled()) {
 436          return UnlockContext(this, /*valid=*/true, /*relock=*/false);
 437      }
 438      bool was_locked = getEncryptionStatus() == Locked;
 439      if(was_locked)
 440      {
 441          // Request UI to unlock wallet
 442          Q_EMIT requireUnlock();
 443      }
 444      // If wallet is still locked, unlock was failed or cancelled, mark context as invalid
 445      bool valid = getEncryptionStatus() != Locked;
 446  
 447      return UnlockContext(this, valid, was_locked);
 448  }
 449  
 450  WalletModel::UnlockContext::UnlockContext(WalletModel *_wallet, bool _valid, bool _relock):
 451          wallet(_wallet),
 452          valid(_valid),
 453          relock(_relock)
 454  {
 455  }
 456  
 457  WalletModel::UnlockContext::~UnlockContext()
 458  {
 459      if(valid && relock)
 460      {
 461          wallet->setWalletLocked(true);
 462      }
 463  }
 464  
 465  bool WalletModel::bumpFee(Txid hash, Txid& new_hash)
 466  {
 467      CCoinControl coin_control;
 468      std::vector<bilingual_str> errors;
 469      CAmount old_fee;
 470      CAmount new_fee;
 471      CMutableTransaction mtx;
 472      if (!m_wallet->createBumpTransaction(hash, coin_control, errors, old_fee, new_fee, mtx)) {
 473          QMessageBox::critical(nullptr, tr("Fee bump error"), tr("Increasing transaction fee failed") + "<br />(" +
 474              (errors.size() ? QString::fromStdString(errors[0].translated) : "") +")");
 475          return false;
 476      }
 477  
 478      // allow a user based fee verification
 479      /*: Asks a user if they would like to manually increase the fee of a transaction that has already been created. */
 480      QString questionString = tr("Do you want to increase the fee?");
 481      questionString.append("<br />");
 482      questionString.append("<table style=\"text-align: left;\">");
 483      questionString.append("<tr><td>");
 484      questionString.append(tr("Current fee:"));
 485      questionString.append("</td><td>");
 486      questionString.append(BitcoinUnits::formatHtmlWithUnit(getOptionsModel()->getDisplayUnit(), old_fee));
 487      questionString.append("</td></tr><tr><td>");
 488      questionString.append(tr("Increase:"));
 489      questionString.append("</td><td>");
 490      questionString.append(BitcoinUnits::formatHtmlWithUnit(getOptionsModel()->getDisplayUnit(), new_fee - old_fee));
 491      questionString.append("</td></tr><tr><td>");
 492      questionString.append(tr("New fee:"));
 493      questionString.append("</td><td>");
 494      questionString.append(BitcoinUnits::formatHtmlWithUnit(getOptionsModel()->getDisplayUnit(), new_fee));
 495      questionString.append("</td></tr></table>");
 496  
 497      // Display warning in the "Confirm fee bump" window if the "Coin Control Features" option is enabled
 498      if (getOptionsModel()->getCoinControlFeatures()) {
 499          questionString.append("<br><br>");
 500          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."));
 501      }
 502  
 503      const bool enable_send{!wallet().privateKeysDisabled() || wallet().hasExternalSigner()};
 504      const bool always_show_unsigned{getOptionsModel()->getEnablePSBTControls()};
 505      auto confirmationDialog = new SendConfirmationDialog(tr("Confirm fee bump"), questionString, "", "", SEND_CONFIRM_DELAY, enable_send, always_show_unsigned, nullptr);
 506      confirmationDialog->setAttribute(Qt::WA_DeleteOnClose);
 507      // TODO: Replace QDialog::exec() with safer QDialog::show().
 508      const auto retval = static_cast<QMessageBox::StandardButton>(confirmationDialog->exec());
 509  
 510      // cancel sign&broadcast if user doesn't want to bump the fee
 511      if (retval != QMessageBox::Yes && retval != QMessageBox::Save) {
 512          return false;
 513      }
 514  
 515      // Short-circuit if we are returning a bumped transaction PSBT to clipboard
 516      if (retval == QMessageBox::Save) {
 517          // "Create Unsigned" clicked
 518          PartiallySignedTransaction psbtx(mtx);
 519          bool complete = false;
 520          const auto err{wallet().fillPSBT({.sign = false, .bip32_derivs = true}, nullptr, psbtx, complete)};
 521          if (err || complete) {
 522              QMessageBox::critical(nullptr, tr("Fee bump error"), tr("Can't draft transaction."));
 523              return false;
 524          }
 525          // Serialize the PSBT
 526          DataStream ssTx{};
 527          ssTx << psbtx;
 528          GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str());
 529          Q_EMIT message(tr("PSBT copied"), tr("Fee-bump PSBT copied to clipboard"), CClientUIInterface::MSG_INFORMATION | CClientUIInterface::MODAL);
 530          return true;
 531      }
 532  
 533      WalletModel::UnlockContext ctx(requestUnlock());
 534      if (!ctx.isValid()) {
 535          return false;
 536      }
 537  
 538      assert(!m_wallet->privateKeysDisabled() || wallet().hasExternalSigner());
 539  
 540      // sign bumped transaction
 541      if (!m_wallet->signBumpTransaction(mtx)) {
 542          QMessageBox::critical(nullptr, tr("Fee bump error"), tr("Can't sign transaction."));
 543          return false;
 544      }
 545      // commit the bumped transaction
 546      if(!m_wallet->commitBumpTransaction(hash, std::move(mtx), errors, new_hash)) {
 547          QMessageBox::critical(nullptr, tr("Fee bump error"), tr("Could not commit transaction") + "<br />(" +
 548              QString::fromStdString(errors[0].translated)+")");
 549          return false;
 550      }
 551      return true;
 552  }
 553  
 554  void WalletModel::displayAddress(std::string sAddress) const
 555  {
 556      CTxDestination dest = DecodeDestination(sAddress);
 557      try {
 558          util::Result<void> result = m_wallet->displayAddress(dest);
 559          if (!result) {
 560              QMessageBox::warning(nullptr, tr("Signer error"), QString::fromStdString(util::ErrorString(result).translated));
 561          }
 562      } catch (const std::runtime_error& e) {
 563          QMessageBox::critical(nullptr, tr("Can't display address"), e.what());
 564      }
 565  }
 566  
 567  bool WalletModel::isWalletEnabled()
 568  {
 569     return !gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET);
 570  }
 571  
 572  QString WalletModel::getWalletName() const
 573  {
 574      return QString::fromStdString(m_wallet->getWalletName());
 575  }
 576  
 577  QString WalletModel::getDisplayName() const
 578  {
 579      return GUIUtil::WalletDisplayName(getWalletName());
 580  }
 581  
 582  bool WalletModel::isMultiwallet() const
 583  {
 584      return m_node.walletLoader().getWallets().size() > 1;
 585  }
 586  
 587  void WalletModel::refresh(bool pk_hash_only)
 588  {
 589      addressTableModel = new AddressTableModel(this, pk_hash_only);
 590  }
 591  
 592  uint256 WalletModel::getLastBlockProcessed() const
 593  {
 594      return m_client_model ? m_client_model->getBestBlockHash() : uint256{};
 595  }
 596  
 597  CAmount WalletModel::getAvailableBalance(const CCoinControl* control)
 598  {
 599      // No selected coins, return the cached balance
 600      if (!control || !control->HasSelected()) {
 601          const interfaces::WalletBalances& balances = getCachedBalance();
 602          return balances.balance;
 603      }
 604      // Fetch balance from the wallet, taking into account the selected coins
 605      return wallet().getAvailableBalance(*control);
 606  }
 607