Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
1// Copyright (c) 2011-present The Bitcoin Core developers2// Distributed under the MIT software license, see the accompanying3// file COPYING or http://www.opensource.org/licenses/mit-license.php.5#include <qt/psbtoperationsdialog.h>7#include <common/messages.h>8#include <core_io.h>9#include <interfaces/node.h>10#include <key_io.h>11#include <node/psbt.h>12#include <node/types.h>13#include <policy/policy.h>14#include <qt/bitcoinunits.h>15#include <qt/forms/ui_psbtoperationsdialog.h>16#include <qt/guiutil.h>17#include <qt/optionsmodel.h>18#include <util/fs.h>19#include <util/strencodings.h>21#include <fstream>22#include <iostream>23#include <string>25using common::TransactionErrorString;26using node::AnalyzePSBT;27using node::DEFAULT_MAX_RAW_TX_FEE_RATE;28using node::PSBTAnalysis;29using node::TransactionError;31PSBTOperationsDialog::PSBTOperationsDialog(32QWidget* parent, WalletModel* wallet_model, ClientModel* client_model) : QDialog(parent, GUIUtil::dialog_flags),33m_ui(new Ui::PSBTOperationsDialog),34m_wallet_model(wallet_model),35m_client_model(client_model)36{37m_ui->setupUi(this);39connect(m_ui->signTransactionButton, &QPushButton::clicked, this, &PSBTOperationsDialog::signTransaction);40connect(m_ui->broadcastTransactionButton, &QPushButton::clicked, this, &PSBTOperationsDialog::broadcastTransaction);41connect(m_ui->copyToClipboardButton, &QPushButton::clicked, this, &PSBTOperationsDialog::copyToClipboard);42connect(m_ui->saveButton, &QPushButton::clicked, this, &PSBTOperationsDialog::saveTransaction);44connect(m_ui->closeButton, &QPushButton::clicked, this, &PSBTOperationsDialog::close);46m_ui->signTransactionButton->setEnabled(false);47m_ui->broadcastTransactionButton->setEnabled(false);48}50PSBTOperationsDialog::~PSBTOperationsDialog()51{52delete m_ui;53}55void PSBTOperationsDialog::openWithPSBT(PartiallySignedTransaction psbtx)56{57m_transaction_data = psbtx;59bool complete = FinalizePSBT(psbtx); // Make sure all existing signatures are fully combined before checking for completeness.60if (m_wallet_model) {61size_t n_could_sign;62const auto err{m_wallet_model->wallet().fillPSBT({.sign = false, .bip32_derivs= true}, &n_could_sign, *m_transaction_data, complete)};63if (err) {64showStatus(tr("Failed to load transaction: %1")65.arg(QString::fromStdString(PSBTErrorString(*err).translated)),66StatusLevel::Error);67return;68}69m_ui->signTransactionButton->setEnabled(!complete && !m_wallet_model->wallet().privateKeysDisabled() && n_could_sign > 0);70} else {71m_ui->signTransactionButton->setEnabled(false);72}74m_ui->broadcastTransactionButton->setEnabled(complete);76updateTransactionDisplay();77}79void PSBTOperationsDialog::signTransaction()80{81bool complete;82size_t n_signed;84WalletModel::UnlockContext ctx(m_wallet_model->requestUnlock());86const auto err{m_wallet_model->wallet().fillPSBT({.sign = true, .bip32_derivs = true}, &n_signed, *m_transaction_data, complete)};88if (err) {89showStatus(tr("Failed to sign transaction: %1")90.arg(QString::fromStdString(PSBTErrorString(*err).translated)), StatusLevel::Error);91return;92}94updateTransactionDisplay();96if (!complete && !ctx.isValid()) {97showStatus(tr("Cannot sign inputs while wallet is locked."), StatusLevel::Warn);98} else if (!complete && n_signed < 1) {99showStatus(tr("Could not sign any more inputs."), StatusLevel::Warn);100} else if (!complete) {101showStatus(tr("Signed %n input(s), but more signatures are still required.", "", n_signed),102StatusLevel::Info);103} else {104showStatus(tr("Signed transaction successfully. Transaction is ready to broadcast."),105StatusLevel::Info);106m_ui->broadcastTransactionButton->setEnabled(true);107}108}110void PSBTOperationsDialog::broadcastTransaction()111{112CMutableTransaction mtx;113if (!FinalizeAndExtractPSBT(*m_transaction_data, mtx)) {114// This is never expected to fail unless we were given a malformed PSBT115// (e.g. with an invalid signature.)116showStatus(tr("Unknown error processing transaction."), StatusLevel::Error);117return;118}120CTransactionRef tx = MakeTransactionRef(mtx);121std::string err_string;122TransactionError error =123m_client_model->node().broadcastTransaction(tx, DEFAULT_MAX_RAW_TX_FEE_RATE.GetFeePerK(), err_string);125if (error == TransactionError::OK) {126showStatus(tr("Transaction broadcast successfully! Transaction ID: %1")127.arg(QString::fromStdString(tx->GetHash().GetHex())), StatusLevel::Info);128} else {129showStatus(tr("Transaction broadcast failed: %1")130.arg(QString::fromStdString(TransactionErrorString(error).translated)), StatusLevel::Error);131}132}134void PSBTOperationsDialog::copyToClipboard() {135DataStream ssTx{};136ssTx << *m_transaction_data;137GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str());138showStatus(tr("PSBT copied to clipboard."), StatusLevel::Info);139}141void PSBTOperationsDialog::saveTransaction() {142DataStream ssTx{};143ssTx << *m_transaction_data;145QString selected_filter;146QString filename_suggestion = "";147bool first = true;148for (const PSBTOutput& out : m_transaction_data->outputs) {149if (!first) {150filename_suggestion.append("-");151}152CTxDestination address;153ExtractDestination(out.script, address);154QString amount = BitcoinUnits::format(m_client_model->getOptionsModel()->getDisplayUnit(), out.amount);155QString address_str = QString::fromStdString(EncodeDestination(address));156filename_suggestion.append(address_str + "-" + amount);157first = false;158}159filename_suggestion.append(".psbt");160QString filename = GUIUtil::getSaveFileName(this,161tr("Save Transaction Data"), filename_suggestion,162//: Expanded name of the binary PSBT file format. See: BIP 174.163tr("Partially Signed Transaction (Binary)") + QLatin1String(" (*.psbt)"), &selected_filter);164if (filename.isEmpty()) {165return;166}167std::ofstream out{filename.toLocal8Bit().data(), std::ofstream::out | std::ofstream::binary};168out << ssTx.str();169out.close();170showStatus(tr("PSBT saved to disk."), StatusLevel::Info);171}173void PSBTOperationsDialog::updateTransactionDisplay() {174m_ui->transactionDescription->setText(renderTransaction(*m_transaction_data));175showTransactionStatus(*m_transaction_data);176}178QString PSBTOperationsDialog::renderTransaction(const PartiallySignedTransaction &psbtx)179{180QString tx_description;181QLatin1String bullet_point(" * ");182CAmount totalAmount = 0;183for (const PSBTOutput& out : psbtx.outputs) {184CTxDestination address;185ExtractDestination(out.script, address);186totalAmount += out.amount;187tx_description.append(bullet_point).append(tr("Sends %1 to %2")188.arg(BitcoinUnits::formatWithUnit(BitcoinUnit::BTC, out.amount))189.arg(QString::fromStdString(EncodeDestination(address))));190// Check if the address is one of ours191if (m_wallet_model != nullptr && m_wallet_model->wallet().txoutIsMine(CTxOut(out.amount, out.script))) tx_description.append(" (" + tr("own address") + ")");192tx_description.append("<br>");193}195PSBTAnalysis analysis = AnalyzePSBT(psbtx);196tx_description.append(bullet_point);197if (!*analysis.fee) {198// This happens if the transaction is missing input UTXO information.199tx_description.append(tr("Unable to calculate transaction fee or total transaction amount."));200} else {201tx_description.append(tr("Pays transaction fee: "));202tx_description.append(BitcoinUnits::formatWithUnit(BitcoinUnit::BTC, *analysis.fee));204// add total amount in all subdivision units205tx_description.append("<hr />");206QStringList alternativeUnits;207for (const BitcoinUnits::Unit u : BitcoinUnits::availableUnits())208{209if(u != m_client_model->getOptionsModel()->getDisplayUnit()) {210alternativeUnits.append(BitcoinUnits::formatHtmlWithUnit(u, totalAmount));211}212}213tx_description.append(QString("<b>%1</b>: <b>%2</b>").arg(tr("Total Amount"))214.arg(BitcoinUnits::formatHtmlWithUnit(m_client_model->getOptionsModel()->getDisplayUnit(), totalAmount)));215tx_description.append(QString("<br /><span style='font-size:10pt; font-weight:normal;'>(=%1)</span>")216.arg(alternativeUnits.join(" " + tr("or") + " ")));217}219size_t num_unsigned = CountPSBTUnsignedInputs(psbtx);220if (num_unsigned > 0) {221tx_description.append("<br><br>");222tx_description.append(tr("Transaction has %n unsigned input(s).", "", num_unsigned));223}225return tx_description;226}228void PSBTOperationsDialog::showStatus(const QString &msg, StatusLevel level) {229m_ui->statusBar->setText(msg);230switch (level) {231case StatusLevel::Info: {232m_ui->statusBar->setStyleSheet("QLabel { background-color : lightgreen }");233break;234}235case StatusLevel::Warn: {236m_ui->statusBar->setStyleSheet("QLabel { background-color : orange }");237break;238}239case StatusLevel::Error: {240m_ui->statusBar->setStyleSheet("QLabel { background-color : red }");241break;242}243}244m_ui->statusBar->show();245}247size_t PSBTOperationsDialog::couldSignInputs(const PartiallySignedTransaction &psbtx) {248if (!m_wallet_model) {249return 0;250}
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.