@@ -199,7 +199,16 @@ void SendCoinsDialog::setModel(WalletModel *_model)
199
199
// set default rbf checkbox state
200
200
ui->optInRBF ->setCheckState (Qt::Checked);
201
201
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 ()) {
203
212
ui->sendButton ->setText (tr (" Cr&eate Unsigned" ));
204
213
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));
205
214
}
@@ -313,14 +322,14 @@ bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informa
313
322
formatted.append (recipientElement);
314
323
}
315
324
316
- if (model->wallet ().privateKeysDisabled ()) {
325
+ if (model->wallet ().privateKeysDisabled () && !model-> wallet (). hasExternalSigner () ) {
317
326
question_string.append (tr (" Do you want to draft this transaction?" ));
318
327
} else {
319
328
question_string.append (tr (" Are you sure you want to send?" ));
320
329
}
321
330
322
331
question_string.append (" <br /><span style='font-size:10pt;'>" );
323
- if (model->wallet ().privateKeysDisabled ()) {
332
+ if (model->wallet ().privateKeysDisabled () && !model-> wallet (). hasExternalSigner () ) {
324
333
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));
325
334
} else {
326
335
question_string.append (tr (" Please, review your transaction." ));
@@ -386,8 +395,8 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
386
395
if (!PrepareSendText (question_string, informative_text, detailed_text)) return ;
387
396
assert (m_current_transaction);
388
397
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 " );
391
400
SendConfirmationDialog confirmationDialog (confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, confirmButtonText, this );
392
401
confirmationDialog.exec ();
393
402
QMessageBox::StandardButton retval = static_cast <QMessageBox::StandardButton>(confirmationDialog.result ());
@@ -403,9 +412,58 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
403
412
CMutableTransaction mtx = CMutableTransaction{*(m_current_transaction->getWtx ())};
404
413
PartiallySignedTransaction psbtx (mtx);
405
414
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 );
407
418
assert (!complete);
408
419
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);
409
467
// Serialize the PSBT
410
468
CDataStream ssTx (SER_NETWORK, PROTOCOL_VERSION);
411
469
ssTx << psbtx;
@@ -447,7 +505,7 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
447
505
break ;
448
506
default :
449
507
assert (false );
450
- }
508
+ } // msgBox.exec()
451
509
} else {
452
510
// now send the prepared transaction
453
511
WalletModel::SendCoinsReturn sendStatus = model->sendCoins (*m_current_transaction);
@@ -614,7 +672,9 @@ void SendCoinsDialog::setBalance(const interfaces::WalletBalances& balances)
614
672
if (model && model->getOptionsModel ())
615
673
{
616
674
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 ()) {
618
678
balance = balances.watch_only_balance ;
619
679
ui->labelBalanceName ->setText (tr (" Watch-only balance:" ));
620
680
}
@@ -698,7 +758,7 @@ void SendCoinsDialog::on_buttonMinimizeFee_clicked()
698
758
void SendCoinsDialog::useAvailableBalance (SendCoinsEntry* entry)
699
759
{
700
760
// 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 () ;
702
762
703
763
// Calculate available amount to send.
704
764
CAmount amount = model->wallet ().getAvailableBalance (*m_coin_control);
@@ -753,7 +813,7 @@ void SendCoinsDialog::updateCoinControlState()
753
813
m_coin_control->m_confirm_target = getConfTargetForIndex (ui->confTargetSelector ->currentIndex ());
754
814
m_coin_control->m_signal_bip125_rbf = ui->optInRBF ->isChecked ();
755
815
// 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 () ;
757
817
}
758
818
759
819
void SendCoinsDialog::updateNumberOfBlocks (int count, const QDateTime& blockDate, double nVerificationProgress, bool headers, SynchronizationState sync_state) {
0 commit comments