diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 2918676c22..370c5da9d0 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -317,6 +317,8 @@ void BitcoinGUI::createActions() signMessageAction->setStatusTip(tr("Sign messages with your Bitcoin addresses to prove you own them")); verifyMessageAction = new QAction(tr("&Verify message..."), this); 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(tr("Node window"), this); openRPCConsoleAction->setStatusTip(tr("Open node debugging and diagnostic console")); @@ -366,6 +368,7 @@ void BitcoinGUI::createActions() connect(changePassphraseAction, &QAction::triggered, walletFrame, &WalletFrame::changePassphrase); connect(signMessageAction, &QAction::triggered, [this]{ showNormalIfMinimized(); }); connect(signMessageAction, &QAction::triggered, [this]{ gotoSignMessageTab(); }); + connect(m_load_psbt_action, &QAction::triggered, [this]{ gotoLoadPSBT(); }); connect(verifyMessageAction, &QAction::triggered, [this]{ showNormalIfMinimized(); }); connect(verifyMessageAction, &QAction::triggered, [this]{ gotoVerifyMessageTab(); }); connect(usedSendingAddressesAction, &QAction::triggered, walletFrame, &WalletFrame::usedSendingAddresses); @@ -438,6 +441,7 @@ void BitcoinGUI::createMenuBar() file->addAction(backupWalletAction); file->addAction(signMessageAction); file->addAction(verifyMessageAction); + file->addAction(m_load_psbt_action); file->addSeparator(); } file->addAction(quitAction); @@ -854,6 +858,10 @@ void BitcoinGUI::gotoVerifyMessageTab(QString addr) { if (walletFrame) walletFrame->gotoVerifyMessageTab(addr); } +void BitcoinGUI::gotoLoadPSBT() +{ + if (walletFrame) walletFrame->gotoLoadPSBT(); +} #endif // ENABLE_WALLET void BitcoinGUI::updateNetworkState() diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index 809cf8b4ed..5592770d36 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -135,6 +135,7 @@ private: QAction* usedReceivingAddressesAction = nullptr; QAction* signMessageAction = nullptr; QAction* verifyMessageAction = nullptr; + QAction* m_load_psbt_action = nullptr; QAction* aboutAction = nullptr; QAction* receiveCoinsAction = nullptr; QAction* receiveCoinsMenuAction = nullptr; @@ -270,6 +271,8 @@ public Q_SLOTS: 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/walletframe.cpp b/src/qt/walletframe.cpp index dac3326cc4..02a9583ae9 100644 --- a/src/qt/walletframe.cpp +++ b/src/qt/walletframe.cpp @@ -163,6 +163,14 @@ void WalletFrame::gotoVerifyMessageTab(QString addr) walletView->gotoVerifyMessageTab(addr); } +void WalletFrame::gotoLoadPSBT() +{ + WalletView *walletView = currentWalletView(); + if (walletView) { + walletView->gotoLoadPSBT(); + } +} + void WalletFrame::encryptWallet(bool status) { WalletView *walletView = currentWalletView(); diff --git a/src/qt/walletframe.h b/src/qt/walletframe.h index 20fad08b0e..d90ade5005 100644 --- a/src/qt/walletframe.h +++ b/src/qt/walletframe.h @@ -78,6 +78,9 @@ public Q_SLOTS: /** 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/walletview.cpp b/src/qt/walletview.cpp index bdcb82e06b..d0eca1a25b 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -4,6 +4,9 @@ #include +#include +#include +#include #include #include #include @@ -20,6 +23,7 @@ #include #include +#include #include #include @@ -197,6 +201,80 @@ void WalletView::gotoVerifyMessageTab(QString addr) signVerifyMessageDialog->setAddress_VM(addr); } +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 data(std::istreambuf_iterator{in}, {}); + + std::string error; + PartiallySignedTransaction psbtx; + if (!DecodeRawPSBT(psbtx, data, 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/final_scriptWitness + // 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(), 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 sucessfully."), 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); diff --git a/src/qt/walletview.h b/src/qt/walletview.h index 78d870f59f..00a95eeec5 100644 --- a/src/qt/walletview.h +++ b/src/qt/walletview.h @@ -83,6 +83,8 @@ public Q_SLOTS: 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.