diff --git a/src/qt/sendcoinsdialog.h b/src/qt/sendcoinsdialog.h --- a/src/qt/sendcoinsdialog.h +++ b/src/qt/sendcoinsdialog.h @@ -118,6 +118,7 @@ const QString &informative_text = "", const QString &detailed_text = "", int secDelay = SEND_CONFIRM_DELAY, + const QString &confirmText = "Send", QWidget *parent = nullptr); int exec(); @@ -129,6 +130,7 @@ QAbstractButton *yesButton; QTimer countDownTimer; int secDelay; + QString confirmButtonText; }; #endif // BITCOIN_QT_SENDCOINSDIALOG_H diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -216,6 +217,15 @@ updateFeeSectionControls(); updateMinFeeLabel(); updateSmartFeeLabel(); + + if (model->privateKeysDisabled()) { + ui->sendButton->setText(tr("Cr&eate Unsigned")); + ui->sendButton->setToolTip( + tr("Creates a Partially Signed Bitcoin Transaction (PSBT) for " + "use with e.g. an offline %1 wallet, or a PSBT-compatible " + "hardware wallet.") + .arg(PACKAGE_NAME)); + } } } @@ -334,9 +344,24 @@ formatted.append(recipientElement); } - QString questionString = tr("Are you sure you want to send?"); + QString questionString; + if (model->privateKeysDisabled()) { + questionString.append(tr("Do you want to draft this transaction?")); + } else { + questionString.append(tr("Are you sure you want to send?")); + } + questionString.append("
"); - questionString.append(tr("Please, review your transaction.")); + if (model->privateKeysDisabled()) { + questionString.append( + tr("Please, review your transaction proposal. This will produce a " + "Partially Signed Bitcoin Transaction (PSBT) which you can copy " + "and then sign with e.g. an offline %1 wallet, or a " + "PSBT-compatible hardware wallet.") + .arg(PACKAGE_NAME)); + } else { + questionString.append(tr("Please, review your transaction.")); + } questionString.append("%1"); if (txFee > Amount::zero()) { @@ -391,9 +416,15 @@ questionString = questionString.arg("

" + formatted.at(0)); } + const QString confirmation = model->privateKeysDisabled() + ? tr("Confirm transaction proposal") + : tr("Confirm send coins"); + const QString confirmButtonText = model->privateKeysDisabled() + ? tr("Copy PSBT to clipboard") + : tr("Send"); SendConfirmationDialog confirmationDialog( - tr("Confirm send coins"), questionString, informative_text, - detailed_text, SEND_CONFIRM_DELAY, this); + confirmation, questionString, informative_text, detailed_text, + SEND_CONFIRM_DELAY, confirmButtonText, this); confirmationDialog.exec(); QMessageBox::StandardButton retval = static_cast(confirmationDialog.result()); @@ -403,17 +434,40 @@ return; } - // now send the prepared transaction - WalletModel::SendCoinsReturn sendStatus = - model->sendCoins(currentTransaction); - // process sendStatus and on error generate message shown to user - processSendCoinsReturn(sendStatus); - - if (sendStatus.status == WalletModel::OK) { + bool send_failure = false; + if (model->privateKeysDisabled()) { + CMutableTransaction mtx = + CMutableTransaction{*(currentTransaction.getWtx())}; + PartiallySignedTransaction psbtx(mtx); + bool complete = false; + const TransactionError err = model->wallet().fillPSBT( + psbtx, complete, SigHashType().withForkId(), false /* sign */, + true /* bip32derivs */); + assert(!complete); + assert(err == TransactionError::OK); + // Serialize the PSBT + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << psbtx; + GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str()); + Q_EMIT message(tr("PSBT copied"), "Copied to clipboard", + CClientUIInterface::MSG_INFORMATION); + } else { + // now send the prepared transaction + WalletModel::SendCoinsReturn sendStatus = + model->sendCoins(currentTransaction); + // process sendStatus and on error generate message shown to user + processSendCoinsReturn(sendStatus); + + if (sendStatus.status == WalletModel::OK) { + Q_EMIT coinsSent(currentTransaction.getWtx()->GetId()); + } else { + send_failure = true; + } + } + if (!send_failure) { accept(); CoinControlDialog::coinControl()->UnSelectAll(); coinControlUpdateLabels(); - Q_EMIT coinsSent(currentTransaction.getWtx()->GetId()); } fNewRecipientAllowed = true; } @@ -922,12 +976,12 @@ } } -SendConfirmationDialog::SendConfirmationDialog(const QString &title, - const QString &text, - const QString &informative_text, - const QString &detailed_text, - int _secDelay, QWidget *parent) - : QMessageBox(parent), secDelay(_secDelay) { +SendConfirmationDialog::SendConfirmationDialog( + const QString &title, const QString &text, const QString &informative_text, + const QString &detailed_text, int _secDelay, + const QString &_confirmButtonText, QWidget *parent) + : QMessageBox(parent), secDelay(_secDelay), + confirmButtonText(_confirmButtonText) { setIcon(QMessageBox::Question); // On macOS, the window title is ignored (as required by the macOS // Guidelines). @@ -961,9 +1015,10 @@ void SendConfirmationDialog::updateYesButton() { if (secDelay > 0) { yesButton->setEnabled(false); - yesButton->setText(tr("Send") + " (" + QString::number(secDelay) + ")"); + yesButton->setText(confirmButtonText + " (" + + QString::number(secDelay) + ")"); } else { yesButton->setEnabled(true); - yesButton->setText(tr("Send")); + yesButton->setText(confirmButtonText); } } diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -201,7 +201,8 @@ bilingual_str error; auto &newTx = transaction.getWtx(); - newTx = m_wallet->createTransaction(vecSend, coinControl, true /* sign */, + newTx = m_wallet->createTransaction(vecSend, coinControl, + !privateKeysDisabled() /* sign */, nChangePosRet, nFeeRequired, error); transaction.setTransactionFee(nFeeRequired); if (fSubtractFeeFromAmount && newTx) {