@@ -199,7 +199,16 @@ void SendCoinsDialog::setModel(WalletModel *_model)
199199 // set default rbf checkbox state
200200 ui->optInRBF ->setCheckState (Qt::Checked);
201201
202- if (model->wallet ().privateKeysDisabled ()) {
202+ if (model->wallet ().hasExternalSigner ()) {
203+ ui->sendButton ->setText (tr (" Sign on device" ));
204+ if (gArgs .GetArg (" -signer" , " " ) != " " ) {
205+ ui->sendButton ->setEnabled (true );
206+ ui->sendButton ->setToolTip (tr (" Connect your hardware wallet first." ));
207+ } else {
208+ ui->sendButton ->setEnabled (false );
209+ ui->sendButton ->setToolTip (tr (" Set external signer script path in Options -> Wallet" ));
210+ }
211+ } else if (model->wallet ().privateKeysDisabled ()) {
203212 ui->sendButton ->setText (tr (" Cr&eate Unsigned" ));
204213 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));
205214 }
@@ -313,14 +322,14 @@ bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informa
313322 formatted.append (recipientElement);
314323 }
315324
316- if (model->wallet ().privateKeysDisabled ()) {
325+ if (model->wallet ().privateKeysDisabled () && !model-> wallet (). hasExternalSigner () ) {
317326 question_string.append (tr (" Do you want to draft this transaction?" ));
318327 } else {
319328 question_string.append (tr (" Are you sure you want to send?" ));
320329 }
321330
322331 question_string.append (" <br /><span style='font-size:10pt;'>" );
323- if (model->wallet ().privateKeysDisabled ()) {
332+ if (model->wallet ().privateKeysDisabled () && !model-> wallet (). hasExternalSigner () ) {
324333 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));
325334 } else {
326335 question_string.append (tr (" Please, review your transaction." ));
@@ -386,8 +395,8 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
386395 if (!PrepareSendText (question_string, informative_text, detailed_text)) return ;
387396 assert (m_current_transaction);
388397
389- const QString confirmation = model->wallet ().privateKeysDisabled () ? tr (" Confirm transaction proposal" ) : tr (" Confirm send coins" );
390- const QString confirmButtonText = model->wallet ().privateKeysDisabled () ? tr (" Create Unsigned" ) : tr (" Send " );
398+ const QString confirmation = model->wallet ().privateKeysDisabled () && !model-> wallet (). hasExternalSigner () ? tr (" Confirm transaction proposal" ) : tr (" Confirm send coins" );
399+ const QString confirmButtonText = model->wallet ().privateKeysDisabled () && !model-> wallet (). hasExternalSigner () ? tr (" Create Unsigned" ) : tr (" Sign and send " );
391400 SendConfirmationDialog confirmationDialog (confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, confirmButtonText, this );
392401 confirmationDialog.exec ();
393402 QMessageBox::StandardButton retval = static_cast <QMessageBox::StandardButton>(confirmationDialog.result ());
@@ -403,9 +412,58 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
403412 CMutableTransaction mtx = CMutableTransaction{*(m_current_transaction->getWtx ())};
404413 PartiallySignedTransaction psbtx (mtx);
405414 bool complete = false ;
406- const TransactionError err = model->wallet ().fillPSBT (SIGHASH_ALL, false /* sign */ , true /* bip32derivs */ , psbtx, complete, nullptr );
415+ // Always fill without signing first. This prevents an external signer
416+ // from being called prematurely and is not expensive.
417+ TransactionError err = model->wallet ().fillPSBT (SIGHASH_ALL, false /* sign */ , true /* bip32derivs */ , psbtx, complete, nullptr );
407418 assert (!complete);
408419 assert (err == TransactionError::OK);
420+ if (model->wallet ().hasExternalSigner ()) {
421+ try {
422+ err = model->wallet ().fillPSBT (SIGHASH_ALL, true /* sign */ , true /* bip32derivs */ , psbtx, complete, nullptr );
423+ } catch (const std::runtime_error& e) {
424+ QMessageBox::critical (nullptr , tr (" Sign failed" ), e.what ());
425+ send_failure = true ;
426+ return ;
427+ }
428+ if (err == TransactionError::EXTERNAL_SIGNER_NOT_FOUND) {
429+ QMessageBox::critical (nullptr , tr (" External signer not found" ), " External signer not found" );
430+ send_failure = true ;
431+ return ;
432+ }
433+ if (err == TransactionError::EXTERNAL_SIGNER_FAILED) {
434+ QMessageBox::critical (nullptr , tr (" External signer failure" ), " External signer failure" );
435+ send_failure = true ;
436+ return ;
437+ }
438+ if (err != TransactionError::OK) {
439+ tfm::format (std::cerr, " Failed to sign PSBT" );
440+ processSendCoinsReturn (WalletModel::TransactionCreationFailed);
441+ send_failure = true ;
442+ return ;
443+ }
444+ // fillPSBT does not always properly finalize
445+ complete = FinalizeAndExtractPSBT (psbtx, mtx);
446+ }
447+
448+ // Broadcast transaction if complete (even with an external signer this
449+ // is not always the case, e.g. in a multisig wallet).
450+ if (complete) {
451+ const CTransactionRef tx = MakeTransactionRef (mtx);
452+ m_current_transaction->setWtx (tx);
453+ WalletModel::SendCoinsReturn sendStatus = model->sendCoins (*m_current_transaction);
454+ // process sendStatus and on error generate message shown to user
455+ processSendCoinsReturn (sendStatus);
456+
457+ if (sendStatus.status == WalletModel::OK) {
458+ Q_EMIT coinsSent (m_current_transaction->getWtx ()->GetHash ());
459+ } else {
460+ send_failure = true ;
461+ }
462+ return ;
463+ }
464+
465+ // Copy PSBT to clipboard and offer to save
466+ assert (!complete);
409467 // Serialize the PSBT
410468 CDataStream ssTx (SER_NETWORK, PROTOCOL_VERSION);
411469 ssTx << psbtx;
@@ -447,7 +505,7 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
447505 break ;
448506 default :
449507 assert (false );
450- }
508+ } // msgBox.exec()
451509 } else {
452510 // now send the prepared transaction
453511 WalletModel::SendCoinsReturn sendStatus = model->sendCoins (*m_current_transaction);
@@ -614,7 +672,9 @@ void SendCoinsDialog::setBalance(const interfaces::WalletBalances& balances)
614672 if (model && model->getOptionsModel ())
615673 {
616674 CAmount balance = balances.balance ;
617- if (model->wallet ().privateKeysDisabled ()) {
675+ if (model->wallet ().hasExternalSigner ()) {
676+ ui->labelBalanceName ->setText (tr (" External balance:" ));
677+ } else if (model->wallet ().privateKeysDisabled ()) {
618678 balance = balances.watch_only_balance ;
619679 ui->labelBalanceName ->setText (tr (" Watch-only balance:" ));
620680 }
@@ -698,7 +758,7 @@ void SendCoinsDialog::on_buttonMinimizeFee_clicked()
698758void SendCoinsDialog::useAvailableBalance (SendCoinsEntry* entry)
699759{
700760 // Include watch-only for wallets without private key
701- m_coin_control->fAllowWatchOnly = model->wallet ().privateKeysDisabled ();
761+ m_coin_control->fAllowWatchOnly = model->wallet ().privateKeysDisabled () && !model-> wallet (). hasExternalSigner () ;
702762
703763 // Calculate available amount to send.
704764 CAmount amount = model->wallet ().getAvailableBalance (*m_coin_control);
@@ -753,7 +813,7 @@ void SendCoinsDialog::updateCoinControlState()
753813 m_coin_control->m_confirm_target = getConfTargetForIndex (ui->confTargetSelector ->currentIndex ());
754814 m_coin_control->m_signal_bip125_rbf = ui->optInRBF ->isChecked ();
755815 // Include watch-only for wallets without private key
756- m_coin_control->fAllowWatchOnly = model->wallet ().privateKeysDisabled ();
816+ m_coin_control->fAllowWatchOnly = model->wallet ().privateKeysDisabled () && !model-> wallet (). hasExternalSigner () ;
757817}
758818
759819void SendCoinsDialog::updateNumberOfBlocks (int count, const QDateTime& blockDate, double nVerificationProgress, bool headers, SynchronizationState sync_state) {
0 commit comments