@@ -188,7 +188,16 @@ void SendCoinsDialog::setModel(WalletModel *_model)
188
188
// set default rbf checkbox state
189
189
ui->optInRBF ->setCheckState (Qt::Checked);
190
190
191
- if (model->wallet ().privateKeysDisabled ()) {
191
+ if (model->wallet ().hasExternalSigner ()) {
192
+ ui->sendButton ->setText (tr (" Sign on device" ));
193
+ if (gArgs .GetArg (" -signer" , " " ) != " " ) {
194
+ ui->sendButton ->setEnabled (true );
195
+ ui->sendButton ->setToolTip (tr (" Connect your hardware wallet first." ));
196
+ } else {
197
+ ui->sendButton ->setEnabled (false );
198
+ ui->sendButton ->setToolTip (tr (" Set external signer script path in Options -> Wallet" ));
199
+ }
200
+ } else if (model->wallet ().privateKeysDisabled ()) {
192
201
ui->sendButton ->setText (tr (" Cr&eate Unsigned" ));
193
202
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));
194
203
}
@@ -302,14 +311,14 @@ bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informa
302
311
formatted.append (recipientElement);
303
312
}
304
313
305
- if (model->wallet ().privateKeysDisabled ()) {
314
+ if (model->wallet ().privateKeysDisabled () && !model-> wallet (). hasExternalSigner () ) {
306
315
question_string.append (tr (" Do you want to draft this transaction?" ));
307
316
} else {
308
317
question_string.append (tr (" Are you sure you want to send?" ));
309
318
}
310
319
311
320
question_string.append (" <br /><span style='font-size:10pt;'>" );
312
- if (model->wallet ().privateKeysDisabled ()) {
321
+ if (model->wallet ().privateKeysDisabled () && !model-> wallet (). hasExternalSigner () ) {
313
322
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));
314
323
} else {
315
324
question_string.append (tr (" Please, review your transaction." ));
@@ -375,8 +384,8 @@ void SendCoinsDialog::on_sendButton_clicked()
375
384
if (!PrepareSendText (question_string, informative_text, detailed_text)) return ;
376
385
assert (m_current_transaction);
377
386
378
- const QString confirmation = model->wallet ().privateKeysDisabled () ? tr (" Confirm transaction proposal" ) : tr (" Confirm send coins" );
379
- const QString confirmButtonText = model->wallet ().privateKeysDisabled () ? tr (" Create Unsigned" ) : tr (" Send" );
387
+ const QString confirmation = model->wallet ().privateKeysDisabled () && !model-> wallet (). hasExternalSigner () ? tr (" Confirm transaction proposal" ) : tr (" Confirm send coins" );
388
+ const QString confirmButtonText = model->wallet ().privateKeysDisabled () && !model-> wallet (). hasExternalSigner () ? tr (" Create Unsigned" ) : tr (" Send" );
380
389
SendConfirmationDialog confirmationDialog (confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, confirmButtonText, this );
381
390
confirmationDialog.exec ();
382
391
QMessageBox::StandardButton retval = static_cast <QMessageBox::StandardButton>(confirmationDialog.result ());
@@ -392,49 +401,77 @@ void SendCoinsDialog::on_sendButton_clicked()
392
401
CMutableTransaction mtx = CMutableTransaction{*(m_current_transaction->getWtx ())};
393
402
PartiallySignedTransaction psbtx (mtx);
394
403
bool complete = false ;
395
- const TransactionError err = model->wallet ().fillPSBT (SIGHASH_ALL, false /* sign */ , true /* bip32derivs */ , psbtx, complete, nullptr );
404
+ // Always fill without signing first, to prevents an external signer
405
+ // from being called prematurely. This is not expensive.
406
+ TransactionError err = model->wallet ().fillPSBT (SIGHASH_ALL, false /* sign */ , true /* bip32derivs */ , psbtx, complete, nullptr );
396
407
assert (!complete);
397
- assert (err == TransactionError::OK);
398
- // Serialize the PSBT
399
- CDataStream ssTx (SER_NETWORK, PROTOCOL_VERSION);
400
- ssTx << psbtx;
401
- GUIUtil::setClipboard (EncodeBase64 (ssTx.str ()).c_str ());
402
- QMessageBox msgBox;
403
- msgBox.setText (" Unsigned Transaction" );
404
- msgBox.setInformativeText (" The PSBT has been copied to the clipboard. You can also save it." );
405
- msgBox.setStandardButtons (QMessageBox::Save | QMessageBox::Discard);
406
- msgBox.setDefaultButton (QMessageBox::Discard);
407
- switch (msgBox.exec ()) {
408
- case QMessageBox::Save: {
409
- QString selectedFilter;
410
- QString fileNameSuggestion = " " ;
411
- bool first = true ;
412
- for (const SendCoinsRecipient &rcp : m_current_transaction->getRecipients ()) {
413
- if (!first) {
414
- fileNameSuggestion.append (" - " );
415
- }
416
- QString labelOrAddress = rcp.label .isEmpty () ? rcp.address : rcp.label ;
417
- QString amount = BitcoinUnits::formatWithUnit (model->getOptionsModel ()->getDisplayUnit (), rcp.amount );
418
- fileNameSuggestion.append (labelOrAddress + " -" + amount);
419
- first = false ;
420
- }
421
- fileNameSuggestion.append (" .psbt" );
422
- QString filename = GUIUtil::getSaveFileName (this ,
423
- tr (" Save Transaction Data" ), fileNameSuggestion,
424
- tr (" Partially Signed Transaction (Binary) (*.psbt)" ), &selectedFilter);
425
- if (filename.isEmpty ()) {
408
+ if (model->wallet ().hasExternalSigner ()) {
409
+ try {
410
+ err = model->wallet ().fillPSBT (SIGHASH_ALL, true /* sign */ , true /* bip32derivs */ , psbtx, complete, nullptr );
411
+ } catch (const ExternalSignerException& e) {
412
+ QMessageBox::critical (nullptr , tr (" Sign failed" ), e.what ());
413
+ send_failure = true ;
426
414
return ;
427
415
}
428
- std::ofstream out (filename.toLocal8Bit ().data ());
429
- out << ssTx.str ();
430
- out.close ();
431
- Q_EMIT message (tr (" PSBT saved" ), " PSBT saved to disk" , CClientUIInterface::MSG_INFORMATION);
432
- break ;
433
416
}
434
- case QMessageBox::Discard:
435
- break ;
436
- default :
437
- assert (false );
417
+ // fillPSBT does not always properly finalize
418
+ complete = FinalizeAndExtractPSBT (psbtx, mtx);
419
+ if (complete) {
420
+ std::string err_string;
421
+ TransactionError result = BroadcastTransaction (*clientModel->node ().context (), MakeTransactionRef (mtx), err_string, COIN / 10 , /* relay */ true , /* await_callback */ false );
422
+
423
+ if (result == TransactionError::OK) {
424
+ Q_EMIT coinsSent (mtx.GetHash ());
425
+ } else {
426
+ processSendCoinsReturn (WalletModel::TransactionCreationFailed);
427
+ send_failure = true ;
428
+ }
429
+ } else if (err == TransactionError::OK) {
430
+ // Serialize the PSBT
431
+ CDataStream ssTx (SER_NETWORK, PROTOCOL_VERSION);
432
+ ssTx << psbtx;
433
+ GUIUtil::setClipboard (EncodeBase64 (ssTx.str ()).c_str ());
434
+ QMessageBox msgBox;
435
+ msgBox.setText (" Unsigned Transaction" );
436
+ msgBox.setInformativeText (" The PSBT has been copied to the clipboard. You can also save it." );
437
+ msgBox.setStandardButtons (QMessageBox::Save | QMessageBox::Discard);
438
+ msgBox.setDefaultButton (QMessageBox::Discard);
439
+ switch (msgBox.exec ()) {
440
+ case QMessageBox::Save: {
441
+ QString selectedFilter;
442
+ QString fileNameSuggestion = " " ;
443
+ bool first = true ;
444
+ for (const SendCoinsRecipient &rcp : m_current_transaction->getRecipients ()) {
445
+ if (!first) {
446
+ fileNameSuggestion.append (" - " );
447
+ }
448
+ QString labelOrAddress = rcp.label .isEmpty () ? rcp.address : rcp.label ;
449
+ QString amount = BitcoinUnits::formatWithUnit (model->getOptionsModel ()->getDisplayUnit (), rcp.amount );
450
+ fileNameSuggestion.append (labelOrAddress + " -" + amount);
451
+ first = false ;
452
+ }
453
+ fileNameSuggestion.append (" .psbt" );
454
+ QString filename = GUIUtil::getSaveFileName (this ,
455
+ tr (" Save Transaction Data" ), fileNameSuggestion,
456
+ tr (" Partially Signed Transaction (Binary) (*.psbt)" ), &selectedFilter);
457
+ if (filename.isEmpty ()) {
458
+ return ;
459
+ }
460
+ std::ofstream out (filename.toLocal8Bit ().data ());
461
+ out << ssTx.str ();
462
+ out.close ();
463
+ Q_EMIT message (tr (" PSBT saved" ), " PSBT saved to disk" , CClientUIInterface::MSG_INFORMATION);
464
+ break ;
465
+ }
466
+ case QMessageBox::Discard:
467
+ break ;
468
+ default :
469
+ assert (false );
470
+ }
471
+ } else {
472
+ // TODO: process error
473
+ processSendCoinsReturn (WalletModel::TransactionCreationFailed);
474
+ send_failure = true ;
438
475
}
439
476
} else {
440
477
// now send the prepared transaction
@@ -602,7 +639,9 @@ void SendCoinsDialog::setBalance(const interfaces::WalletBalances& balances)
602
639
if (model && model->getOptionsModel ())
603
640
{
604
641
CAmount balance = balances.balance ;
605
- if (model->wallet ().privateKeysDisabled ()) {
642
+ if (model->wallet ().hasExternalSigner ()) {
643
+ ui->labelBalanceName ->setText (tr (" External balance:" ));
644
+ } else if (model->wallet ().privateKeysDisabled ()) {
606
645
balance = balances.watch_only_balance ;
607
646
ui->labelBalanceName ->setText (tr (" Watch-only balance:" ));
608
647
}
@@ -686,7 +725,7 @@ void SendCoinsDialog::on_buttonMinimizeFee_clicked()
686
725
void SendCoinsDialog::useAvailableBalance (SendCoinsEntry* entry)
687
726
{
688
727
// Include watch-only for wallets without private key
689
- m_coin_control->fAllowWatchOnly = model->wallet ().privateKeysDisabled ();
728
+ m_coin_control->fAllowWatchOnly = model->wallet ().privateKeysDisabled () && !model-> wallet (). hasExternalSigner () ;
690
729
691
730
// Calculate available amount to send.
692
731
CAmount amount = model->wallet ().getAvailableBalance (*m_coin_control);
@@ -741,7 +780,7 @@ void SendCoinsDialog::updateCoinControlState(CCoinControl& ctrl)
741
780
ctrl.m_confirm_target = getConfTargetForIndex (ui->confTargetSelector ->currentIndex ());
742
781
ctrl.m_signal_bip125_rbf = ui->optInRBF ->isChecked ();
743
782
// Include watch-only for wallets without private key
744
- ctrl.fAllowWatchOnly = model->wallet ().privateKeysDisabled ();
783
+ ctrl.fAllowWatchOnly = model->wallet ().privateKeysDisabled () && !model-> wallet (). hasExternalSigner () ;
745
784
}
746
785
747
786
void SendCoinsDialog::updateSmartFeeLabel ()
0 commit comments