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