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