Skip to content

Commit 1ed14c6

Browse files
committed
Merge #872: Menu action to export a watchonly wallet
6573196 doc: Release note for export watchonly wallet gui action (Ava Chow) cb51f97 gui: Menu action for exporting a watchonly wallet (Ava Chow) 5907a5c gui: Add ExceptionSafeConnect that takes a lambda (Ava Chow) Pull request description: Allows a user to export a watchonly version of their wallet to be used in an airgapped setup. Built on bitcoin/bitcoin#32489 ACKs for top commit: polespinasa: lgtm ACK 6573196 pablomartin4btc: ACK 6573196 hebasto: ACK 6573196. Tree-SHA512: 30732ecf2ff40dbbd62a8a9974a907fd60f0da89afacce618fb706a02349135dc06d7dfcc11009caba0e020609ab7e586a0ebbeb7cd65940ee2df229d23f0605
2 parents e27c179 + 6573196 commit 1ed14c6

7 files changed

Lines changed: 74 additions & 1 deletion

File tree

doc/release-notes-gui-872.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
GUI
2+
---
3+
4+
* A menu action has been added to allow creating a watchonly wallet file from
5+
an existing descriptor wallet. This option mirrors the `exportwatchonlywallet`
6+
RPC - the exported file can be imported to another node using the Restore
7+
Wallet menu action.

src/interfaces/wallet.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,9 @@ class Wallet
302302

303303
//! Return pointer to internal wallet class, useful for testing.
304304
virtual wallet::CWallet* wallet() { return nullptr; }
305+
306+
//! Export a watchonly wallet file. See CWallet::ExportWatchOnlyWallet
307+
virtual util::Result<std::string> exportWatchOnlyWallet(const fs::path& destination) = 0;
305308
};
306309

307310
//! Wallet chain client that in addition to having chain client methods for

src/qt/bitcoingui.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,10 @@ void BitcoinGUI::createActions()
377377
m_mask_values_action->setStatusTip(tr("Mask the values in the Overview tab"));
378378
m_mask_values_action->setCheckable(true);
379379

380+
m_export_watchonly_action = new QAction(tr("Export watch-only wallet"), this);
381+
m_export_watchonly_action->setEnabled(false);
382+
m_export_watchonly_action->setStatusTip(tr("Export a watch-only version of the current wallet that can be restored onto another node."));
383+
380384
connect(quitAction, &QAction::triggered, this, &BitcoinGUI::quitRequested);
381385
connect(aboutAction, &QAction::triggered, this, &BitcoinGUI::aboutClicked);
382386
connect(aboutQtAction, &QAction::triggered, qApp, QApplication::aboutQt);
@@ -524,6 +528,18 @@ void BitcoinGUI::createActions()
524528
});
525529
connect(m_mask_values_action, &QAction::toggled, this, &BitcoinGUI::setPrivacy);
526530
connect(m_mask_values_action, &QAction::toggled, this, &BitcoinGUI::enableHistoryAction);
531+
GUIUtil::ExceptionSafeConnect(m_export_watchonly_action, &QAction::triggered, [this](bool) {
532+
QString destination = GUIUtil::getSaveFileName(this, tr("Save Watch-only Wallet Export"), QString(), QString(), nullptr);
533+
if (destination.isEmpty()) return;
534+
WalletModel* model = walletFrame->currentWalletModel();
535+
if (!Assume(model)) return;
536+
util::Result<std::string> export_res = model->wallet().exportWatchOnlyWallet(GUIUtil::QStringToPath(destination));
537+
if (export_res) {
538+
QMessageBox::information(nullptr, tr("Export Successful"), tr("The wallet has been exported to ") + QString::fromStdString(*export_res));
539+
} else {
540+
QMessageBox::critical(nullptr, tr("Export Error"), QString::fromStdString(util::ErrorString(export_res).translated));
541+
}
542+
});
527543
}
528544
#endif // ENABLE_WALLET
529545

@@ -547,6 +563,7 @@ void BitcoinGUI::createMenuBar()
547563
file->addSeparator();
548564
file->addAction(backupWalletAction);
549565
file->addAction(m_restore_wallet_action);
566+
file->addAction(m_export_watchonly_action);
550567
file->addSeparator();
551568
file->addAction(openAction);
552569
file->addAction(signMessageAction);
@@ -832,6 +849,7 @@ void BitcoinGUI::setCurrentWallet(WalletModel* wallet_model)
832849
break;
833850
}
834851
}
852+
m_export_watchonly_action->setEnabled(!wallet_model->wallet().privateKeysDisabled());
835853
updateWindowTitle();
836854
}
837855

@@ -866,6 +884,7 @@ void BitcoinGUI::setWalletActionsEnabled(bool enabled)
866884
openAction->setEnabled(enabled);
867885
m_close_wallet_action->setEnabled(enabled);
868886
m_close_all_wallets_action->setEnabled(enabled);
887+
m_export_watchonly_action->setEnabled(enabled);
869888
}
870889

871890
void BitcoinGUI::createTrayIcon()

src/qt/bitcoingui.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ class BitcoinGUI : public QMainWindow
167167
QAction* m_mask_values_action{nullptr};
168168
QAction* m_migrate_wallet_action{nullptr};
169169
QMenu* m_migrate_wallet_menu{nullptr};
170+
QAction* m_export_watchonly_action{nullptr};
170171
#ifdef ENABLE_WALLET
171172
QLabel *m_wallet_selector_label = nullptr;
172173
QComboBox* m_wallet_selector = nullptr;

src/qt/guiutil.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -972,7 +972,11 @@ void PrintSlotException(
972972
{
973973
std::string description = sender->metaObject()->className();
974974
description += "->";
975-
description += receiver->metaObject()->className();
975+
if (receiver) {
976+
description += receiver->metaObject()->className();
977+
} else {
978+
description += "anonymous function";
979+
}
976980
PrintExceptionContinue(exception, description);
977981
}
978982

src/qt/guiutil.h

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,38 @@ namespace GUIUtil
399399
},
400400
type);
401401
}
402+
template <typename Sender, typename Signal, typename Slot>
403+
auto ExceptionSafeConnect(
404+
Sender sender, Signal signal, Slot method)
405+
{
406+
return QObject::connect(
407+
sender, signal,
408+
[sender, method](auto&&... args) {
409+
bool ok{true};
410+
try {
411+
method(std::forward<decltype(args)>(args)...);
412+
} catch (const NonFatalCheckError& e) {
413+
PrintSlotException(&e, sender, nullptr);
414+
ok = QMetaObject::invokeMethod(
415+
qApp, "handleNonFatalException",
416+
blockingGUIThreadConnection(),
417+
Q_ARG(QString, QString::fromStdString(e.what())));
418+
} catch (const std::exception& e) {
419+
PrintSlotException(&e, sender, nullptr);
420+
ok = QMetaObject::invokeMethod(
421+
qApp, "handleRunawayException",
422+
blockingGUIThreadConnection(),
423+
Q_ARG(QString, QString::fromStdString(e.what())));
424+
} catch (...) {
425+
PrintSlotException(nullptr, sender, nullptr);
426+
ok = QMetaObject::invokeMethod(
427+
qApp, "handleRunawayException",
428+
blockingGUIThreadConnection(),
429+
Q_ARG(QString, "Unknown failure occurred."));
430+
}
431+
assert(ok);
432+
});
433+
}
402434

403435
/**
404436
* Shows a QDialog instance asynchronously, and deletes it on close.

src/wallet/interfaces.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <util/ui_change_type.h>
2222
#include <wallet/coincontrol.h>
2323
#include <wallet/context.h>
24+
#include <wallet/export.h>
2425
#include <wallet/feebumper.h>
2526
#include <wallet/fees.h>
2627
#include <wallet/load.h>
@@ -522,6 +523,12 @@ class WalletImpl : public Wallet
522523
}
523524
CWallet* wallet() override { return m_wallet.get(); }
524525

526+
util::Result<std::string> exportWatchOnlyWallet(const fs::path& destination) override {
527+
LOCK(m_wallet->cs_wallet);
528+
m_wallet->TopUpKeyPool();
529+
return ExportWatchOnlyWallet(*m_wallet, destination, m_context);
530+
}
531+
525532
WalletContext& m_context;
526533
std::shared_ptr<CWallet> m_wallet;
527534
};

0 commit comments

Comments
 (0)