Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/qt/forms/optionsdialog.ui
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,16 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="m_enable_psbt_controls">
<property name="text">
<string extracomment="An options window setting to enable PSBT controls.">Enable &amp;PSBT controls</string>
</property>
<property name="toolTip">
<string extracomment="Tooltip text for options window setting that enables PSBT controls.">Whether to show PSBT controls.</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
Expand Down
1 change: 1 addition & 0 deletions src/qt/optionsdialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ void OptionsDialog::setMapper()
mapper->addMapping(ui->coinControlFeatures, OptionsModel::CoinControlFeatures);
mapper->addMapping(ui->subFeeFromAmount, OptionsModel::SubFeeFromAmount);
mapper->addMapping(ui->externalSignerPath, OptionsModel::ExternalSignerPath);
mapper->addMapping(ui->m_enable_psbt_controls, OptionsModel::EnablePSBTControls);

/* Network */
mapper->addMapping(ui->mapPortUpnp, OptionsModel::MapPortUPnP);
Expand Down
11 changes: 11 additions & 0 deletions src/qt/optionsmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ void OptionsModel::Init(bool resetSettings)
settings.setValue("fCoinControlFeatures", false);
fCoinControlFeatures = settings.value("fCoinControlFeatures", false).toBool();

if (!settings.contains("enable_psbt_controls")) {
settings.setValue("enable_psbt_controls", false);
}
m_enable_psbt_controls = settings.value("enable_psbt_controls", false).toBool();

// These are shared with the core or have a command-line parameter
// and we want command-line parameters to overwrite the GUI settings.
//
Expand Down Expand Up @@ -360,6 +365,8 @@ QVariant OptionsModel::data(const QModelIndex & index, int role) const
return m_use_embedded_monospaced_font;
case CoinControlFeatures:
return fCoinControlFeatures;
case EnablePSBTControls:
return settings.value("enable_psbt_controls");
case Prune:
return settings.value("bPrune");
case PruneSize:
Expand Down Expand Up @@ -507,6 +514,10 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in
settings.setValue("fCoinControlFeatures", fCoinControlFeatures);
Q_EMIT coinControlFeaturesChanged(fCoinControlFeatures);
break;
case EnablePSBTControls:
m_enable_psbt_controls = value.toBool();
settings.setValue("enable_psbt_controls", m_enable_psbt_controls);
break;
case Prune:
if (settings.value("bPrune") != value) {
settings.setValue("bPrune", value);
Expand Down
3 changes: 3 additions & 0 deletions src/qt/optionsmodel.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ class OptionsModel : public QAbstractListModel
SpendZeroConfChange, // bool
Listen, // bool
Server, // bool
EnablePSBTControls, // bool
OptionIDRowCount,
};

Expand All @@ -91,6 +92,7 @@ class OptionsModel : public QAbstractListModel
bool getUseEmbeddedMonospacedFont() const { return m_use_embedded_monospaced_font; }
bool getCoinControlFeatures() const { return fCoinControlFeatures; }
bool getSubFeeFromAmount() const { return m_sub_fee_from_amount; }
bool getEnablePSBTControls() const { return m_enable_psbt_controls; }
const QString& getOverriddenByCommandLine() { return strOverriddenByCommandLine; }

/* Explicit setters */
Expand All @@ -116,6 +118,7 @@ class OptionsModel : public QAbstractListModel
bool m_use_embedded_monospaced_font;
bool fCoinControlFeatures;
bool m_sub_fee_from_amount;
bool m_enable_psbt_controls;
/* settings that were overridden by command-line */
QString strOverriddenByCommandLine;

Expand Down
54 changes: 35 additions & 19 deletions src/qt/sendcoinsdialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -324,16 +324,22 @@ bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informa
formatted.append(recipientElement);
}

if (model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner()) {
question_string.append(tr("Do you want to draft this transaction?"));
} else {
question_string.append(tr("Are you sure you want to send?"));
}

/*: Message displayed when attempting to create a transaction. Cautionary text to prompt the user to verify
that the displayed transaction details represent the transaction the user intends to create. */
question_string.append(tr("Do you want to create this transaction?"));
question_string.append("<br /><span style='font-size:10pt;'>");
if (model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner()) {
/*: Text to inform a user attempting to create a transaction of their current options. At this stage,
a user can only create a PSBT. This string is displayed when private keys are disabled and an external
signer is not available. */
question_string.append(tr("Please, review your transaction proposal. This will produce a Partially Signed Bitcoin Transaction (PSBT) which you can save or copy and then sign with e.g. an offline %1 wallet, or a PSBT-compatible hardware wallet.").arg(PACKAGE_NAME));
} else if (model->getOptionsModel()->getEnablePSBTControls()) {
/*: Text to inform a user attempting to create a transaction of their current options. At this stage,
a user can send their transaction or create a PSBT. This string is displayed when both private keys
and PSBT controls are enabled. */
question_string.append(tr("Please, review your transaction. You can create and send this transaction or create a Partially Signed Bitcoin Transaction (PSBT), which you can save or copy and then sign with, e.g., an offline %1 wallet, or a PSBT-compatible hardware wallet.").arg(PACKAGE_NAME));
} else {
/*: Text to prompt a user to review the details of the transaction they are attempting to send. */
question_string.append(tr("Please, review your transaction."));
}
question_string.append("</span>%1");
Expand Down Expand Up @@ -397,21 +403,20 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
if (!PrepareSendText(question_string, informative_text, detailed_text)) return;
assert(m_current_transaction);

const QString confirmation = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner() ? tr("Confirm transaction proposal") : tr("Confirm send coins");
const QString confirmButtonText = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner() ? tr("Create Unsigned") : tr("Sign and send");
auto confirmationDialog = new SendConfirmationDialog(confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, confirmButtonText, this);
const QString confirmation = tr("Confirm send coins");
auto confirmationDialog = new SendConfirmationDialog(confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, !model->wallet().privateKeysDisabled(), model->getOptionsModel()->getEnablePSBTControls(), this);
confirmationDialog->setAttribute(Qt::WA_DeleteOnClose);
// TODO: Replace QDialog::exec() with safer QDialog::show().
const auto retval = static_cast<QMessageBox::StandardButton>(confirmationDialog->exec());

if(retval != QMessageBox::Yes)
if(retval != QMessageBox::Yes && retval != QMessageBox::Save)
{
fNewRecipientAllowed = true;
return;
}

bool send_failure = false;
if (model->wallet().privateKeysDisabled()) {
if (retval == QMessageBox::Save) {
CMutableTransaction mtx = CMutableTransaction{*(m_current_transaction->getWtx())};
PartiallySignedTransaction psbtx(mtx);
bool complete = false;
Expand Down Expand Up @@ -512,6 +517,7 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
assert(false);
} // msgBox.exec()
} else {
assert(!model->wallet().privateKeysDisabled());
// now send the prepared transaction
WalletModel::SendCoinsReturn sendStatus = model->sendCoins(*m_current_transaction);
// process sendStatus and on error generate message shown to user
Expand Down Expand Up @@ -1031,52 +1037,62 @@ void SendCoinsDialog::coinControlUpdateLabels()
}
}

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)
SendConfirmationDialog::SendConfirmationDialog(const QString& title, const QString& text, const QString& informative_text, const QString& detailed_text, int _secDelay, bool enable_send, bool always_show_unsigned, QWidget* parent)
: QMessageBox(parent), secDelay(_secDelay), m_enable_send(enable_send)
{
setIcon(QMessageBox::Question);
setWindowTitle(title); // On macOS, the window title is ignored (as required by the macOS Guidelines).
setText(text);
setInformativeText(informative_text);
setDetailedText(detailed_text);
setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel);
if (always_show_unsigned || !enable_send) addButton(QMessageBox::Save);
setDefaultButton(QMessageBox::Cancel);
yesButton = button(QMessageBox::Yes);
if (confirmButtonText.isEmpty()) {
confirmButtonText = yesButton->text();
}
updateYesButton();
m_psbt_button = button(QMessageBox::Save);
updateButtons();
connect(&countDownTimer, &QTimer::timeout, this, &SendConfirmationDialog::countDown);
}

int SendConfirmationDialog::exec()
{
updateYesButton();
updateButtons();
countDownTimer.start(1000);
return QMessageBox::exec();
}

void SendConfirmationDialog::countDown()
{
secDelay--;
updateYesButton();
updateButtons();

if(secDelay <= 0)
{
countDownTimer.stop();
}
}

void SendConfirmationDialog::updateYesButton()
void SendConfirmationDialog::updateButtons()
{
if(secDelay > 0)
{
yesButton->setEnabled(false);
yesButton->setText(confirmButtonText + " (" + QString::number(secDelay) + ")");
yesButton->setText(confirmButtonText + (m_enable_send ? (" (" + QString::number(secDelay) + ")") : QString("")));
if (m_psbt_button) {
m_psbt_button->setEnabled(false);
m_psbt_button->setText(m_psbt_button_text + " (" + QString::number(secDelay) + ")");
}
}
else
{
yesButton->setEnabled(true);
yesButton->setEnabled(m_enable_send);
yesButton->setText(confirmButtonText);
if (m_psbt_button) {
m_psbt_button->setEnabled(true);
m_psbt_button->setText(m_psbt_button_text);
}
}
}
9 changes: 6 additions & 3 deletions src/qt/sendcoinsdialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,18 +114,21 @@ class SendConfirmationDialog : public QMessageBox
Q_OBJECT

public:
SendConfirmationDialog(const QString& title, const QString& text, const QString& informative_text = "", const QString& detailed_text = "", int secDelay = SEND_CONFIRM_DELAY, const QString& confirmText = "", QWidget* parent = nullptr);
SendConfirmationDialog(const QString& title, const QString& text, const QString& informative_text = "", const QString& detailed_text = "", int secDelay = SEND_CONFIRM_DELAY, bool enable_send = true, bool always_show_unsigned = true, QWidget* parent = nullptr);
int exec() override;

private Q_SLOTS:
void countDown();
void updateYesButton();
void updateButtons();

private:
QAbstractButton *yesButton;
QAbstractButton *m_psbt_button;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style nit:

Suggested change
QAbstractButton *m_psbt_button;
QAbstractButton* m_psbt_button;

QTimer countDownTimer;
int secDelay;
QString confirmButtonText;
QString confirmButtonText{tr("Send")};
bool m_enable_send;
QString m_psbt_button_text{tr("Create Unsigned")};
Comment on lines +129 to +131
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

Suggested change
QString confirmButtonText{tr("Send")};
bool m_enable_send;
QString m_psbt_button_text{tr("Create Unsigned")};
const QString confirmButtonText{tr("Send")};
const bool m_enable_send;
const QString m_psbt_button_text{tr("Create Unsigned")};

};

#endif // BITCOIN_QT_SENDCOINSDIALOG_H
13 changes: 7 additions & 6 deletions src/qt/walletmodel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -480,10 +480,9 @@ bool WalletModel::bumpFee(uint256 hash, uint256& new_hash)
return false;
}

const bool create_psbt = m_wallet->privateKeysDisabled();

// allow a user based fee verification
QString questionString = create_psbt ? tr("Do you want to draft a transaction with fee increase?") : tr("Do you want to increase the fee?");
/*: 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>");
Expand All @@ -506,13 +505,13 @@ bool WalletModel::bumpFee(uint256 hash, uint256& new_hash)
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."));
}

auto confirmationDialog = new SendConfirmationDialog(tr("Confirm fee bump"), questionString);
auto confirmationDialog = new SendConfirmationDialog(tr("Confirm fee bump"), questionString, "", "", SEND_CONFIRM_DELAY, !m_wallet->privateKeysDisabled(), getOptionsModel()->getEnablePSBTControls(), 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) {
if (retval != QMessageBox::Yes && retval != QMessageBox::Save) {
return false;
}

Expand All @@ -523,7 +522,7 @@ bool WalletModel::bumpFee(uint256 hash, uint256& new_hash)
}

// Short-circuit if we are returning a bumped transaction PSBT to clipboard
if (create_psbt) {
if (retval == QMessageBox::Save) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As m_psbt_button = button(QMessageBox::Save); is not near, maybe add a comment which describes that this code actually handles "Create Unsigned" button press?

Is // Short-circuit if we are returning a bumped transaction PSBT to clipboard comment above still relevant?

PartiallySignedTransaction psbtx(mtx);
bool complete = false;
const TransactionError err = wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, nullptr, psbtx, complete);
Expand All @@ -539,6 +538,8 @@ bool WalletModel::bumpFee(uint256 hash, uint256& new_hash)
return true;
}

assert(!m_wallet->privateKeysDisabled());

// sign bumped transaction
if (!m_wallet->signBumpTransaction(mtx)) {
QMessageBox::critical(nullptr, tr("Fee bump error"), tr("Can't sign transaction."));
Expand Down