mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-02-24 12:41:41 -05:00

671b7a3251
gui: fix create unsigned transaction fee bump (furszy) Pull request description: Fixes #810. Not much to explain; we were requiring the wallet to be unlocked for the unsigned transaction creation process. Fix this by moving the unlock wallet request to the signed transaction creation process. ACKs for top commit: pablomartin4btc: tACK671b7a3251
hebasto: ACK671b7a3251
, tested on Ubuntu 24.04. Tree-SHA512: 5b9ec5a1b91c014c05c83c63daaa8ba33f9dc1bfa930442315a0913db710df17a1b6bb4ad39f1419a7054f37ebedb7ad52e1c5d3d2fb444b1676162e89a4efd2
630 lines
22 KiB
C++
630 lines
22 KiB
C++
// Copyright (c) 2011-2022 The Bitcoin Core developers
|
|
// Distributed under the MIT software license, see the accompanying
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
#include <qt/walletmodel.h>
|
|
|
|
#include <qt/addresstablemodel.h>
|
|
#include <qt/clientmodel.h>
|
|
#include <qt/guiconstants.h>
|
|
#include <qt/guiutil.h>
|
|
#include <qt/optionsmodel.h>
|
|
#include <qt/paymentserver.h>
|
|
#include <qt/recentrequeststablemodel.h>
|
|
#include <qt/sendcoinsdialog.h>
|
|
#include <qt/transactiontablemodel.h>
|
|
|
|
#include <common/args.h> // for GetBoolArg
|
|
#include <interfaces/handler.h>
|
|
#include <interfaces/node.h>
|
|
#include <key_io.h>
|
|
#include <node/interface_ui.h>
|
|
#include <psbt.h>
|
|
#include <util/translation.h>
|
|
#include <wallet/coincontrol.h>
|
|
#include <wallet/wallet.h> // for CRecipient
|
|
|
|
#include <stdint.h>
|
|
#include <functional>
|
|
|
|
#include <QDebug>
|
|
#include <QMessageBox>
|
|
#include <QSet>
|
|
#include <QTimer>
|
|
|
|
using wallet::CCoinControl;
|
|
using wallet::CRecipient;
|
|
using wallet::DEFAULT_DISABLE_WALLET;
|
|
|
|
WalletModel::WalletModel(std::unique_ptr<interfaces::Wallet> wallet, ClientModel& client_model, const PlatformStyle *platformStyle, QObject *parent) :
|
|
QObject(parent),
|
|
m_wallet(std::move(wallet)),
|
|
m_client_model(&client_model),
|
|
m_node(client_model.node()),
|
|
optionsModel(client_model.getOptionsModel()),
|
|
timer(new QTimer(this))
|
|
{
|
|
fHaveWatchOnly = m_wallet->haveWatchOnly();
|
|
addressTableModel = new AddressTableModel(this);
|
|
transactionTableModel = new TransactionTableModel(platformStyle, this);
|
|
recentRequestsTableModel = new RecentRequestsTableModel(this);
|
|
|
|
subscribeToCoreSignals();
|
|
}
|
|
|
|
WalletModel::~WalletModel()
|
|
{
|
|
unsubscribeFromCoreSignals();
|
|
}
|
|
|
|
void WalletModel::startPollBalance()
|
|
{
|
|
// Update the cached balance right away, so every view can make use of it,
|
|
// so them don't need to waste resources recalculating it.
|
|
pollBalanceChanged();
|
|
|
|
// This timer will be fired repeatedly to update the balance
|
|
// Since the QTimer::timeout is a private signal, it cannot be used
|
|
// in the GUIUtil::ExceptionSafeConnect directly.
|
|
connect(timer, &QTimer::timeout, this, &WalletModel::timerTimeout);
|
|
GUIUtil::ExceptionSafeConnect(this, &WalletModel::timerTimeout, this, &WalletModel::pollBalanceChanged);
|
|
timer->start(MODEL_UPDATE_DELAY);
|
|
}
|
|
|
|
void WalletModel::setClientModel(ClientModel* client_model)
|
|
{
|
|
m_client_model = client_model;
|
|
if (!m_client_model) timer->stop();
|
|
}
|
|
|
|
void WalletModel::updateStatus()
|
|
{
|
|
EncryptionStatus newEncryptionStatus = getEncryptionStatus();
|
|
|
|
if(cachedEncryptionStatus != newEncryptionStatus) {
|
|
Q_EMIT encryptionStatusChanged();
|
|
}
|
|
}
|
|
|
|
void WalletModel::pollBalanceChanged()
|
|
{
|
|
// Avoid recomputing wallet balances unless a TransactionChanged or
|
|
// BlockTip notification was received.
|
|
if (!fForceCheckBalanceChanged && m_cached_last_update_tip == getLastBlockProcessed()) return;
|
|
|
|
// Try to get balances and return early if locks can't be acquired. This
|
|
// avoids the GUI from getting stuck on periodical polls if the core is
|
|
// holding the locks for a longer time - for example, during a wallet
|
|
// rescan.
|
|
interfaces::WalletBalances new_balances;
|
|
uint256 block_hash;
|
|
if (!m_wallet->tryGetBalances(new_balances, block_hash)) {
|
|
return;
|
|
}
|
|
|
|
if (fForceCheckBalanceChanged || block_hash != m_cached_last_update_tip) {
|
|
fForceCheckBalanceChanged = false;
|
|
|
|
// Balance and number of transactions might have changed
|
|
m_cached_last_update_tip = block_hash;
|
|
|
|
checkBalanceChanged(new_balances);
|
|
if(transactionTableModel)
|
|
transactionTableModel->updateConfirmations();
|
|
}
|
|
}
|
|
|
|
void WalletModel::checkBalanceChanged(const interfaces::WalletBalances& new_balances)
|
|
{
|
|
if (new_balances.balanceChanged(m_cached_balances)) {
|
|
m_cached_balances = new_balances;
|
|
Q_EMIT balanceChanged(new_balances);
|
|
}
|
|
}
|
|
|
|
interfaces::WalletBalances WalletModel::getCachedBalance() const
|
|
{
|
|
return m_cached_balances;
|
|
}
|
|
|
|
void WalletModel::updateTransaction()
|
|
{
|
|
// Balance and number of transactions might have changed
|
|
fForceCheckBalanceChanged = true;
|
|
}
|
|
|
|
void WalletModel::updateAddressBook(const QString &address, const QString &label,
|
|
bool isMine, wallet::AddressPurpose purpose, int status)
|
|
{
|
|
if(addressTableModel)
|
|
addressTableModel->updateEntry(address, label, isMine, purpose, status);
|
|
}
|
|
|
|
void WalletModel::updateWatchOnlyFlag(bool fHaveWatchonly)
|
|
{
|
|
fHaveWatchOnly = fHaveWatchonly;
|
|
Q_EMIT notifyWatchonlyChanged(fHaveWatchonly);
|
|
}
|
|
|
|
bool WalletModel::validateAddress(const QString& address) const
|
|
{
|
|
return IsValidDestinationString(address.toStdString());
|
|
}
|
|
|
|
WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction &transaction, const CCoinControl& coinControl)
|
|
{
|
|
CAmount total = 0;
|
|
bool fSubtractFeeFromAmount = false;
|
|
QList<SendCoinsRecipient> recipients = transaction.getRecipients();
|
|
std::vector<CRecipient> vecSend;
|
|
|
|
if(recipients.empty())
|
|
{
|
|
return OK;
|
|
}
|
|
|
|
QSet<QString> setAddress; // Used to detect duplicates
|
|
int nAddresses = 0;
|
|
|
|
// Pre-check input data for validity
|
|
for (const SendCoinsRecipient &rcp : recipients)
|
|
{
|
|
if (rcp.fSubtractFeeFromAmount)
|
|
fSubtractFeeFromAmount = true;
|
|
{ // User-entered bitcoin address / amount:
|
|
if(!validateAddress(rcp.address))
|
|
{
|
|
return InvalidAddress;
|
|
}
|
|
if(rcp.amount <= 0)
|
|
{
|
|
return InvalidAmount;
|
|
}
|
|
setAddress.insert(rcp.address);
|
|
++nAddresses;
|
|
|
|
CRecipient recipient{DecodeDestination(rcp.address.toStdString()), rcp.amount, rcp.fSubtractFeeFromAmount};
|
|
vecSend.push_back(recipient);
|
|
|
|
total += rcp.amount;
|
|
}
|
|
}
|
|
if(setAddress.size() != nAddresses)
|
|
{
|
|
return DuplicateAddress;
|
|
}
|
|
|
|
// If no coin was manually selected, use the cached balance
|
|
// Future: can merge this call with 'createTransaction'.
|
|
CAmount nBalance = getAvailableBalance(&coinControl);
|
|
|
|
if(total > nBalance)
|
|
{
|
|
return AmountExceedsBalance;
|
|
}
|
|
|
|
try {
|
|
CAmount nFeeRequired = 0;
|
|
int nChangePosRet = -1;
|
|
|
|
auto& newTx = transaction.getWtx();
|
|
const auto& res = m_wallet->createTransaction(vecSend, coinControl, /*sign=*/!wallet().privateKeysDisabled(), nChangePosRet, nFeeRequired);
|
|
newTx = res ? *res : nullptr;
|
|
transaction.setTransactionFee(nFeeRequired);
|
|
if (fSubtractFeeFromAmount && newTx)
|
|
transaction.reassignAmounts(nChangePosRet);
|
|
|
|
if(!newTx)
|
|
{
|
|
if(!fSubtractFeeFromAmount && (total + nFeeRequired) > nBalance)
|
|
{
|
|
return SendCoinsReturn(AmountWithFeeExceedsBalance);
|
|
}
|
|
Q_EMIT message(tr("Send Coins"), QString::fromStdString(util::ErrorString(res).translated),
|
|
CClientUIInterface::MSG_ERROR);
|
|
return TransactionCreationFailed;
|
|
}
|
|
|
|
// Reject absurdly high fee. (This can never happen because the
|
|
// wallet never creates transactions with fee greater than
|
|
// m_default_max_tx_fee. This merely a belt-and-suspenders check).
|
|
if (nFeeRequired > m_wallet->getDefaultMaxTxFee()) {
|
|
return AbsurdFee;
|
|
}
|
|
} catch (const std::runtime_error& err) {
|
|
// Something unexpected happened, instruct user to report this bug.
|
|
Q_EMIT message(tr("Send Coins"), QString::fromStdString(err.what()),
|
|
CClientUIInterface::MSG_ERROR);
|
|
return TransactionCreationFailed;
|
|
}
|
|
|
|
return SendCoinsReturn(OK);
|
|
}
|
|
|
|
void WalletModel::sendCoins(WalletModelTransaction& transaction)
|
|
{
|
|
QByteArray transaction_array; /* store serialized transaction */
|
|
|
|
{
|
|
std::vector<std::pair<std::string, std::string>> vOrderForm;
|
|
for (const SendCoinsRecipient &rcp : transaction.getRecipients())
|
|
{
|
|
if (!rcp.message.isEmpty()) // Message from normal bitcoin:URI (bitcoin:123...?message=example)
|
|
vOrderForm.emplace_back("Message", rcp.message.toStdString());
|
|
}
|
|
|
|
auto& newTx = transaction.getWtx();
|
|
wallet().commitTransaction(newTx, /*value_map=*/{}, std::move(vOrderForm));
|
|
|
|
DataStream ssTx;
|
|
ssTx << TX_WITH_WITNESS(*newTx);
|
|
transaction_array.append((const char*)ssTx.data(), ssTx.size());
|
|
}
|
|
|
|
// Add addresses / update labels that we've sent to the address book,
|
|
// and emit coinsSent signal for each recipient
|
|
for (const SendCoinsRecipient &rcp : transaction.getRecipients())
|
|
{
|
|
{
|
|
std::string strAddress = rcp.address.toStdString();
|
|
CTxDestination dest = DecodeDestination(strAddress);
|
|
std::string strLabel = rcp.label.toStdString();
|
|
{
|
|
// Check if we have a new address or an updated label
|
|
std::string name;
|
|
if (!m_wallet->getAddress(
|
|
dest, &name, /* is_mine= */ nullptr, /* purpose= */ nullptr))
|
|
{
|
|
m_wallet->setAddressBook(dest, strLabel, wallet::AddressPurpose::SEND);
|
|
}
|
|
else if (name != strLabel)
|
|
{
|
|
m_wallet->setAddressBook(dest, strLabel, {}); // {} means don't change purpose
|
|
}
|
|
}
|
|
}
|
|
Q_EMIT coinsSent(this, rcp, transaction_array);
|
|
}
|
|
|
|
checkBalanceChanged(m_wallet->getBalances()); // update balance immediately, otherwise there could be a short noticeable delay until pollBalanceChanged hits
|
|
}
|
|
|
|
OptionsModel* WalletModel::getOptionsModel() const
|
|
{
|
|
return optionsModel;
|
|
}
|
|
|
|
AddressTableModel* WalletModel::getAddressTableModel() const
|
|
{
|
|
return addressTableModel;
|
|
}
|
|
|
|
TransactionTableModel* WalletModel::getTransactionTableModel() const
|
|
{
|
|
return transactionTableModel;
|
|
}
|
|
|
|
RecentRequestsTableModel* WalletModel::getRecentRequestsTableModel() const
|
|
{
|
|
return recentRequestsTableModel;
|
|
}
|
|
|
|
WalletModel::EncryptionStatus WalletModel::getEncryptionStatus() const
|
|
{
|
|
if(!m_wallet->isCrypted())
|
|
{
|
|
// A previous bug allowed for watchonly wallets to be encrypted (encryption keys set, but nothing is actually encrypted).
|
|
// To avoid misrepresenting the encryption status of such wallets, we only return NoKeys for watchonly wallets that are unencrypted.
|
|
if (m_wallet->privateKeysDisabled()) {
|
|
return NoKeys;
|
|
}
|
|
return Unencrypted;
|
|
}
|
|
else if(m_wallet->isLocked())
|
|
{
|
|
return Locked;
|
|
}
|
|
else
|
|
{
|
|
return Unlocked;
|
|
}
|
|
}
|
|
|
|
bool WalletModel::setWalletEncrypted(const SecureString& passphrase)
|
|
{
|
|
return m_wallet->encryptWallet(passphrase);
|
|
}
|
|
|
|
bool WalletModel::setWalletLocked(bool locked, const SecureString &passPhrase)
|
|
{
|
|
if(locked)
|
|
{
|
|
// Lock
|
|
return m_wallet->lock();
|
|
}
|
|
else
|
|
{
|
|
// Unlock
|
|
return m_wallet->unlock(passPhrase);
|
|
}
|
|
}
|
|
|
|
bool WalletModel::changePassphrase(const SecureString &oldPass, const SecureString &newPass)
|
|
{
|
|
m_wallet->lock(); // Make sure wallet is locked before attempting pass change
|
|
return m_wallet->changeWalletPassphrase(oldPass, newPass);
|
|
}
|
|
|
|
// Handlers for core signals
|
|
static void NotifyUnload(WalletModel* walletModel)
|
|
{
|
|
qDebug() << "NotifyUnload";
|
|
bool invoked = QMetaObject::invokeMethod(walletModel, "unload");
|
|
assert(invoked);
|
|
}
|
|
|
|
static void NotifyKeyStoreStatusChanged(WalletModel *walletmodel)
|
|
{
|
|
qDebug() << "NotifyKeyStoreStatusChanged";
|
|
bool invoked = QMetaObject::invokeMethod(walletmodel, "updateStatus", Qt::QueuedConnection);
|
|
assert(invoked);
|
|
}
|
|
|
|
static void NotifyAddressBookChanged(WalletModel *walletmodel,
|
|
const CTxDestination &address, const std::string &label, bool isMine,
|
|
wallet::AddressPurpose purpose, ChangeType status)
|
|
{
|
|
QString strAddress = QString::fromStdString(EncodeDestination(address));
|
|
QString strLabel = QString::fromStdString(label);
|
|
|
|
qDebug() << "NotifyAddressBookChanged: " + strAddress + " " + strLabel + " isMine=" + QString::number(isMine) + " purpose=" + QString::number(static_cast<uint8_t>(purpose)) + " status=" + QString::number(status);
|
|
bool invoked = QMetaObject::invokeMethod(walletmodel, "updateAddressBook",
|
|
Q_ARG(QString, strAddress),
|
|
Q_ARG(QString, strLabel),
|
|
Q_ARG(bool, isMine),
|
|
Q_ARG(wallet::AddressPurpose, purpose),
|
|
Q_ARG(int, status));
|
|
assert(invoked);
|
|
}
|
|
|
|
static void NotifyTransactionChanged(WalletModel *walletmodel, const uint256 &hash, ChangeType status)
|
|
{
|
|
Q_UNUSED(hash);
|
|
Q_UNUSED(status);
|
|
bool invoked = QMetaObject::invokeMethod(walletmodel, "updateTransaction", Qt::QueuedConnection);
|
|
assert(invoked);
|
|
}
|
|
|
|
static void ShowProgress(WalletModel *walletmodel, const std::string &title, int nProgress)
|
|
{
|
|
// emits signal "showProgress"
|
|
bool invoked = QMetaObject::invokeMethod(walletmodel, "showProgress", Qt::QueuedConnection,
|
|
Q_ARG(QString, QString::fromStdString(title)),
|
|
Q_ARG(int, nProgress));
|
|
assert(invoked);
|
|
}
|
|
|
|
static void NotifyWatchonlyChanged(WalletModel *walletmodel, bool fHaveWatchonly)
|
|
{
|
|
bool invoked = QMetaObject::invokeMethod(walletmodel, "updateWatchOnlyFlag", Qt::QueuedConnection,
|
|
Q_ARG(bool, fHaveWatchonly));
|
|
assert(invoked);
|
|
}
|
|
|
|
static void NotifyCanGetAddressesChanged(WalletModel* walletmodel)
|
|
{
|
|
bool invoked = QMetaObject::invokeMethod(walletmodel, "canGetAddressesChanged");
|
|
assert(invoked);
|
|
}
|
|
|
|
void WalletModel::subscribeToCoreSignals()
|
|
{
|
|
// Connect signals to wallet
|
|
m_handler_unload = m_wallet->handleUnload(std::bind(&NotifyUnload, this));
|
|
m_handler_status_changed = m_wallet->handleStatusChanged(std::bind(&NotifyKeyStoreStatusChanged, this));
|
|
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));
|
|
m_handler_transaction_changed = m_wallet->handleTransactionChanged(std::bind(NotifyTransactionChanged, this, std::placeholders::_1, std::placeholders::_2));
|
|
m_handler_show_progress = m_wallet->handleShowProgress(std::bind(ShowProgress, this, std::placeholders::_1, std::placeholders::_2));
|
|
m_handler_watch_only_changed = m_wallet->handleWatchOnlyChanged(std::bind(NotifyWatchonlyChanged, this, std::placeholders::_1));
|
|
m_handler_can_get_addrs_changed = m_wallet->handleCanGetAddressesChanged(std::bind(NotifyCanGetAddressesChanged, this));
|
|
}
|
|
|
|
void WalletModel::unsubscribeFromCoreSignals()
|
|
{
|
|
// Disconnect signals from wallet
|
|
m_handler_unload->disconnect();
|
|
m_handler_status_changed->disconnect();
|
|
m_handler_address_book_changed->disconnect();
|
|
m_handler_transaction_changed->disconnect();
|
|
m_handler_show_progress->disconnect();
|
|
m_handler_watch_only_changed->disconnect();
|
|
m_handler_can_get_addrs_changed->disconnect();
|
|
}
|
|
|
|
// WalletModel::UnlockContext implementation
|
|
WalletModel::UnlockContext WalletModel::requestUnlock()
|
|
{
|
|
// Bugs in earlier versions may have resulted in wallets with private keys disabled to become "encrypted"
|
|
// (encryption keys are present, but not actually doing anything).
|
|
// To avoid issues with such wallets, check if the wallet has private keys disabled, and if so, return a context
|
|
// that indicates the wallet is not encrypted.
|
|
if (m_wallet->privateKeysDisabled()) {
|
|
return UnlockContext(this, /*valid=*/true, /*relock=*/false);
|
|
}
|
|
bool was_locked = getEncryptionStatus() == Locked;
|
|
if(was_locked)
|
|
{
|
|
// Request UI to unlock wallet
|
|
Q_EMIT requireUnlock();
|
|
}
|
|
// If wallet is still locked, unlock was failed or cancelled, mark context as invalid
|
|
bool valid = getEncryptionStatus() != Locked;
|
|
|
|
return UnlockContext(this, valid, was_locked);
|
|
}
|
|
|
|
WalletModel::UnlockContext::UnlockContext(WalletModel *_wallet, bool _valid, bool _relock):
|
|
wallet(_wallet),
|
|
valid(_valid),
|
|
relock(_relock)
|
|
{
|
|
}
|
|
|
|
WalletModel::UnlockContext::~UnlockContext()
|
|
{
|
|
if(valid && relock)
|
|
{
|
|
wallet->setWalletLocked(true);
|
|
}
|
|
}
|
|
|
|
bool WalletModel::bumpFee(uint256 hash, uint256& new_hash)
|
|
{
|
|
CCoinControl coin_control;
|
|
coin_control.m_signal_bip125_rbf = true;
|
|
std::vector<bilingual_str> errors;
|
|
CAmount old_fee;
|
|
CAmount new_fee;
|
|
CMutableTransaction mtx;
|
|
if (!m_wallet->createBumpTransaction(hash, coin_control, errors, old_fee, new_fee, mtx)) {
|
|
QMessageBox::critical(nullptr, tr("Fee bump error"), tr("Increasing transaction fee failed") + "<br />(" +
|
|
(errors.size() ? QString::fromStdString(errors[0].translated) : "") +")");
|
|
return false;
|
|
}
|
|
|
|
// allow a user based fee verification
|
|
/*: Asks a user if they would like to manually increase the fee of a transaction that has already been created. */
|
|
QString questionString = tr("Do you want to increase the fee?");
|
|
questionString.append("<br />");
|
|
questionString.append("<table style=\"text-align: left;\">");
|
|
questionString.append("<tr><td>");
|
|
questionString.append(tr("Current fee:"));
|
|
questionString.append("</td><td>");
|
|
questionString.append(BitcoinUnits::formatHtmlWithUnit(getOptionsModel()->getDisplayUnit(), old_fee));
|
|
questionString.append("</td></tr><tr><td>");
|
|
questionString.append(tr("Increase:"));
|
|
questionString.append("</td><td>");
|
|
questionString.append(BitcoinUnits::formatHtmlWithUnit(getOptionsModel()->getDisplayUnit(), new_fee - old_fee));
|
|
questionString.append("</td></tr><tr><td>");
|
|
questionString.append(tr("New fee:"));
|
|
questionString.append("</td><td>");
|
|
questionString.append(BitcoinUnits::formatHtmlWithUnit(getOptionsModel()->getDisplayUnit(), new_fee));
|
|
questionString.append("</td></tr></table>");
|
|
|
|
// Display warning in the "Confirm fee bump" window if the "Coin Control Features" option is enabled
|
|
if (getOptionsModel()->getCoinControlFeatures()) {
|
|
questionString.append("<br><br>");
|
|
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."));
|
|
}
|
|
|
|
const bool enable_send{!wallet().privateKeysDisabled() || wallet().hasExternalSigner()};
|
|
const bool always_show_unsigned{getOptionsModel()->getEnablePSBTControls()};
|
|
auto confirmationDialog = new SendConfirmationDialog(tr("Confirm fee bump"), questionString, "", "", SEND_CONFIRM_DELAY, enable_send, always_show_unsigned, nullptr);
|
|
confirmationDialog->setAttribute(Qt::WA_DeleteOnClose);
|
|
// TODO: Replace QDialog::exec() with safer QDialog::show().
|
|
const auto retval = static_cast<QMessageBox::StandardButton>(confirmationDialog->exec());
|
|
|
|
// cancel sign&broadcast if user doesn't want to bump the fee
|
|
if (retval != QMessageBox::Yes && retval != QMessageBox::Save) {
|
|
return false;
|
|
}
|
|
|
|
// Short-circuit if we are returning a bumped transaction PSBT to clipboard
|
|
if (retval == QMessageBox::Save) {
|
|
// "Create Unsigned" clicked
|
|
PartiallySignedTransaction psbtx(mtx);
|
|
bool complete = false;
|
|
const TransactionError err = wallet().fillPSBT(SIGHASH_ALL, /*sign=*/false, /*bip32derivs=*/true, nullptr, psbtx, complete);
|
|
if (err != TransactionError::OK || complete) {
|
|
QMessageBox::critical(nullptr, tr("Fee bump error"), tr("Can't draft transaction."));
|
|
return false;
|
|
}
|
|
// Serialize the PSBT
|
|
DataStream ssTx{};
|
|
ssTx << psbtx;
|
|
GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str());
|
|
Q_EMIT message(tr("PSBT copied"), tr("Fee-bump PSBT copied to clipboard"), CClientUIInterface::MSG_INFORMATION | CClientUIInterface::MODAL);
|
|
return true;
|
|
}
|
|
|
|
WalletModel::UnlockContext ctx(requestUnlock());
|
|
if (!ctx.isValid()) {
|
|
return false;
|
|
}
|
|
|
|
assert(!m_wallet->privateKeysDisabled() || wallet().hasExternalSigner());
|
|
|
|
// sign bumped transaction
|
|
if (!m_wallet->signBumpTransaction(mtx)) {
|
|
QMessageBox::critical(nullptr, tr("Fee bump error"), tr("Can't sign transaction."));
|
|
return false;
|
|
}
|
|
// commit the bumped transaction
|
|
if(!m_wallet->commitBumpTransaction(hash, std::move(mtx), errors, new_hash)) {
|
|
QMessageBox::critical(nullptr, tr("Fee bump error"), tr("Could not commit transaction") + "<br />(" +
|
|
QString::fromStdString(errors[0].translated)+")");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void WalletModel::displayAddress(std::string sAddress) const
|
|
{
|
|
CTxDestination dest = DecodeDestination(sAddress);
|
|
try {
|
|
util::Result<void> result = m_wallet->displayAddress(dest);
|
|
if (!result) {
|
|
QMessageBox::warning(nullptr, tr("Signer error"), QString::fromStdString(util::ErrorString(result).translated));
|
|
}
|
|
} catch (const std::runtime_error& e) {
|
|
QMessageBox::critical(nullptr, tr("Can't display address"), e.what());
|
|
}
|
|
}
|
|
|
|
bool WalletModel::isWalletEnabled()
|
|
{
|
|
return !gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET);
|
|
}
|
|
|
|
QString WalletModel::getWalletName() const
|
|
{
|
|
return QString::fromStdString(m_wallet->getWalletName());
|
|
}
|
|
|
|
QString WalletModel::getDisplayName() const
|
|
{
|
|
const QString name = getWalletName();
|
|
return name.isEmpty() ? "["+tr("default wallet")+"]" : name;
|
|
}
|
|
|
|
bool WalletModel::isMultiwallet() const
|
|
{
|
|
return m_node.walletLoader().getWallets().size() > 1;
|
|
}
|
|
|
|
void WalletModel::refresh(bool pk_hash_only)
|
|
{
|
|
addressTableModel = new AddressTableModel(this, pk_hash_only);
|
|
}
|
|
|
|
uint256 WalletModel::getLastBlockProcessed() const
|
|
{
|
|
return m_client_model ? m_client_model->getBestBlockHash() : uint256{};
|
|
}
|
|
|
|
CAmount WalletModel::getAvailableBalance(const CCoinControl* control)
|
|
{
|
|
// No selected coins, return the cached balance
|
|
if (!control || !control->HasSelected()) {
|
|
const interfaces::WalletBalances& balances = getCachedBalance();
|
|
CAmount available_balance = balances.balance;
|
|
// if wallet private keys are disabled, this is a watch-only wallet
|
|
// so, let's include the watch-only balance.
|
|
if (balances.have_watch_only && m_wallet->privateKeysDisabled()) {
|
|
available_balance += balances.watch_only_balance;
|
|
}
|
|
return available_balance;
|
|
}
|
|
// Fetch balance from the wallet, taking into account the selected coins
|
|
return wallet().getAvailableBalance(*control);
|
|
}
|