@@ -401,6 +401,80 @@ bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informa
401
401
return true ;
402
402
}
403
403
404
+ void SendCoinsDialog::presentPSBT (PartiallySignedTransaction& psbtx)
405
+ {
406
+ // Serialize the PSBT
407
+ CDataStream ssTx (SER_NETWORK, PROTOCOL_VERSION);
408
+ ssTx << psbtx;
409
+ GUIUtil::setClipboard (EncodeBase64 (ssTx.str ()).c_str ());
410
+ QMessageBox msgBox;
411
+ msgBox.setText (" Unsigned Transaction" );
412
+ msgBox.setInformativeText (" The PSBT has been copied to the clipboard. You can also save it." );
413
+ msgBox.setStandardButtons (QMessageBox::Save | QMessageBox::Discard);
414
+ msgBox.setDefaultButton (QMessageBox::Discard);
415
+ switch (msgBox.exec ()) {
416
+ case QMessageBox::Save: {
417
+ QString selectedFilter;
418
+ QString fileNameSuggestion = " " ;
419
+ bool first = true ;
420
+ for (const SendCoinsRecipient &rcp : m_current_transaction->getRecipients ()) {
421
+ if (!first) {
422
+ fileNameSuggestion.append (" - " );
423
+ }
424
+ QString labelOrAddress = rcp.label .isEmpty () ? rcp.address : rcp.label ;
425
+ QString amount = BitcoinUnits::formatWithUnit (model->getOptionsModel ()->getDisplayUnit (), rcp.amount );
426
+ fileNameSuggestion.append (labelOrAddress + " -" + amount);
427
+ first = false ;
428
+ }
429
+ fileNameSuggestion.append (" .psbt" );
430
+ QString filename = GUIUtil::getSaveFileName (this ,
431
+ tr (" Save Transaction Data" ), fileNameSuggestion,
432
+ // : Expanded name of the binary PSBT file format. See: BIP 174.
433
+ tr (" Partially Signed Transaction (Binary)" ) + QLatin1String (" (*.psbt)" ), &selectedFilter);
434
+ if (filename.isEmpty ()) {
435
+ return ;
436
+ }
437
+ std::ofstream out{filename.toLocal8Bit ().data (), std::ofstream::out | std::ofstream::binary};
438
+ out << ssTx.str ();
439
+ out.close ();
440
+ Q_EMIT message (tr (" PSBT saved" ), " PSBT saved to disk" , CClientUIInterface::MSG_INFORMATION);
441
+ break ;
442
+ }
443
+ case QMessageBox::Discard:
444
+ break ;
445
+ default :
446
+ assert (false );
447
+ } // msgBox.exec()
448
+ }
449
+
450
+ bool SendCoinsDialog::signWithExternalSigner (PartiallySignedTransaction& psbtx, CMutableTransaction& mtx, bool & complete) {
451
+ TransactionError err;
452
+ try {
453
+ err = model->wallet ().fillPSBT (SIGHASH_ALL, /* sign=*/ true , /* bip32derivs=*/ true , /* n_signed=*/ nullptr , psbtx, complete);
454
+ } catch (const std::runtime_error& e) {
455
+ QMessageBox::critical (nullptr , tr (" Sign failed" ), e.what ());
456
+ return false ;
457
+ }
458
+ if (err == TransactionError::EXTERNAL_SIGNER_NOT_FOUND) {
459
+ // : "External signer" means using devices such as hardware wallets.
460
+ QMessageBox::critical (nullptr , tr (" External signer not found" ), " External signer not found" );
461
+ return false ;
462
+ }
463
+ if (err == TransactionError::EXTERNAL_SIGNER_FAILED) {
464
+ // : "External signer" means using devices such as hardware wallets.
465
+ QMessageBox::critical (nullptr , tr (" External signer failure" ), " External signer failure" );
466
+ return false ;
467
+ }
468
+ if (err != TransactionError::OK) {
469
+ tfm::format (std::cerr, " Failed to sign PSBT" );
470
+ processSendCoinsReturn (WalletModel::TransactionCreationFailed);
471
+ return false ;
472
+ }
473
+ // fillPSBT does not always properly finalize
474
+ complete = FinalizeAndExtractPSBT (psbtx, mtx);
475
+ return true ;
476
+ }
477
+
404
478
void SendCoinsDialog::sendButtonClicked ([[maybe_unused]] bool checked)
405
479
{
406
480
if (!model || !model->getOptionsModel ())
@@ -411,7 +485,9 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
411
485
assert (m_current_transaction);
412
486
413
487
const QString confirmation = tr (" Confirm send coins" );
414
- auto confirmationDialog = new SendConfirmationDialog (confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, !model->wallet ().privateKeysDisabled (), model->getOptionsModel ()->getEnablePSBTControls (), this );
488
+ const bool enable_send{!model->wallet ().privateKeysDisabled () || model->wallet ().hasExternalSigner ()};
489
+ const bool always_show_unsigned{model->getOptionsModel ()->getEnablePSBTControls ()};
490
+ auto confirmationDialog = new SendConfirmationDialog (confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, enable_send, always_show_unsigned, this );
415
491
confirmationDialog->setAttribute (Qt::WA_DeleteOnClose);
416
492
// TODO: Replace QDialog::exec() with safer QDialog::show().
417
493
const auto retval = static_cast <QMessageBox::StandardButton>(confirmationDialog->exec ());
@@ -424,49 +500,50 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
424
500
425
501
bool send_failure = false ;
426
502
if (retval == QMessageBox::Save) {
503
+ // "Create Unsigned" clicked
427
504
CMutableTransaction mtx = CMutableTransaction{*(m_current_transaction->getWtx ())};
428
505
PartiallySignedTransaction psbtx (mtx);
429
506
bool complete = false ;
430
- // Always fill without signing first. This prevents an external signer
431
- // from being called prematurely and is not expensive.
432
- TransactionError err = model->wallet ().fillPSBT (SIGHASH_ALL, false /* sign */ , true /* bip32derivs */ , nullptr , psbtx, complete);
507
+ // Fill without signing
508
+ TransactionError err = model->wallet ().fillPSBT (SIGHASH_ALL, /* sign=*/ false , /* bip32derivs=*/ true , /* n_signed=*/ nullptr , psbtx, complete);
433
509
assert (!complete);
434
510
assert (err == TransactionError::OK);
511
+
512
+ // Copy PSBT to clipboard and offer to save
513
+ presentPSBT (psbtx);
514
+ } else {
515
+ // "Send" clicked
516
+ assert (!model->wallet ().privateKeysDisabled () || model->wallet ().hasExternalSigner ());
517
+ bool broadcast = true ;
435
518
if (model->wallet ().hasExternalSigner ()) {
436
- try {
437
- err = model->wallet ().fillPSBT (SIGHASH_ALL, true /* sign */ , true /* bip32derivs */ , nullptr , psbtx, complete);
438
- } catch (const std::runtime_error& e) {
439
- QMessageBox::critical (nullptr , tr (" Sign failed" ), e.what ());
440
- send_failure = true ;
441
- return ;
442
- }
443
- if (err == TransactionError::EXTERNAL_SIGNER_NOT_FOUND) {
444
- // : "External signer" means using devices such as hardware wallets.
445
- QMessageBox::critical (nullptr , tr (" External signer not found" ), " External signer not found" );
446
- send_failure = true ;
447
- return ;
448
- }
449
- if (err == TransactionError::EXTERNAL_SIGNER_FAILED) {
450
- // : "External signer" means using devices such as hardware wallets.
451
- QMessageBox::critical (nullptr , tr (" External signer failure" ), " External signer failure" );
452
- send_failure = true ;
453
- return ;
454
- }
455
- if (err != TransactionError::OK) {
456
- tfm::format (std::cerr, " Failed to sign PSBT" );
457
- processSendCoinsReturn (WalletModel::TransactionCreationFailed);
458
- send_failure = true ;
459
- return ;
519
+ CMutableTransaction mtx = CMutableTransaction{*(m_current_transaction->getWtx ())};
520
+ PartiallySignedTransaction psbtx (mtx);
521
+ bool complete = false ;
522
+ // Always fill without signing first. This prevents an external signer
523
+ // from being called prematurely and is not expensive.
524
+ TransactionError err = model->wallet ().fillPSBT (SIGHASH_ALL, /* sign=*/ false , /* bip32derivs=*/ true , /* n_signed=*/ nullptr , psbtx, complete);
525
+ assert (!complete);
526
+ assert (err == TransactionError::OK);
527
+ send_failure = !signWithExternalSigner (psbtx, mtx, complete);
528
+ // Don't broadcast when user rejects it on the device or there's a failure:
529
+ broadcast = complete && !send_failure;
530
+ if (!send_failure) {
531
+ // A transaction signed with an external signer is not always complete,
532
+ // e.g. in a multisig wallet.
533
+ if (complete) {
534
+ // Prepare transaction for broadcast transaction if complete
535
+ const CTransactionRef tx = MakeTransactionRef (mtx);
536
+ m_current_transaction->setWtx (tx);
537
+ } else {
538
+ presentPSBT (psbtx);
539
+ }
460
540
}
461
- // fillPSBT does not always properly finalize
462
- complete = FinalizeAndExtractPSBT (psbtx, mtx);
463
541
}
464
542
465
- // Broadcast transaction if complete (even with an external signer this
466
- // is not always the case, e.g. in a multisig wallet).
467
- if (complete) {
468
- const CTransactionRef tx = MakeTransactionRef (mtx);
469
- m_current_transaction->setWtx (tx);
543
+ // Broadcast the transaction, unless an external signer was used and it
544
+ // failed, or more signatures are needed.
545
+ if (broadcast) {
546
+ // now send the prepared transaction
470
547
WalletModel::SendCoinsReturn sendStatus = model->sendCoins (*m_current_transaction);
471
548
// process sendStatus and on error generate message shown to user
472
549
processSendCoinsReturn (sendStatus);
@@ -476,64 +553,6 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
476
553
} else {
477
554
send_failure = true ;
478
555
}
479
- return ;
480
- }
481
-
482
- // Copy PSBT to clipboard and offer to save
483
- assert (!complete);
484
- // Serialize the PSBT
485
- CDataStream ssTx (SER_NETWORK, PROTOCOL_VERSION);
486
- ssTx << psbtx;
487
- GUIUtil::setClipboard (EncodeBase64 (ssTx.str ()).c_str ());
488
- QMessageBox msgBox;
489
- msgBox.setText (" Unsigned Transaction" );
490
- msgBox.setInformativeText (" The PSBT has been copied to the clipboard. You can also save it." );
491
- msgBox.setStandardButtons (QMessageBox::Save | QMessageBox::Discard);
492
- msgBox.setDefaultButton (QMessageBox::Discard);
493
- switch (msgBox.exec ()) {
494
- case QMessageBox::Save: {
495
- QString selectedFilter;
496
- QString fileNameSuggestion = " " ;
497
- bool first = true ;
498
- for (const SendCoinsRecipient &rcp : m_current_transaction->getRecipients ()) {
499
- if (!first) {
500
- fileNameSuggestion.append (" - " );
501
- }
502
- QString labelOrAddress = rcp.label .isEmpty () ? rcp.address : rcp.label ;
503
- QString amount = BitcoinUnits::formatWithUnit (model->getOptionsModel ()->getDisplayUnit (), rcp.amount );
504
- fileNameSuggestion.append (labelOrAddress + " -" + amount);
505
- first = false ;
506
- }
507
- fileNameSuggestion.append (" .psbt" );
508
- QString filename = GUIUtil::getSaveFileName (this ,
509
- tr (" Save Transaction Data" ), fileNameSuggestion,
510
- // : Expanded name of the binary PSBT file format. See: BIP 174.
511
- tr (" Partially Signed Transaction (Binary)" ) + QLatin1String (" (*.psbt)" ), &selectedFilter);
512
- if (filename.isEmpty ()) {
513
- return ;
514
- }
515
- std::ofstream out{filename.toLocal8Bit ().data (), std::ofstream::out | std::ofstream::binary};
516
- out << ssTx.str ();
517
- out.close ();
518
- Q_EMIT message (tr (" PSBT saved" ), " PSBT saved to disk" , CClientUIInterface::MSG_INFORMATION);
519
- break ;
520
- }
521
- case QMessageBox::Discard:
522
- break ;
523
- default :
524
- assert (false );
525
- } // msgBox.exec()
526
- } else {
527
- assert (!model->wallet ().privateKeysDisabled ());
528
- // now send the prepared transaction
529
- WalletModel::SendCoinsReturn sendStatus = model->sendCoins (*m_current_transaction);
530
- // process sendStatus and on error generate message shown to user
531
- processSendCoinsReturn (sendStatus);
532
-
533
- if (sendStatus.status == WalletModel::OK) {
534
- Q_EMIT coinsSent (m_current_transaction->getWtx ()->GetHash ());
535
- } else {
536
- send_failure = true ;
537
556
}
538
557
}
539
558
if (!send_failure) {
0 commit comments