gui: Menu action for exporting a watchonly wallet
What changed, and why it matters
This commit adds a new menu item in the Bitcoin Core desktop wallet that lets users export a 'watch-only' copy of their wallet. A watch-only wallet can see transactions and balances but cannot spend funds. The change only wires up an existing export function to the graphical user interface; it does not appear to fix a bug or introduce a security vulnerability.
No security action required. Treat as a normal feature addition. If reviewing further, verify that ExportWatchOnlyWallet itself handles path validation and does not overwrite unexpected files, but that logic is outside this commit.
Security signals we found
No security-relevant signals detected in the diff
New GUI feature exposing existing wallet export functionality
Action is disabled for wallets with private keys disabled
Evidence from the diff
The patch exposes the existing CWallet::ExportWatchOnlyWallet capability through the Wallet interface and adds a Qt GUI action. It adds exportWatchOnlyWallet() to the wallet interface, implements it in wallet/interfaces.cpp (locking cs_wallet, topping up the key pool, and calling ExportWatchOnlyWallet), and creates a menu action in BitcoinGUI that prompts for a destination file, calls the export, and shows a success or error message. The action is enabled only when private keys are not disabled.
Changed components
src/interfaces/wallet.hsrc/qt/bitcoingui.cppsrc/qt/bitcoingui.hsrc/wallet/interfaces.cppInspect captured patch +30 / −0
diff --git a/src/interfaces/wallet.h b/src/interfaces/wallet.h
index 326361aa..f080f070 100644
--- a/src/interfaces/wallet.h
+++ b/src/interfaces/wallet.h
@@ -302,6 +302,9 @@ public:
//! Return pointer to internal wallet class, useful for testing.
virtual wallet::CWallet* wallet() { return nullptr; }
+
+ //! Export a watchonly wallet file. See CWallet::ExportWatchOnlyWallet
+ virtual util::Result<std::string> exportWatchOnlyWallet(const fs::path& destination) = 0;
};
//! Wallet chain client that in addition to having chain client methods for
diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp
index 5f169a95..98d9d21b 100644
--- a/src/qt/bitcoingui.cpp
+++ b/src/qt/bitcoingui.cpp
@@ -377,6 +377,10 @@ void BitcoinGUI::createActions()
m_mask_values_action->setStatusTip(tr("Mask the values in the Overview tab"));
m_mask_values_action->setCheckable(true);
+ m_export_watchonly_action = new QAction(tr("Export watch-only wallet"), this);
+ m_export_watchonly_action->setEnabled(false);
+ m_export_watchonly_action->setStatusTip(tr("Export a watch-only version of the current wallet that can be restored onto another node."));
+
connect(quitAction, &QAction::triggered, this, &BitcoinGUI::quitRequested);
connect(aboutAction, &QAction::triggered, this, &BitcoinGUI::aboutClicked);
connect(aboutQtAction, &QAction::triggered, qApp, QApplication::aboutQt);
@@ -524,6 +528,18 @@ void BitcoinGUI::createActions()
});
connect(m_mask_values_action, &QAction::toggled, this, &BitcoinGUI::setPrivacy);
connect(m_mask_values_action, &QAction::toggled, this, &BitcoinGUI::enableHistoryAction);
+ GUIUtil::ExceptionSafeConnect(m_export_watchonly_action, &QAction::triggered, [this](bool) {
+ QString destination = GUIUtil::getSaveFileName(this, tr("Save Watch-only Wallet Export"), QString(), QString(), nullptr);
+ if (destination.isEmpty()) return;
+ WalletModel* model = walletFrame->currentWalletModel();
+ if (!Assume(model)) return;
+ util::Result<std::string> export_res = model->wallet().exportWatchOnlyWallet(GUIUtil::QStringToPath(destination));
+ if (export_res) {
+ QMessageBox::information(nullptr, tr("Export Successful"), tr("The wallet has been exported to ") + QString::fromStdString(*export_res));
+ } else {
+ QMessageBox::critical(nullptr, tr("Export Error"), QString::fromStdString(util::ErrorString(export_res).translated));
+ }
+ });
}
#endif // ENABLE_WALLET
@@ -547,6 +563,7 @@ void BitcoinGUI::createMenuBar()
file->addSeparator();
file->addAction(backupWalletAction);
file->addAction(m_restore_wallet_action);
+ file->addAction(m_export_watchonly_action);
file->addSeparator();
file->addAction(openAction);
file->addAction(signMessageAction);
@@ -832,6 +849,7 @@ void BitcoinGUI::setCurrentWallet(WalletModel* wallet_model)
break;
}
}
+ m_export_watchonly_action->setEnabled(!wallet_model->wallet().privateKeysDisabled());
updateWindowTitle();
}
@@ -866,6 +884,7 @@ void BitcoinGUI::setWalletActionsEnabled(bool enabled)
openAction->setEnabled(enabled);
m_close_wallet_action->setEnabled(enabled);
m_close_all_wallets_action->setEnabled(enabled);
+ m_export_watchonly_action->setEnabled(enabled);
}
void BitcoinGUI::createTrayIcon()
diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h
index babb8961..538e1d19 100644
--- a/src/qt/bitcoingui.h
+++ b/src/qt/bitcoingui.h
@@ -167,6 +167,7 @@ private:
QAction* m_mask_values_action{nullptr};
QAction* m_migrate_wallet_action{nullptr};
QMenu* m_migrate_wallet_menu{nullptr};
+ QAction* m_export_watchonly_action{nullptr};
#ifdef ENABLE_WALLET
QLabel *m_wallet_selector_label = nullptr;
QComboBox* m_wallet_selector = nullptr;
diff --git a/src/wallet/interfaces.cpp b/src/wallet/interfaces.cpp
index 9f9876da..1c5f689e 100644
--- a/src/wallet/interfaces.cpp
+++ b/src/wallet/interfaces.cpp
@@ -21,6 +21,7 @@
#include <util/ui_change_type.h>
#include <wallet/coincontrol.h>
#include <wallet/context.h>
+#include <wallet/export.h>
#include <wallet/feebumper.h>
#include <wallet/fees.h>
#include <wallet/load.h>
@@ -522,6 +523,12 @@ public:
}
CWallet* wallet() override { return m_wallet.get(); }
+ util::Result<std::string> exportWatchOnlyWallet(const fs::path& destination) override {
+ LOCK(m_wallet->cs_wallet);
+ m_wallet->TopUpKeyPool();
+ return ExportWatchOnlyWallet(*m_wallet, destination, m_context);
+ }
+
WalletContext& m_context;
std::shared_ptr<CWallet> m_wallet;
};
Why this scored 17/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.