aboutsummaryrefslogtreecommitdiff
path: root/src/qt/sendcoinsdialog.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/qt/sendcoinsdialog.cpp')
-rw-r--r--src/qt/sendcoinsdialog.cpp87
1 files changed, 76 insertions, 11 deletions
diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp
index e3ea6e9015..ff53d8160f 100644
--- a/src/qt/sendcoinsdialog.cpp
+++ b/src/qt/sendcoinsdialog.cpp
@@ -199,7 +199,18 @@ void SendCoinsDialog::setModel(WalletModel *_model)
// set default rbf checkbox state
ui->optInRBF->setCheckState(Qt::Checked);
- if (model->wallet().privateKeysDisabled()) {
+ if (model->wallet().hasExternalSigner()) {
+ //: "device" usually means a hardware wallet.
+ ui->sendButton->setText(tr("Sign on device"));
+ if (gArgs.GetArg("-signer", "") != "") {
+ ui->sendButton->setEnabled(true);
+ ui->sendButton->setToolTip(tr("Connect your hardware wallet first."));
+ } else {
+ ui->sendButton->setEnabled(false);
+ //: "External signer" means using devices such as hardware wallets.
+ ui->sendButton->setToolTip(tr("Set external signer script path in Options -> Wallet"));
+ }
+ } else if (model->wallet().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));
}
@@ -313,14 +324,14 @@ bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informa
formatted.append(recipientElement);
}
- if (model->wallet().privateKeysDisabled()) {
+ 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?"));
}
question_string.append("<br /><span style='font-size:10pt;'>");
- if (model->wallet().privateKeysDisabled()) {
+ if (model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner()) {
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 {
question_string.append(tr("Please, review your transaction."));
@@ -386,8 +397,8 @@ 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() ? tr("Confirm transaction proposal") : tr("Confirm send coins");
- const QString confirmButtonText = model->wallet().privateKeysDisabled() ? tr("Create Unsigned") : tr("Send");
+ 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");
SendConfirmationDialog confirmationDialog(confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, confirmButtonText, this);
confirmationDialog.exec();
QMessageBox::StandardButton retval = static_cast<QMessageBox::StandardButton>(confirmationDialog.result());
@@ -403,9 +414,60 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
CMutableTransaction mtx = CMutableTransaction{*(m_current_transaction->getWtx())};
PartiallySignedTransaction psbtx(mtx);
bool complete = false;
- const TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, psbtx, complete, nullptr);
+ // Always fill without signing first. This prevents an external signer
+ // from being called prematurely and is not expensive.
+ TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, nullptr, psbtx, complete);
assert(!complete);
assert(err == TransactionError::OK);
+ if (model->wallet().hasExternalSigner()) {
+ try {
+ err = model->wallet().fillPSBT(SIGHASH_ALL, true /* sign */, true /* bip32derivs */, nullptr, psbtx, complete);
+ } catch (const std::runtime_error& e) {
+ QMessageBox::critical(nullptr, tr("Sign failed"), e.what());
+ send_failure = true;
+ return;
+ }
+ if (err == TransactionError::EXTERNAL_SIGNER_NOT_FOUND) {
+ //: "External signer" means using devices such as hardware wallets.
+ QMessageBox::critical(nullptr, tr("External signer not found"), "External signer not found");
+ send_failure = true;
+ return;
+ }
+ if (err == TransactionError::EXTERNAL_SIGNER_FAILED) {
+ //: "External signer" means using devices such as hardware wallets.
+ QMessageBox::critical(nullptr, tr("External signer failure"), "External signer failure");
+ send_failure = true;
+ return;
+ }
+ if (err != TransactionError::OK) {
+ tfm::format(std::cerr, "Failed to sign PSBT");
+ processSendCoinsReturn(WalletModel::TransactionCreationFailed);
+ send_failure = true;
+ return;
+ }
+ // fillPSBT does not always properly finalize
+ complete = FinalizeAndExtractPSBT(psbtx, mtx);
+ }
+
+ // Broadcast transaction if complete (even with an external signer this
+ // is not always the case, e.g. in a multisig wallet).
+ if (complete) {
+ const CTransactionRef tx = MakeTransactionRef(mtx);
+ m_current_transaction->setWtx(tx);
+ WalletModel::SendCoinsReturn sendStatus = model->sendCoins(*m_current_transaction);
+ // process sendStatus and on error generate message shown to user
+ processSendCoinsReturn(sendStatus);
+
+ if (sendStatus.status == WalletModel::OK) {
+ Q_EMIT coinsSent(m_current_transaction->getWtx()->GetHash());
+ } else {
+ send_failure = true;
+ }
+ return;
+ }
+
+ // Copy PSBT to clipboard and offer to save
+ assert(!complete);
// Serialize the PSBT
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
ssTx << psbtx;
@@ -432,7 +494,8 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
fileNameSuggestion.append(".psbt");
QString filename = GUIUtil::getSaveFileName(this,
tr("Save Transaction Data"), fileNameSuggestion,
- tr("Partially Signed Transaction (Binary)", "Name of binary PSBT file format") + QLatin1String(" (*.psbt)"), &selectedFilter);
+ //: Expanded name of the binary PSBT file format. See: BIP 174.
+ tr("Partially Signed Transaction (Binary)") + QLatin1String(" (*.psbt)"), &selectedFilter);
if (filename.isEmpty()) {
return;
}
@@ -446,7 +509,7 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
break;
default:
assert(false);
- }
+ } // msgBox.exec()
} else {
// now send the prepared transaction
WalletModel::SendCoinsReturn sendStatus = model->sendCoins(*m_current_transaction);
@@ -613,7 +676,9 @@ void SendCoinsDialog::setBalance(const interfaces::WalletBalances& balances)
if(model && model->getOptionsModel())
{
CAmount balance = balances.balance;
- if (model->wallet().privateKeysDisabled()) {
+ if (model->wallet().hasExternalSigner()) {
+ ui->labelBalanceName->setText(tr("External balance:"));
+ } else if (model->wallet().privateKeysDisabled()) {
balance = balances.watch_only_balance;
ui->labelBalanceName->setText(tr("Watch-only balance:"));
}
@@ -697,7 +762,7 @@ void SendCoinsDialog::on_buttonMinimizeFee_clicked()
void SendCoinsDialog::useAvailableBalance(SendCoinsEntry* entry)
{
// Include watch-only for wallets without private key
- m_coin_control->fAllowWatchOnly = model->wallet().privateKeysDisabled();
+ m_coin_control->fAllowWatchOnly = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner();
// Calculate available amount to send.
CAmount amount = model->wallet().getAvailableBalance(*m_coin_control);
@@ -752,7 +817,7 @@ void SendCoinsDialog::updateCoinControlState()
m_coin_control->m_confirm_target = getConfTargetForIndex(ui->confTargetSelector->currentIndex());
m_coin_control->m_signal_bip125_rbf = ui->optInRBF->isChecked();
// Include watch-only for wallets without private key
- m_coin_control->fAllowWatchOnly = model->wallet().privateKeysDisabled();
+ m_coin_control->fAllowWatchOnly = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner();
}
void SendCoinsDialog::updateNumberOfBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, bool headers, SynchronizationState sync_state) {