@ -401,86 +401,8 @@ bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informa
return true ;
}
void SendCoinsDialog : : sendButtonClicked( [[maybe_unused]] bool checked )
void SendCoinsDialog : : presentPSBT( PartiallySignedTransaction & psbtx )
{
if ( ! model | | ! model - > getOptionsModel ( ) )
return ;
QString question_string , informative_text , detailed_text ;
if ( ! PrepareSendText ( question_string , informative_text , detailed_text ) ) return ;
assert ( m_current_transaction ) ;
const QString confirmation = tr ( " Confirm send coins " ) ;
auto confirmationDialog = new SendConfirmationDialog ( confirmation , question_string , informative_text , detailed_text , SEND_CONFIRM_DELAY , ! model - > wallet ( ) . privateKeysDisabled ( ) , model - > getOptionsModel ( ) - > getEnablePSBTControls ( ) , this ) ;
confirmationDialog - > setAttribute ( Qt : : WA_DeleteOnClose ) ;
// TODO: Replace QDialog::exec() with safer QDialog::show().
const auto retval = static_cast < QMessageBox : : StandardButton > ( confirmationDialog - > exec ( ) ) ;
if ( retval ! = QMessageBox : : Yes & & retval ! = QMessageBox : : Save )
{
fNewRecipientAllowed = true ;
return ;
}
bool send_failure = false ;
if ( retval = = QMessageBox : : Save ) {
CMutableTransaction mtx = CMutableTransaction { * ( m_current_transaction - > getWtx ( ) ) } ;
PartiallySignedTransaction psbtx ( mtx ) ;
bool complete = false ;
// 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 ;
@ -523,8 +445,104 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
default :
assert ( false ) ;
} // msgBox.exec()
}
bool SendCoinsDialog : : signWithExternalSigner ( PartiallySignedTransaction & psbtx , CMutableTransaction & mtx , bool & complete ) {
TransactionError err ;
try {
err = model - > wallet ( ) . fillPSBT ( SIGHASH_ALL , /*sign=*/ true , /*bip32derivs=*/ true , /*n_signed=*/ nullptr , psbtx , complete ) ;
} catch ( const std : : runtime_error & e ) {
QMessageBox : : critical ( nullptr , tr ( " Sign failed " ) , e . what ( ) ) ;
return false ;
}
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 " ) ;
return false ;
}
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 " ) ;
return false ;
}
if ( err ! = TransactionError : : OK ) {
tfm : : format ( std : : cerr , " Failed to sign PSBT " ) ;
processSendCoinsReturn ( WalletModel : : TransactionCreationFailed ) ;
return false ;
}
// fillPSBT does not always properly finalize
complete = FinalizeAndExtractPSBT ( psbtx , mtx ) ;
return true ;
}
void SendCoinsDialog : : sendButtonClicked ( [[maybe_unused]] bool checked )
{
if ( ! model | | ! model - > getOptionsModel ( ) )
return ;
QString question_string , informative_text , detailed_text ;
if ( ! PrepareSendText ( question_string , informative_text , detailed_text ) ) return ;
assert ( m_current_transaction ) ;
const QString confirmation = tr ( " Confirm send coins " ) ;
const bool enable_send { ! model - > wallet ( ) . privateKeysDisabled ( ) | | model - > wallet ( ) . hasExternalSigner ( ) } ;
const bool always_show_unsigned { model - > getOptionsModel ( ) - > getEnablePSBTControls ( ) } ;
auto confirmationDialog = new SendConfirmationDialog ( confirmation , question_string , informative_text , detailed_text , SEND_CONFIRM_DELAY , enable_send , always_show_unsigned , this ) ;
confirmationDialog - > setAttribute ( Qt : : WA_DeleteOnClose ) ;
// TODO: Replace QDialog::exec() with safer QDialog::show().
const auto retval = static_cast < QMessageBox : : StandardButton > ( confirmationDialog - > exec ( ) ) ;
if ( retval ! = QMessageBox : : Yes & & retval ! = QMessageBox : : Save )
{
fNewRecipientAllowed = true ;
return ;
}
bool send_failure = false ;
if ( retval = = QMessageBox : : Save ) {
// "Create Unsigned" clicked
CMutableTransaction mtx = CMutableTransaction { * ( m_current_transaction - > getWtx ( ) ) } ;
PartiallySignedTransaction psbtx ( mtx ) ;
bool complete = false ;
// Fill without signing
TransactionError err = model - > wallet ( ) . fillPSBT ( SIGHASH_ALL , /*sign=*/ false , /*bip32derivs=*/ true , /*n_signed=*/ nullptr , psbtx , complete ) ;
assert ( ! complete ) ;
assert ( err = = TransactionError : : OK ) ;
// Copy PSBT to clipboard and offer to save
presentPSBT ( psbtx ) ;
} else {
assert ( ! model - > wallet ( ) . privateKeysDisabled ( ) ) ;
// "Send" clicked
assert ( ! model - > wallet ( ) . privateKeysDisabled ( ) | | model - > wallet ( ) . hasExternalSigner ( ) ) ;
bool broadcast = true ;
if ( model - > wallet ( ) . hasExternalSigner ( ) ) {
CMutableTransaction mtx = CMutableTransaction { * ( m_current_transaction - > getWtx ( ) ) } ;
PartiallySignedTransaction psbtx ( mtx ) ;
bool complete = false ;
// 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 , /*sign=*/ false , /*bip32derivs=*/ true , /*n_signed=*/ nullptr , psbtx , complete ) ;
assert ( ! complete ) ;
assert ( err = = TransactionError : : OK ) ;
send_failure = ! signWithExternalSigner ( psbtx , mtx , complete ) ;
// Don't broadcast when user rejects it on the device or there's a failure:
broadcast = complete & & ! send_failure ;
if ( ! send_failure ) {
// A transaction signed with an external signer is not always complete,
// e.g. in a multisig wallet.
if ( complete ) {
// Prepare transaction for broadcast transaction if complete
const CTransactionRef tx = MakeTransactionRef ( mtx ) ;
m_current_transaction - > setWtx ( tx ) ;
} else {
presentPSBT ( psbtx ) ;
}
}
}
// Broadcast the transaction, unless an external signer was used and it
// failed, or more signatures are needed.
if ( broadcast ) {
// now send the prepared transaction
WalletModel : : SendCoinsReturn sendStatus = model - > sendCoins ( * m_current_transaction ) ;
// process sendStatus and on error generate message shown to user
@ -536,6 +554,7 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
send_failure = true ;
}
}
}
if ( ! send_failure ) {
accept ( ) ;
m_coin_control - > UnSelectAll ( ) ;