diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -140,6 +140,7 @@ QAction *usedReceivingAddressesAction = nullptr; QAction *signMessageAction = nullptr; QAction *verifyMessageAction = nullptr; + QAction *m_load_psbt_action = nullptr; QAction *aboutAction = nullptr; QAction *receiveCoinsAction = nullptr; QAction *receiveCoinsMenuAction = nullptr; @@ -282,6 +283,8 @@ void gotoSignMessageTab(QString addr = ""); /** Show Sign/Verify Message dialog and switch to verify message tab */ void gotoVerifyMessageTab(QString addr = ""); + /** Show load Partially Signed Bitcoin Transaction dialog */ + void gotoLoadPSBT(); /** Show open dialog */ void openClicked(); diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -364,6 +364,9 @@ verifyMessageAction->setStatusTip( tr("Verify messages to ensure they were signed with specified Bitcoin " "addresses")); + m_load_psbt_action = new QAction(tr("Load PSBT..."), this); + m_load_psbt_action->setStatusTip( + tr("Load Partially Signed Bitcoin Transaction")); openRPCConsoleAction = new QAction(platformStyle->TextColorIcon(":/icons/debugwindow"), @@ -443,6 +446,8 @@ [this] { showNormalIfMinimized(); }); connect(verifyMessageAction, &QAction::triggered, [this] { gotoVerifyMessageTab(); }); + connect(m_load_psbt_action, &QAction::triggered, + [this] { gotoLoadPSBT(); }); connect(usedSendingAddressesAction, &QAction::triggered, walletFrame, &WalletFrame::usedSendingAddresses); connect(usedReceivingAddressesAction, &QAction::triggered, walletFrame, @@ -530,6 +535,7 @@ file->addAction(backupWalletAction); file->addAction(signMessageAction); file->addAction(verifyMessageAction); + file->addAction(m_load_psbt_action); file->addSeparator(); } file->addAction(quitAction); @@ -967,6 +973,11 @@ walletFrame->gotoVerifyMessageTab(addr); } } +void BitcoinGUI::gotoLoadPSBT() { + if (walletFrame) { + walletFrame->gotoLoadPSBT(); + } +} #endif // ENABLE_WALLET void BitcoinGUI::updateNetworkState() { diff --git a/src/qt/walletframe.h b/src/qt/walletframe.h --- a/src/qt/walletframe.h +++ b/src/qt/walletframe.h @@ -79,6 +79,9 @@ /** Show Sign/Verify Message dialog and switch to verify message tab */ void gotoVerifyMessageTab(QString addr = ""); + /** Load Partially Signed Bitcoin Transaction */ + void gotoLoadPSBT(); + /** Encrypt the wallet */ void encryptWallet(bool status); /** Backup the wallet */ diff --git a/src/qt/walletframe.cpp b/src/qt/walletframe.cpp --- a/src/qt/walletframe.cpp +++ b/src/qt/walletframe.cpp @@ -153,6 +153,13 @@ } } +void WalletFrame::gotoLoadPSBT() { + WalletView *walletView = currentWalletView(); + if (walletView) { + walletView->gotoLoadPSBT(); + } +} + void WalletFrame::encryptWallet(bool status) { WalletView *walletView = currentWalletView(); if (walletView) { diff --git a/src/qt/walletview.h b/src/qt/walletview.h --- a/src/qt/walletview.h +++ b/src/qt/walletview.h @@ -89,6 +89,8 @@ void gotoSignMessageTab(QString addr = ""); /** Show Sign/Verify Message dialog and switch to verify message tab */ void gotoVerifyMessageTab(QString addr = ""); + /** Load Partially Signed Bitcoin Transaction */ + void gotoLoadPSBT(); /** * Show incoming transaction notification for new transactions. diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -4,7 +4,11 @@ #include +#include // For GetConfig #include +#include +#include +#include #include #include #include @@ -20,6 +24,7 @@ #include #include #include +#include #include #include @@ -257,6 +262,101 @@ } } +void WalletView::gotoLoadPSBT() { + QString filename = GUIUtil::getOpenFileName( + this, tr("Load Transaction Data"), QString(), + tr("Partially Signed Transaction (*.psbt)"), nullptr); + if (filename.isEmpty()) { + return; + } + if (GetFileSize(filename.toLocal8Bit().data(), MAX_FILE_SIZE_PSBT) == + MAX_FILE_SIZE_PSBT) { + Q_EMIT message(tr("Error"), + tr("PSBT file must be smaller than 100 MiB"), + CClientUIInterface::MSG_ERROR); + return; + } + std::ifstream in(filename.toLocal8Bit().data(), std::ios::binary); + std::string dataStr(std::istreambuf_iterator{in}, {}); + + std::string error; + PartiallySignedTransaction psbtx; + if (!DecodeRawPSBT(psbtx, dataStr, error)) { + Q_EMIT message(tr("Error"), + tr("Unable to decode PSBT file") + "\n" + + QString::fromStdString(error), + CClientUIInterface::MSG_ERROR); + return; + } + + CMutableTransaction mtx; + bool complete = false; + PSBTAnalysis analysis = AnalyzePSBT(psbtx); + QMessageBox msgBox; + msgBox.setText("PSBT"); + switch (analysis.next) { + case PSBTRole::CREATOR: + case PSBTRole::UPDATER: + msgBox.setInformativeText( + "PSBT is incomplete. Copy to clipboard for manual inspection?"); + break; + case PSBTRole::SIGNER: + msgBox.setInformativeText( + "Transaction needs more signatures. Copy to clipboard?"); + break; + case PSBTRole::FINALIZER: + case PSBTRole::EXTRACTOR: + complete = FinalizeAndExtractPSBT(psbtx, mtx); + if (complete) { + msgBox.setInformativeText( + tr("Would you like to send this transaction?")); + } else { + // The analyzer missed something, e.g. if there are + // final_scriptSig but with invalid signatures. + msgBox.setInformativeText( + tr("There was an unexpected problem processing the PSBT. " + "Copy to clipboard for manual inspection?")); + } + } + + msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel); + switch (msgBox.exec()) { + case QMessageBox::Yes: { + if (complete) { + std::string err_string; + CTransactionRef tx = MakeTransactionRef(mtx); + + TransactionError result = BroadcastTransaction( + *clientModel->node().context(), GetConfig(), tx, err_string, + DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK(), /* relay */ true, + /* wait_callback */ false); + if (result == TransactionError::OK) { + Q_EMIT message(tr("Success"), + tr("Broadcasted transaction successfully."), + CClientUIInterface::MSG_INFORMATION | + CClientUIInterface::MODAL); + } else { + Q_EMIT message(tr("Error"), + QString::fromStdString(err_string), + CClientUIInterface::MSG_ERROR); + } + } else { + // Serialize the PSBT + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << psbtx; + GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str()); + Q_EMIT message(tr("PSBT copied"), "Copied to clipboard", + CClientUIInterface::MSG_INFORMATION); + return; + } + } + case QMessageBox::Cancel: + break; + default: + assert(false); + } +} + bool WalletView::handlePaymentRequest(const SendCoinsRecipient &recipient) { return sendCoinsPage->handlePaymentRequest(recipient); }