diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 90e46c79815..b1eb70974d1 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -281,6 +281,8 @@ bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informa } // prepare transaction for getting txFee earlier + // Create unsigned transaction to support creating unsigned PSBTs. + // Signing is deferred until the user clicks "Send". m_current_transaction = std::make_unique(recipients); WalletModel::SendCoinsReturn prepareStatus; @@ -288,7 +290,7 @@ bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informa CCoinControl coin_control = *m_coin_control; coin_control.m_allow_other_inputs = !coin_control.HasSelected(); // future, could introduce a checkbox to customize this value. - prepareStatus = model->prepareTransaction(*m_current_transaction, coin_control); + prepareStatus = model->prepareTransaction(*m_current_transaction, coin_control, /*sign=*/false); // process prepareStatus and on error generate message shown to user processSendCoinsReturn(prepareStatus, @@ -515,6 +517,12 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked) presentPSBT(psbtx); } else { // "Send" clicked + WalletModel::UnlockContext ctx(model->requestUnlock()); + if (!ctx.isValid()) { + fNewRecipientAllowed = true; + return; + } + assert(!model->wallet().privateKeysDisabled() || model->wallet().hasExternalSigner()); bool broadcast = true; if (model->wallet().hasExternalSigner()) { @@ -540,6 +548,24 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked) presentPSBT(psbtx); } } + } else { + // Sign the transaction now that the user has confirmed they want to send. + CMutableTransaction mtx = CMutableTransaction{*(m_current_transaction->getWtx())}; + PartiallySignedTransaction psbtx(mtx); + bool complete = false; + // Fill and sign the PSBT + const auto err{model->wallet().fillPSBT(std::nullopt, /*sign=*/true, /*bip32derivs=*/false, /*n_signed=*/nullptr, psbtx, complete)}; + if (err || !complete) { + Q_EMIT message(tr("Send Coins"), tr("Failed to sign transaction."), + CClientUIInterface::MSG_ERROR); + send_failure = true; + broadcast = false; + } else { + // Extract the signed transaction + CHECK_NONFATAL(FinalizeAndExtractPSBT(psbtx, mtx)); + const CTransactionRef tx = MakeTransactionRef(mtx); + m_current_transaction->setWtx(tx); + } } // Broadcast the transaction, unless an external signer was used and it diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 7a35f8e4d35..febd5f90814 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -148,7 +148,7 @@ bool WalletModel::validateAddress(const QString& address) const return IsValidDestinationString(address.toStdString()); } -WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction &transaction, const CCoinControl& coinControl) +WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransaction &transaction, const CCoinControl& coinControl, bool sign) { transaction.getWtx() = nullptr; // reset tx output @@ -203,7 +203,9 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact try { auto& newTx = transaction.getWtx(); - const auto& res = m_wallet->createTransaction(vecSend, coinControl, /*sign=*/!wallet().privateKeysDisabled(), /*change_pos=*/std::nullopt); + // Only sign if explicitly requested via the sign parameter (e.g. when user clicks Send). + const bool should_sign = sign && !wallet().privateKeysDisabled(); + const auto& res = m_wallet->createTransaction(vecSend, coinControl, should_sign, /*change_pos=*/std::nullopt); if (!res) { Q_EMIT message(tr("Send Coins"), QString::fromStdString(util::ErrorString(res).translated), CClientUIInterface::MSG_ERROR); @@ -217,6 +219,10 @@ WalletModel::SendCoinsReturn WalletModel::prepareTransaction(WalletModelTransact transaction.reassignAmounts(static_cast(res->change_pos.value_or(-1))); } + if (!fSubtractFeeFromAmount && (total + nFeeRequired) > nBalance) { + return SendCoinsReturn(AmountExceedsBalance); + } + // 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). diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index ced057574b8..85b5255b3a9 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -95,7 +95,7 @@ class WalletModel : public QObject }; // prepare transaction for getting txfee before sending coins - SendCoinsReturn prepareTransaction(WalletModelTransaction &transaction, const wallet::CCoinControl& coinControl); + SendCoinsReturn prepareTransaction(WalletModelTransaction &transaction, const wallet::CCoinControl& coinControl, bool sign = false); // Send coins to a list of recipients void sendCoins(WalletModelTransaction& transaction);