@ -401,6 +401,80 @@ bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informa
return true ;
}
void SendCoinsDialog : : presentPSBT ( PartiallySignedTransaction & psbtx )
{
// Serialize the PSBT
CDataStream ssTx ( SER_NETWORK , PROTOCOL_VERSION ) ;
ssTx < < psbtx ;
GUIUtil : : setClipboard ( EncodeBase64 ( ssTx . str ( ) ) . c_str ( ) ) ;
QMessageBox msgBox ;
msgBox . setText ( " Unsigned Transaction " ) ;
msgBox . setInformativeText ( " The PSBT has been copied to the clipboard. You can also save it. " ) ;
msgBox . setStandardButtons ( QMessageBox : : Save | QMessageBox : : Discard ) ;
msgBox . setDefaultButton ( QMessageBox : : Discard ) ;
switch ( msgBox . exec ( ) ) {
case QMessageBox : : Save : {
QString selectedFilter ;
QString fileNameSuggestion = " " ;
bool first = true ;
for ( const SendCoinsRecipient & rcp : m_current_transaction - > getRecipients ( ) ) {
if ( ! first ) {
fileNameSuggestion . append ( " - " ) ;
}
QString labelOrAddress = rcp . label . isEmpty ( ) ? rcp . address : rcp . label ;
QString amount = BitcoinUnits : : formatWithUnit ( model - > getOptionsModel ( ) - > getDisplayUnit ( ) , rcp . amount ) ;
fileNameSuggestion . append ( labelOrAddress + " - " + amount ) ;
first = false ;
}
fileNameSuggestion . append ( " .psbt " ) ;
QString filename = GUIUtil : : getSaveFileName ( this ,
tr ( " Save Transaction Data " ) , fileNameSuggestion ,
//: Expanded name of the binary PSBT file format. See: BIP 174.
tr ( " Partially Signed Transaction (Binary) " ) + QLatin1String ( " (*.psbt) " ) , & selectedFilter ) ;
if ( filename . isEmpty ( ) ) {
return ;
}
std : : ofstream out { filename . toLocal8Bit ( ) . data ( ) , std : : ofstream : : out | std : : ofstream : : binary } ;
out < < ssTx . str ( ) ;
out . close ( ) ;
Q_EMIT message ( tr ( " PSBT saved " ) , " PSBT saved to disk " , CClientUIInterface : : MSG_INFORMATION ) ;
break ;
}
case QMessageBox : : Discard :
break ;
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 ( ) )
@ -411,7 +485,9 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
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 ) ;
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 ( ) ) ;
@ -424,49 +500,50 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
bool send_failure = false ;
if ( retval = = QMessageBox : : Save ) {
// "Create Unsigned" clicked
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 ) ;
// 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 {
// "Send" clicked
assert ( ! model - > wallet ( ) . privateKeysDisabled ( ) | | model - > wallet ( ) . hasExternalSigner ( ) ) ;
bool broadcast = true ;
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 ;
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 ) ;
}
}
// 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 ) ;
// 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
processSendCoinsReturn ( sendStatus ) ;
@ -476,64 +553,6 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
} 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 ;
GUIUtil : : setClipboard ( EncodeBase64 ( ssTx . str ( ) ) . c_str ( ) ) ;
QMessageBox msgBox ;
msgBox . setText ( " Unsigned Transaction " ) ;
msgBox . setInformativeText ( " The PSBT has been copied to the clipboard. You can also save it. " ) ;
msgBox . setStandardButtons ( QMessageBox : : Save | QMessageBox : : Discard ) ;
msgBox . setDefaultButton ( QMessageBox : : Discard ) ;
switch ( msgBox . exec ( ) ) {
case QMessageBox : : Save : {
QString selectedFilter ;
QString fileNameSuggestion = " " ;
bool first = true ;
for ( const SendCoinsRecipient & rcp : m_current_transaction - > getRecipients ( ) ) {
if ( ! first ) {
fileNameSuggestion . append ( " - " ) ;
}
QString labelOrAddress = rcp . label . isEmpty ( ) ? rcp . address : rcp . label ;
QString amount = BitcoinUnits : : formatWithUnit ( model - > getOptionsModel ( ) - > getDisplayUnit ( ) , rcp . amount ) ;
fileNameSuggestion . append ( labelOrAddress + " - " + amount ) ;
first = false ;
}
fileNameSuggestion . append ( " .psbt " ) ;
QString filename = GUIUtil : : getSaveFileName ( this ,
tr ( " Save Transaction Data " ) , fileNameSuggestion ,
//: Expanded name of the binary PSBT file format. See: BIP 174.
tr ( " Partially Signed Transaction (Binary) " ) + QLatin1String ( " (*.psbt) " ) , & selectedFilter ) ;
if ( filename . isEmpty ( ) ) {
return ;
}
std : : ofstream out { filename . toLocal8Bit ( ) . data ( ) , std : : ofstream : : out | std : : ofstream : : binary } ;
out < < ssTx . str ( ) ;
out . close ( ) ;
Q_EMIT message ( tr ( " PSBT saved " ) , " PSBT saved to disk " , CClientUIInterface : : MSG_INFORMATION ) ;
break ;
}
case QMessageBox : : Discard :
break ;
default :
assert ( false ) ;
} // msgBox.exec()
} else {
assert ( ! model - > wallet ( ) . privateKeysDisabled ( ) ) ;
// now send the prepared transaction
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 ;
}
}
if ( ! send_failure ) {