wallet(HD): Qt UI - Seed Phrase dialog (generate/restore/backup)

Adds HDSeedDialog (Settings > Seed Phrase) with Generate New / Reveal for Backup / Restore from Phrase, driven by new WalletModel HD methods. Restore rescans the chain. Requires wallet unlock via the standard UnlockContext.
This commit is contained in:
Sami
2026-06-15 14:44:33 -07:00
parent 2e19d85b18
commit 77b05a84f2
7 changed files with 246 additions and 0 deletions
+1
View File
@@ -307,6 +307,7 @@ if(BUILD_QT)
qt/trianglesunits.cpp
qt/qvaluecombobox.cpp
qt/askpassphrasedialog.cpp
qt/hdseeddialog.cpp
qt/notificator.cpp
qt/qtipcserver.cpp
qt/rpcconsole.cpp
+142
View File
@@ -0,0 +1,142 @@
// Copyright (c) 2026 The Triangles developers
// Distributed under the MIT/X11 software license.
#include "hdseeddialog.h"
#include "walletmodel.h"
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
#include <QPlainTextEdit>
#include <QLabel>
#include <QMessageBox>
#include <QFont>
HDSeedDialog::HDSeedDialog(QWidget *parent)
: QDialog(parent), model(0), seedText(0), statusLabel(0)
{
setWindowTitle(tr("HD Seed Phrase (BIP39)"));
resize(560, 360);
QVBoxLayout *layout = new QVBoxLayout(this);
QLabel *intro = new QLabel(tr(
"A 24-word seed phrase is a complete backup of this wallet. Anyone who has it "
"can spend your coins. Write it down on paper and keep it offline."), this);
intro->setWordWrap(true);
layout->addWidget(intro);
seedText = new QPlainTextEdit(this);
seedText->setPlaceholderText(tr(
"Your 24-word phrase appears here when you generate or reveal it. "
"To restore, paste an existing 24-word phrase here and click 'Restore from Phrase'."));
QFont mono("monospace");
mono.setStyleHint(QFont::Monospace);
seedText->setFont(mono);
layout->addWidget(seedText);
statusLabel = new QLabel(this);
statusLabel->setWordWrap(true);
layout->addWidget(statusLabel);
QHBoxLayout *btns = new QHBoxLayout();
QPushButton *genBtn = new QPushButton(tr("Generate New"), this);
QPushButton *showBtn = new QPushButton(tr("Reveal for Backup"), this);
QPushButton *restoreBtn = new QPushButton(tr("Restore from Phrase"), this);
QPushButton *closeBtn = new QPushButton(tr("Close"), this);
btns->addWidget(genBtn);
btns->addWidget(showBtn);
btns->addWidget(restoreBtn);
btns->addStretch();
btns->addWidget(closeBtn);
layout->addLayout(btns);
connect(genBtn, SIGNAL(clicked()), this, SLOT(onGenerate()));
connect(showBtn, SIGNAL(clicked()), this, SLOT(onShow()));
connect(restoreBtn, SIGNAL(clicked()), this, SLOT(onRestore()));
connect(closeBtn, SIGNAL(clicked()), this, SLOT(accept()));
}
void HDSeedDialog::setModel(WalletModel *modelIn)
{
model = modelIn;
refreshStatus();
}
void HDSeedDialog::refreshStatus()
{
if (!model || !statusLabel) return;
if (model->hdEnabled())
statusLabel->setText(tr("Status: HD seed is ACTIVE. Use 'Reveal for Backup' to view your phrase."));
else
statusLabel->setText(tr("Status: no HD seed yet. Use 'Generate New' to create one."));
}
void HDSeedDialog::onGenerate()
{
if (!model) return;
if (model->hdEnabled()) {
QMessageBox::warning(this, tr("HD seed already set"),
tr("This wallet already has an HD seed. Use 'Reveal for Backup' to view it."));
return;
}
if (QMessageBox::question(this, tr("Generate new seed"),
tr("Generate a new 24-word HD seed for this wallet?"),
QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes)
return;
WalletModel::UnlockContext ctx(model->requestUnlock());
if (!ctx.isValid()) return;
QString mnemonic, err;
if (!model->hdNew(mnemonic, err)) {
QMessageBox::critical(this, tr("Error"), err);
return;
}
seedText->setPlainText(mnemonic);
QMessageBox::information(this, tr("Write this down"),
tr("Your new 24-word seed phrase is shown above. Write it on paper and store it safely "
"and offline. This is the only backup of this wallet."));
refreshStatus();
}
void HDSeedDialog::onShow()
{
if (!model) return;
WalletModel::UnlockContext ctx(model->requestUnlock());
if (!ctx.isValid()) return;
QString mnemonic, err;
if (!model->hdShow(mnemonic, err)) {
QMessageBox::critical(this, tr("Error"), err);
return;
}
seedText->setPlainText(mnemonic);
}
void HDSeedDialog::onRestore()
{
if (!model) return;
QString phrase = seedText->toPlainText().trimmed();
if (phrase.isEmpty()) {
QMessageBox::warning(this, tr("No phrase"),
tr("Paste a 24-word phrase into the box first."));
return;
}
if (QMessageBox::question(this, tr("Restore from phrase"),
tr("Restore the HD seed from the phrase in the box and rescan the chain? "
"This replaces the current HD seed."),
QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes)
return;
WalletModel::UnlockContext ctx(model->requestUnlock());
if (!ctx.isValid()) return;
QString err;
if (!model->hdRestore(phrase, err)) {
QMessageBox::critical(this, tr("Error"), err);
return;
}
QMessageBox::information(this, tr("Restored"),
tr("HD seed restored and the chain was rescanned for your funds."));
refreshStatus();
}
+34
View File
@@ -0,0 +1,34 @@
// Copyright (c) 2026 The Triangles developers
// Distributed under the MIT/X11 software license.
#ifndef HDSEEDDIALOG_H
#define HDSEEDDIALOG_H
#include <QDialog>
class WalletModel;
QT_BEGIN_NAMESPACE
class QPlainTextEdit;
class QLabel;
QT_END_NAMESPACE
/** Generate, reveal (for backup), and restore the wallet's BIP39 HD seed phrase. */
class HDSeedDialog : public QDialog
{
Q_OBJECT
public:
explicit HDSeedDialog(QWidget *parent = 0);
void setModel(WalletModel *model);
private:
WalletModel *model;
QPlainTextEdit *seedText;
QLabel *statusLabel;
void refreshStatus();
private slots:
void onGenerate();
void onShow();
void onRestore();
};
#endif // HDSEEDDIALOG_H
+14
View File
@@ -31,6 +31,7 @@
#include "trianglesunits.h"
#include "guiconstants.h"
#include "askpassphrasedialog.h"
#include "hdseeddialog.h"
#include "notificator.h"
#include "guiutil.h"
#include "rpcconsole.h"
@@ -484,6 +485,8 @@ void TrianglesGUI::createActions(bool fIsTestnet)
backupWalletAction->setStatusTip(tr("Backup wallet to another location"));
changePassphraseAction = new QAction(QIcon(":/menu_16/passphrase"), tr("&Change Passphrase..."), this);
changePassphraseAction->setStatusTip(tr("Change the passphrase used for wallet encryption"));
hdSeedAction = new QAction(QIcon(":/menu_16/passphrase"), tr("&Seed Phrase (HD Backup)..."), this);
hdSeedAction->setStatusTip(tr("Generate, restore, or back up your 24-word HD seed phrase"));
unlockWalletAction = new QAction(QIcon(":/menu_16/unlock"), tr("&Unlock Wallet..."), this);
unlockWalletAction->setStatusTip(tr("Unlock wallet"));
unlockWalletStakingAction = new QAction(QIcon(":/menu_16/unlock"), tr("&Unlock Wallet for staking..."), this);
@@ -509,6 +512,7 @@ void TrianglesGUI::createActions(bool fIsTestnet)
connect(encryptWalletAction, SIGNAL(triggered(bool)), this, SLOT(encryptWallet(bool)));
connect(backupWalletAction, SIGNAL(triggered()), this, SLOT(backupWallet()));
connect(changePassphraseAction, SIGNAL(triggered()), this, SLOT(changePassphrase()));
connect(hdSeedAction, SIGNAL(triggered()), this, SLOT(hdSeedManager()));
connect(unlockWalletAction, SIGNAL(triggered()), this, SLOT(unlockWallet()));
connect(unlockWalletStakingAction, SIGNAL(triggered()), this, SLOT(unlockWalletStaking()));
connect(lockWalletAction, SIGNAL(triggered()), this, SLOT(lockWallet()));
@@ -538,6 +542,7 @@ void TrianglesGUI::createMenuBar()
QMenu *settings = appMenuBar->addMenu(tr("&Settings"));
settings->addAction(encryptWalletAction);
settings->addAction(changePassphraseAction);
settings->addAction(hdSeedAction);
settings->addAction(unlockWalletAction);
settings->addAction(lockWalletAction);
settings->addSeparator();
@@ -1614,6 +1619,15 @@ void TrianglesGUI::changePassphrase()
dlg.exec();
}
void TrianglesGUI::hdSeedManager()
{
if (!walletModel)
return;
HDSeedDialog dlg(this);
dlg.setModel(walletModel);
dlg.exec();
}
void TrianglesGUI::unlockWalletStaking()
{
+3
View File
@@ -131,6 +131,7 @@ private:
QAction *encryptWalletAction;
QAction *backupWalletAction;
QAction *changePassphraseAction;
QAction *hdSeedAction;
QAction *unlockWalletAction;
QAction *unlockWalletStakingAction;
QAction *lockWalletAction;
@@ -243,6 +244,8 @@ private slots:
void backupWallet();
/** Change encrypted wallet passphrase */
void changePassphrase();
/** Open the HD seed phrase (generate/restore/backup) dialog */
void hdSeedManager();
/** Ask for passphrase to unlock wallet temporarily */
void unlockWallet();
/** Ask for passphrase to unlock wallet temporarily - FOR STAKING ONLY */
+46
View File
@@ -676,3 +676,49 @@ void WalletModel::listLockedCoins(std::vector<COutPoint>& vOutpts)
{
return;
}
// ---- HD wallet (BIP39/BIP32) ----
bool WalletModel::hdEnabled() const
{
return wallet->IsHDEnabled();
}
bool WalletModel::hdNew(QString &mnemonicOut, QString &errorOut)
{
std::string mnemonic, strError;
if (!wallet->SetHDSeed("", "", true, mnemonic, strError)) {
errorOut = QString::fromStdString(strError);
return false;
}
wallet->TopUpKeyPool();
mnemonicOut = QString::fromStdString(mnemonic);
return true;
}
bool WalletModel::hdRestore(const QString &mnemonic, QString &errorOut)
{
std::string out, strError;
if (!wallet->SetHDSeed(mnemonic.toStdString(), "", false, out, strError)) {
errorOut = QString::fromStdString(strError);
return false;
}
wallet->TopUpKeyPool();
{
LOCK2(cs_main, wallet->cs_wallet);
wallet->ScanForWalletTransactions(pindexGenesisBlock, true);
wallet->ReacceptWalletTransactions();
}
return true;
}
bool WalletModel::hdShow(QString &mnemonicOut, QString &errorOut)
{
std::string mnemonic;
if (!wallet->GetHDMnemonic(mnemonic)) {
errorOut = QObject::tr("Wallet has no HD seed (use 'Generate New').");
return false;
}
mnemonicOut = QString::fromStdString(mnemonic);
return true;
}
+6
View File
@@ -103,6 +103,12 @@ public:
// Wallet backup
bool backupWallet(const QString &filename);
// ---- HD wallet (BIP39/BIP32) ----
bool hdEnabled() const;
bool hdNew(QString &mnemonicOut, QString &errorOut);
bool hdRestore(const QString &mnemonic, QString &errorOut);
bool hdShow(QString &mnemonicOut, QString &errorOut);
// RAI object for unlocking wallet, returned by requestUnlock()
class UnlockContext
{