Hush lite wallet https://faq.hush.is/sdl
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1053 lines
39 KiB

6 years ago
#include "mainwindow.h"
#include "addressbook.h"
6 years ago
#include "ui_mainwindow.h"
#include "ui_addressbook.h"
#include "ui_zboard.h"
#include "ui_privkey.h"
6 years ago
#include "ui_about.h"
#include "ui_settings.h"
#include "ui_turnstile.h"
#include "ui_turnstileprogress.h"
6 years ago
#include "rpc.h"
#include "balancestablemodel.h"
#include "settings.h"
#include "version.h"
#include "turnstile.h"
#include "senttxstore.h"
6 years ago
#include "connection.h"
6 years ago
using json = nlohmann::json;
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
logger = new Logger(this, QDir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)).filePath("zec-qt-wallet.log"));
6 years ago
6 years ago
// Status Bar
setupStatusBar();
6 years ago
// Settings editor
setupSettingsModal();
// Set up exit action
QObject::connect(ui->actionExit, &QAction::triggered, this, &MainWindow::close);
// Set up donate action
QObject::connect(ui->actionDonate, &QAction::triggered, this, &MainWindow::donate);
// Set up check for updates action
QObject::connect(ui->actionCheck_for_Updates, &QAction::triggered, [=] () {
QDesktopServices::openUrl(QUrl("https://github.com/adityapk00/zec-qt-wallet/releases"));
});
// Import Private Key
QObject::connect(ui->actionImport_Private_Key, &QAction::triggered, this, &MainWindow::importPrivKey);
// Export All Private Keys
QObject::connect(ui->actionExport_All_Private_Keys, &QAction::triggered, this, &MainWindow::exportAllKeys);
// Backup wallet.dat
QObject::connect(ui->actionBackup_wallet_dat, &QAction::triggered, this, &MainWindow::backupWalletDat);
// z-Board.net
QObject::connect(ui->actionz_board_net, &QAction::triggered, this, &MainWindow::postToZBoard);
// Address Book
QObject::connect(ui->action_Address_Book, &QAction::triggered, this, &MainWindow::addressBook);
// Set up about action
QObject::connect(ui->actionAbout, &QAction::triggered, [=] () {
QDialog aboutDialog(this);
Ui_about about;
6 years ago
about.setupUi(&aboutDialog);
Settings::saveRestore(&aboutDialog);
6 years ago
QString version = QString("Version ") % QString(APP_VERSION) % " (" % QString(__DATE__) % ")";
about.versionLabel->setText(version);
aboutDialog.exec();
});
// Initialize to the balances tab
ui->tabWidget->setCurrentIndex(0);
// The zcashd tab is hidden by default, and only later added in if the embedded zcashd is started
zcashdtab = ui->tabWidget->widget(4);
ui->tabWidget->removeTab(4);
setupSendTab();
setupTransactionsTab();
setupRecieveTab();
setupBalancesTab();
6 years ago
setupTurnstileDialog();
setupZcashdTab();
rpc = new RPC(this);
6 years ago
restoreSavedStates();
}
6 years ago
void MainWindow::restoreSavedStates() {
6 years ago
QSettings s;
restoreGeometry(s.value("geometry").toByteArray());
6 years ago
ui->balancesTable->horizontalHeader()->restoreState(s.value("baltablegeometry").toByteArray());
ui->transactionsTable->horizontalHeader()->restoreState(s.value("tratablegeometry").toByteArray());
}
void MainWindow::closeEvent(QCloseEvent* event) {
6 years ago
QSettings s;
6 years ago
s.setValue("geometry", saveGeometry());
s.setValue("baltablegeometry", ui->balancesTable->horizontalHeader()->saveState());
s.setValue("tratablegeometry", ui->transactionsTable->horizontalHeader()->saveState());
6 years ago
// Let the RPC know to shutdown any running service.
rpc->shutdownZcashd();
6 years ago
// Bubble up
6 years ago
QMainWindow::closeEvent(event);
}
void MainWindow::turnstileProgress() {
Ui_TurnstileProgress progress;
QDialog d(this);
progress.setupUi(&d);
Settings::saveRestore(&d);
QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxWarning);
progress.msgIcon->setPixmap(icon.pixmap(64, 64));
bool migrationFinished = false;
auto fnUpdateProgressUI = [=, &migrationFinished] () mutable {
// Get the plan progress
if (rpc->getTurnstile()->isMigrationPresent()) {
auto curProgress = rpc->getTurnstile()->getPlanProgress();
progress.progressTxt->setText(QString::number(curProgress.step) % QString(" / ") % QString::number(curProgress.totalSteps));
progress.progressBar->setValue(100 * curProgress.step / curProgress.totalSteps);
auto nextTxBlock = curProgress.nextBlock - Settings::getInstance()->getBlockNumber();
6 years ago
progress.fromAddr->setText(curProgress.from);
progress.toAddr->setText(curProgress.to);
if (curProgress.step == curProgress.totalSteps) {
migrationFinished = true;
auto txt = QString("Turnstile migration finished");
if (curProgress.hasErrors) {
txt = txt + ". There were some errors.\n\nYour funds are all in your wallet, so you should be able to finish moving them manually.";
6 years ago
}
progress.nextTx->setText(txt);
} else {
progress.nextTx->setText(QString("Next transaction in ")
% QString::number(nextTxBlock < 0 ? 0 : nextTxBlock)
% " blocks via " % curProgress.via % "\n"
% (nextTxBlock <= 0 ? "(waiting for confirmations)" : ""));
6 years ago
}
} else {
progress.progressTxt->setText("");
progress.progressBar->setValue(0);
progress.nextTx->setText("No turnstile migration is in progress");
}
};
QTimer progressTimer(this);
QObject::connect(&progressTimer, &QTimer::timeout, fnUpdateProgressUI);
6 years ago
progressTimer.start(Settings::updateSpeed);
fnUpdateProgressUI();
auto curProgress = rpc->getTurnstile()->getPlanProgress();
// Abort button
if (curProgress.step != curProgress.totalSteps)
progress.buttonBox->button(QDialogButtonBox::Discard)->setText("Abort");
else
progress.buttonBox->button(QDialogButtonBox::Discard)->setVisible(false);
QObject::connect(progress.buttonBox->button(QDialogButtonBox::Discard), &QPushButton::clicked, [&] () {
if (curProgress.step != curProgress.totalSteps) {
auto abort = QMessageBox::warning(this, "Are you sure you want to Abort?",
"Are you sure you want to abort the migration?\nAll further transactions will be cancelled.\nAll your funds are still in your wallet.",
QMessageBox::Yes, QMessageBox::No);
if (abort == QMessageBox::Yes) {
rpc->getTurnstile()->removeFile();
d.close();
ui->statusBar->showMessage("Automatic Sapling turnstile migration aborted.");
}
}
});
6 years ago
d.exec();
if (migrationFinished || curProgress.step == curProgress.totalSteps) {
// Finished, so delete the file
rpc->getTurnstile()->removeFile();
}
}
6 years ago
void MainWindow::turnstileDoMigration(QString fromAddr) {
6 years ago
// Return if there is no connection
if (rpc->getAllZAddresses() == nullptr)
return;
// If a migration is already in progress, show the progress dialog instead
if (rpc->getTurnstile()->isMigrationPresent()) {
turnstileProgress();
return;
}
Ui_Turnstile turnstile;
QDialog d(this);
turnstile.setupUi(&d);
Settings::saveRestore(&d);
6 years ago
QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxInformation);
turnstile.msgIcon->setPixmap(icon.pixmap(64, 64));
auto fnGetAllSproutBalance = [=] () {
double bal = 0;
6 years ago
for (auto addr : *rpc->getAllZAddresses()) {
if (Settings::getInstance()->isSproutAddress(addr)) {
bal += rpc->getAllBalances()->value(addr);
6 years ago
}
}
return bal;
};
//turnstile.migrateZaddList->addItem("All Sprout z-Addrs");
turnstile.fromBalance->setText(Settings::getZECUSDDisplayFormat(fnGetAllSproutBalance()));
for (auto addr : *rpc->getAllZAddresses()) {
if (Settings::getInstance()->isSaplingAddress(addr)) {
turnstile.migrateTo->addItem(addr);
} else {
turnstile.migrateZaddList->addItem(addr);
6 years ago
}
}
auto fnUpdateSproutBalance = [=] (QString addr) {
double bal = 0;
if (addr.startsWith("All")) {
bal = fnGetAllSproutBalance();
} else {
bal = rpc->getAllBalances()->value(addr);
}
auto balTxt = Settings::getZECUSDDisplayFormat(bal);
if (bal < Turnstile::minMigrationAmount) {
turnstile.fromBalance->setStyleSheet("color: red;");
turnstile.fromBalance->setText(balTxt % " [You need at least "
% Settings::getZECDisplayFormat(Turnstile::minMigrationAmount)
% " for automatic migration]");
turnstile.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
} else {
turnstile.fromBalance->setStyleSheet("");
turnstile.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
turnstile.fromBalance->setText(balTxt);
}
};
if (!fromAddr.isEmpty())
turnstile.migrateZaddList->setCurrentText(fromAddr);
fnUpdateSproutBalance(turnstile.migrateZaddList->currentText());
// Combo box selection event
QObject::connect(turnstile.migrateZaddList, &QComboBox::currentTextChanged, fnUpdateSproutBalance);
// Privacy level combobox
// Num tx over num blocks
QList<std::tuple<int, int>> privOptions;
privOptions.push_back(std::make_tuple<int, int>(3, 576));
privOptions.push_back(std::make_tuple<int, int>(5, 1152));
privOptions.push_back(std::make_tuple<int, int>(10, 2304));
QObject::connect(turnstile.privLevel, QOverload<int>::of(&QComboBox::currentIndexChanged), [=] (auto idx) {
// Update the fees
turnstile.minerFee->setText(
Settings::getZECUSDDisplayFormat(std::get<0>(privOptions[idx]) * Settings::getMinerFee()));
6 years ago
});
for (auto i : privOptions) {
turnstile.privLevel->addItem(QString::number((int)(std::get<1>(i) / 24 / 24)) % " days (" % // 24 blks/hr * 24 hrs per day
QString::number(std::get<1>(i)) % " blocks, ~" %
QString::number(std::get<0>(i)) % " txns)"
);
}
turnstile.buttonBox->button(QDialogButtonBox::Ok)->setText("Start");
if (d.exec() == QDialog::Accepted) {
auto privLevel = privOptions[turnstile.privLevel->currentIndex()];
rpc->getTurnstile()->planMigration(
turnstile.migrateZaddList->currentText(),
turnstile.migrateTo->currentText(),
std::get<0>(privLevel), std::get<1>(privLevel));
QMessageBox::information(this, "Backup your wallet.dat",
"The migration will now start. You can check progress in the File -> Sapling Turnstile menu.\n\nYOU MUST BACKUP YOUR wallet.dat NOW!\n\nNew Addresses have been added to your wallet which will be used for the migration.",
QMessageBox::Ok);
}
}
void MainWindow::setupTurnstileDialog() {
// Turnstile migration
QObject::connect(ui->actionTurnstile_Migration, &QAction::triggered, [=] () {
// If there is current migration that is present, show the progress button
if (rpc->getTurnstile()->isMigrationPresent())
turnstileProgress();
else
turnstileDoMigration();
6 years ago
});
6 years ago
}
void MainWindow::setupStatusBar() {
6 years ago
// Status Bar
loadingLabel = new QLabel();
loadingMovie = new QMovie(":/icons/res/loading.gif");
loadingMovie->setScaledSize(QSize(32, 16));
loadingMovie->start();
loadingLabel->setAttribute(Qt::WA_NoSystemBackground);
loadingLabel->setMovie(loadingMovie);
ui->statusBar->addPermanentWidget(loadingLabel);
loadingLabel->setVisible(false);
// Custom status bar menu
ui->statusBar->setContextMenuPolicy(Qt::CustomContextMenu);
QObject::connect(ui->statusBar, &QStatusBar::customContextMenuRequested, [=](QPoint pos) {
auto msg = ui->statusBar->currentMessage();
QMenu menu(this);
6 years ago
if (!msg.isEmpty() && msg.startsWith(Settings::txidStatusMessage)) {
6 years ago
auto txid = msg.split(":")[1].trimmed();
menu.addAction("Copy txid", [=]() {
QGuiApplication::clipboard()->setText(txid);
});
menu.addAction("View tx on block explorer", [=]() {
QString url;
if (Settings::getInstance()->isTestnet()) {
url = "https://explorer.testnet.z.cash/tx/" + txid;
}
else {
url = "https://explorer.zcha.in/transactions/" + txid;
}
QDesktopServices::openUrl(QUrl(url));
});
}
menu.addAction("Refresh", [=]() {
rpc->refresh(true);
});
QPoint gpos(mapToGlobal(pos).x(), mapToGlobal(pos).y() + this->height() - ui->statusBar->height());
menu.exec(gpos);
});
statusLabel = new QLabel();
ui->statusBar->addPermanentWidget(statusLabel);
statusIcon = new QLabel();
ui->statusBar->addPermanentWidget(statusIcon);
}
6 years ago
void MainWindow::setupSettingsModal() {
// Set up File -> Settings action
QObject::connect(ui->actionSettings, &QAction::triggered, [=]() {
QDialog settingsDialog(this);
Ui_Settings settings;
settings.setupUi(&settingsDialog);
Settings::saveRestore(&settingsDialog);
6 years ago
// Setup save sent check box
QObject::connect(settings.chkSaveTxs, &QCheckBox::stateChanged, [=](auto checked) {
Settings::getInstance()->setSaveZtxs(checked);
});
// Setup clear button
QObject::connect(settings.btnClearSaved, &QCheckBox::clicked, [=]() {
if (QMessageBox::warning(this, "Clear saved history?",
"Shielded z-Address transactions are stored locally in your wallet, outside zcashd. You may delete this saved information safely any time for your privacy.\nDo you want to delete the saved shielded transactions now?",
6 years ago
QMessageBox::Yes, QMessageBox::Cancel)) {
SentTxStore::deleteHistory();
// Reload after the clear button so existing txs disappear
rpc->refresh(true);
}
});
// Save sent transactions
settings.chkSaveTxs->setChecked(Settings::getInstance()->getSaveZtxs());
// Custom fees
settings.chkCustomFees->setChecked(Settings::getInstance()->getAllowCustomFees());
6 years ago
// Connection Settings
QIntValidator validator(0, 65535);
settings.port->setValidator(&validator);
// If values are coming from zcash.conf, then disable all the fields
auto zcashConfLocation = Settings::getInstance()->getZcashdConfLocation();
if (!zcashConfLocation.isEmpty()) {
settings.confMsg->setText("Settings are being read from \n" + zcashConfLocation);
settings.hostname->setEnabled(false);
settings.port->setEnabled(false);
settings.rpcuser->setEnabled(false);
settings.rpcpassword->setEnabled(false);
}
else {
// Load current values into the dialog
auto conf = Settings::getInstance()->getSettings();
settings.hostname->setText(conf.host);
settings.port->setText(conf.port);
settings.rpcuser->setText(conf.rpcuser);
settings.rpcpassword->setText(conf.rpcpassword);
6 years ago
settings.confMsg->setText("No local zcash.conf found. Please configure connection manually.");
settings.hostname->setEnabled(true);
settings.port->setEnabled(true);
settings.rpcuser->setEnabled(true);
settings.rpcpassword->setEnabled(true);
}
// Connection tab by default
settings.tabWidget->setCurrentIndex(0);
if (settingsDialog.exec() == QDialog::Accepted) {
// Custom fees
bool customFees = settings.chkCustomFees->isChecked();
Settings::getInstance()->setAllowCustomFees(customFees);
ui->minerFeeAmt->setReadOnly(!customFees);
if (!customFees)
ui->minerFeeAmt->setText(Settings::getDecimalString(Settings::getMinerFee()));
6 years ago
if (zcashConfLocation.isEmpty()) {
// Save settings
Settings::getInstance()->saveSettings(
settings.hostname->text(),
settings.port->text(),
settings.rpcuser->text(),
settings.rpcpassword->text());
auto cl = new ConnectionLoader(this, rpc);
cl->loadConnection();
}
}
6 years ago
});
}
void MainWindow::addressBook() {
// Check to see if there is a target.
QRegExp re("Address[0-9]+", Qt::CaseInsensitive);
for (auto target: ui->sendToWidgets->findChildren<QLineEdit *>(re)) {
if (target->hasFocus()) {
AddressBook::open(this, target);
return;
}
};
// If there was no target, then just run with no target.
AddressBook::open(this);
6 years ago
}
void MainWindow::donate() {
// Set up a donation to me :)
6 years ago
ui->Address1->setText(Settings::getDonationAddr(
6 years ago
Settings::getInstance()->isSaplingAddress(ui->inputsCombo->currentText())));
ui->Address1->setCursorPosition(0);
ui->Amount1->setText("0.01");
ui->MemoTxt1->setText("Thanks for supporting zec-qt-wallet!");
6 years ago
ui->statusBar->showMessage("Donate 0.01 " % Settings::getTokenName() % " to support zec-qt-wallet");
// And switch to the send tab.
ui->tabWidget->setCurrentIndex(1);
}
void MainWindow::postToZBoard() {
QDialog d(this);
Ui_zboard zb;
zb.setupUi(&d);
Settings::saveRestore(&d);
if (rpc->getConnection() == nullptr)
return;
// Fill the from field with sapling addresses.
for (auto i = rpc->getAllBalances()->keyBegin(); i != rpc->getAllBalances()->keyEnd(); i++) {
if (Settings::getInstance()->isSaplingAddress(*i) && rpc->getAllBalances()->value(*i) > 0) {
zb.fromAddr->addItem(*i);
}
}
QMap<QString, QString> topics;
// Insert the main topic automatically
topics.insert("#Main_Area", Settings::getInstance()->isTestnet() ? Settings::getDonationAddr(true) : Settings::getZboardAddr());
zb.topicsList->addItem(topics.firstKey());
// Then call the API to get topics, and if it returns successfully, then add the rest of the topics
rpc->getZboardTopics([&](QMap<QString, QString> topicsMap) {
for (auto t : topicsMap.keys()) {
topics.insert(t, Settings::getInstance()->isTestnet() ? Settings::getDonationAddr(true) : topicsMap[t]);
zb.topicsList->addItem(t);
}
});
// Testnet warning
if (Settings::getInstance()->isTestnet()) {
zb.testnetWarning->setText("You are on testnet, your post won't actually appear on z-board.net");
}
else {
zb.testnetWarning->setText("");
}
QRegExpValidator v(QRegExp("^[a-zA-Z0-9_]{3,20}$"), zb.postAs);
zb.postAs->setValidator(&v);
zb.feeAmount->setText(Settings::getZECUSDDisplayFormat(Settings::getZboardAmount() + Settings::getMinerFee()));
6 years ago
auto fnBuildNameMemo = [=]() -> QString {
auto memo = zb.memoTxt->toPlainText().trimmed();
if (!zb.postAs->text().trimmed().isEmpty())
memo = zb.postAs->text().trimmed() + ":: " + memo;
return memo;
};
auto fnUpdateMemoSize = [=]() {
QString txt = fnBuildNameMemo();
6 years ago
zb.memoSize->setText(QString::number(txt.toUtf8().size()) + "/512");
if (txt.toUtf8().size() <= 512) {
// Everything is fine
zb.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
zb.memoSize->setStyleSheet("");
}
else {
// Overweight
6 years ago
zb.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
zb.memoSize->setStyleSheet("color: red;");
}
// Disallow blank memos
if (zb.memoTxt->toPlainText().trimmed().isEmpty()) {
zb.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
}
else {
zb.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
}
};
// Memo text changed
QObject::connect(zb.memoTxt, &QPlainTextEdit::textChanged, fnUpdateMemoSize);
QObject::connect(zb.postAs, &QLineEdit::textChanged, fnUpdateMemoSize);
6 years ago
zb.memoTxt->setFocus();
fnUpdateMemoSize();
if (d.exec() == QDialog::Accepted) {
// Create a transaction.
Tx tx;
// Send from your first sapling address that has a balance.
tx.fromAddr = zb.fromAddr->currentText();
if (tx.fromAddr.isEmpty()) {
QMessageBox::critical(this, "Error Posting Message", "You need a sapling address with available balance to post", QMessageBox::Ok);
return;
}
auto memo = zb.memoTxt->toPlainText().trimmed();
if (!zb.postAs->text().trimmed().isEmpty())
memo = zb.postAs->text().trimmed() + ":: " + memo;
auto toAddr = topics[zb.topicsList->currentText()];
6 years ago
tx.toAddrs.push_back(ToFields{ toAddr, Settings::getZboardAmount(), memo, memo.toUtf8().toHex() });
tx.fee = Settings::getMinerFee();
json params = json::array();
rpc->fillTxJsonParams(params, tx);
std::cout << std::setw(2) << params << std::endl;
// And send the Tx
rpc->sendZTransaction(params, [=](const json& reply) {
QString opid = QString::fromStdString(reply.get<json::string_t>());
ui->statusBar->showMessage("Computing Tx: " % opid);
// And then start monitoring the transaction
rpc->addNewTxToWatch(tx, opid);
});
}
}
void MainWindow::doImport(QList<QString>* keys) {
if (rpc->getConnection() == nullptr) {
// No connection, just return
return;
}
if (keys->isEmpty()) {
delete keys;
QMessageBox::information(this,
"Imported", "The keys were imported. It may take several minutes to rescan the blockchain. Until then, functionality may be limited",
QMessageBox::Ok);
ui->statusBar->showMessage("Private key import rescan finished");
return;
}
// Pop the first key
QString key = keys->first();
keys->pop_front();
bool rescan = keys->isEmpty();
if (key.startsWith("S") ||
key.startsWith("secret")) { // Z key
rpc->importZPrivKey(key, rescan, [=] (auto) { this->doImport(keys); });
} else {
rpc->importTPrivKey(key, rescan, [=] (auto) { this->doImport(keys); });
}
}
void MainWindow::importPrivKey() {
QDialog d(this);
Ui_PrivKey pui;
pui.setupUi(&d);
Settings::saveRestore(&d);
pui.buttonBox->button(QDialogButtonBox::Save)->setVisible(false);
pui.helpLbl->setText(QString() %
"Please paste your private keys (z-Addr or t-Addr) here, one per line.\n" %
"The keys will be imported into your connected zcashd node");
if (d.exec() == QDialog::Accepted && !pui.privKeyTxt->toPlainText().trimmed().isEmpty()) {
auto rawkeys = pui.privKeyTxt->toPlainText().trimmed().split("\n");
QList<QString> keysTmp;
// Filter out all the empty keys.
std::copy_if(rawkeys.begin(), rawkeys.end(), std::back_inserter(keysTmp), [=] (auto key) {
return !key.startsWith("#") && !key.trimmed().isEmpty();
});
auto keys = new QList<QString>();
std::transform(keysTmp.begin(), keysTmp.end(), std::back_inserter(*keys), [=](auto key) {
return key.trimmed().split(" ")[0];
});
6 years ago
// Start the import. The function takes ownership of keys
doImport(keys);
}
}
/**
* Backup the wallet.dat file. This is kind of a hack, since it has to read from the filesystem rather than an RPC call
* This might fail for various reasons - Remote zcashd, non-standard locations, custom params passed to zcashd, many others
*/
void MainWindow::backupWalletDat() {
if (!rpc->getConnection())
return;
QDir zcashdir(rpc->getConnection()->config->zcashDir);
QString backupDefaultName = "zcash-wallet-backup-" + QDateTime::currentDateTime().toString("yyyyMMdd") + ".dat";
if (Settings::getInstance()->isTestnet()) {
zcashdir.cd("testnet3");
backupDefaultName = "tesetnet-" + backupDefaultName;
}
QFile wallet(zcashdir.filePath("wallet.dat"));
if (!wallet.exists()) {
QMessageBox::critical(this, "No wallet.dat", "Couldn't find the wallet.dat on this computer."
6 years ago
"You need to back it up from the machine zcashd is running on", QMessageBox::Ok);
return;
}
QUrl backupName = QFileDialog::getSaveFileUrl(this, "Backup wallet.dat", backupDefaultName, "Data file (*.dat)");
if (backupName.isEmpty())
return;
if (!wallet.copy(backupName.toLocalFile())) {
QMessageBox::critical(this, "Couldn't backup", "Couldn't backup the wallet.dat file."
6 years ago
"You need to back it up manually.", QMessageBox::Ok);
}
}
void MainWindow::exportAllKeys() {
QDialog d(this);
Ui_PrivKey pui;
pui.setupUi(&d);
// Make the window big by default
auto ps = this->geometry();
QMargins margin = QMargins() + 50;
d.setGeometry(ps.marginsRemoved(margin));
Settings::saveRestore(&d);
pui.privKeyTxt->setPlainText("Loading...");
pui.privKeyTxt->setReadOnly(true);
pui.privKeyTxt->setLineWrapMode(QPlainTextEdit::LineWrapMode::NoWrap);
pui.helpLbl->setText("These are all the private keys for all the addresses in your wallet");
// Disable the save button until it finishes loading
pui.buttonBox->button(QDialogButtonBox::Save)->setEnabled(false);
pui.buttonBox->button(QDialogButtonBox::Ok)->setVisible(false);
// Wire up save button
QObject::connect(pui.buttonBox->button(QDialogButtonBox::Save), &QPushButton::clicked, [=] () {
QString fileName = QFileDialog::getSaveFileName(this, tr("Save File"),
"zcash-all-privatekeys.txt");
QFile file(fileName);
if (!file.open(QIODevice::WriteOnly)) {
QMessageBox::information(this, tr("Unable to open file"), file.errorString());
return;
6 years ago
}
QTextStream out(&file);
out << pui.privKeyTxt->toPlainText();
});
// Call the API
auto isDialogAlive = std::make_shared<bool>(true);
rpc->getAllPrivKeys([=] (auto privKeys) {
// Check to see if we are still showing.
if (! *isDialogAlive.get()) return;
QString allKeysTxt;
for (auto keypair : privKeys) {
allKeysTxt = allKeysTxt % keypair.second % " # addr=" % keypair.first % "\n";
}
pui.privKeyTxt->setPlainText(allKeysTxt);
pui.buttonBox->button(QDialogButtonBox::Save)->setEnabled(true);
});
d.exec();
*isDialogAlive.get() = false;
}
6 years ago
void MainWindow::setupBalancesTab() {
ui->unconfirmedWarning->setVisible(false);
// Double click on balances table
auto fnDoSendFrom = [=](const QString& addr, const QString& to = QString(), bool sendMax = false) {
// Find the inputs combo
for (int i = 0; i < ui->inputsCombo->count(); i++) {
if (ui->inputsCombo->itemText(i).startsWith(addr)) {
ui->inputsCombo->setCurrentIndex(i);
break;
}
}
// If there's a to address, add that as well
if (!to.isEmpty()) {
// Remember to clear any existing address fields, because we are creating a new transaction.
this->removeExtraAddresses();
ui->Address1->setText(to);
}
// See if max button has to be checked
if (sendMax) {
ui->Max1->setChecked(true);
}
// And switch to the send tab.
ui->tabWidget->setCurrentIndex(1);
};
// Double click opens up memo if one exists
QObject::connect(ui->balancesTable, &QTableView::doubleClicked, [=](auto index) {
index = index.sibling(index.row(), 0);
auto addr = ui->balancesTable->model()->data(index).toString();
fnDoSendFrom(addr);
});
6 years ago
// Setup context menu on balances tab
ui->balancesTable->setContextMenuPolicy(Qt::CustomContextMenu);
QObject::connect(ui->balancesTable, &QTableView::customContextMenuRequested, [=] (QPoint pos) {
QModelIndex index = ui->balancesTable->indexAt(pos);
if (index.row() < 0) return;
index = index.sibling(index.row(), 0);
auto addr = ui->balancesTable->model()->data(index).toString();
6 years ago
QMenu menu(this);
menu.addAction("Copy address", [=] () {
6 years ago
QClipboard *clipboard = QGuiApplication::clipboard();
clipboard->setText(addr);
ui->statusBar->showMessage("Copied to clipboard", 3 * 1000);
6 years ago
});
menu.addAction("Get private key", [=] () {
auto fnCB = [=] (const json& reply) {
auto privKey = QString::fromStdString(reply.get<json::string_t>());
QDialog d(this);
Ui_PrivKey pui;
pui.setupUi(&d);
pui.helpLbl->setText("Private Key:");
pui.privKeyTxt->setPlainText(privKey);
pui.privKeyTxt->setReadOnly(true);
pui.privKeyTxt->selectAll();
pui.buttonBox->button(QDialogButtonBox::Save)->setVisible(false);
pui.buttonBox->button(QDialogButtonBox::Ok)->setVisible(false);
d.exec();
};
if (Settings::getInstance()->isZAddress(addr))
rpc->getZPrivKey(addr, fnCB);
else
rpc->getTPrivKey(addr, fnCB);
});
6 years ago
menu.addAction("Send from " % addr.left(40) % (addr.size() > 40 ? "..." : ""), [=]() {
fnDoSendFrom(addr);
6 years ago
});
if (addr.startsWith("t")) {
auto defaultSapling = rpc->getDefaultSaplingAddress();
if (!defaultSapling.isEmpty()) {
menu.addAction("Shield balance to Sapling", [=] () {
fnDoSendFrom(addr, defaultSapling, true);
});
}
menu.addAction("View on block explorer", [=] () {
QString url;
if (Settings::getInstance()->isTestnet()) {
url = "https://explorer.testnet.z.cash/address/" + addr;
} else {
url = "https://explorer.zcha.in/accounts/" + addr;
}
QDesktopServices::openUrl(QUrl(url));
});
}
if (Settings::getInstance()->isSproutAddress(addr)) {
menu.addAction("Migrate to Sapling", [=] () {
this->turnstileDoMigration(addr);
});
}
6 years ago
menu.exec(ui->balancesTable->viewport()->mapToGlobal(pos));
});
}
void MainWindow::setupZcashdTab() {
ui->zcashdlogo->setBasePixmap(QPixmap(":/img/res/zcashdlogo.gif"));
}
6 years ago
void MainWindow::setupTransactionsTab() {
// Double click opens up memo if one exists
QObject::connect(ui->transactionsTable, &QTableView::doubleClicked, [=] (auto index) {
auto txModel = dynamic_cast<TxTableModel *>(ui->transactionsTable->model());
QString memo = txModel->getMemo(index.row());
if (!memo.isEmpty()) {
QMessageBox::information(this, "Memo", memo, QMessageBox::Ok);
}
});
6 years ago
// Set up context menu on transactions tab
ui->transactionsTable->setContextMenuPolicy(Qt::CustomContextMenu);
// Table right click
6 years ago
QObject::connect(ui->transactionsTable, &QTableView::customContextMenuRequested, [=] (QPoint pos) {
QModelIndex index = ui->transactionsTable->indexAt(pos);
if (index.row() < 0) return;
QMenu menu(this);
auto txModel = dynamic_cast<TxTableModel *>(ui->transactionsTable->model());
QString txid = txModel->getTxId(index.row());
QString memo = txModel->getMemo(index.row());
QString addr = txModel->getAddr(index.row());
6 years ago
menu.addAction("Copy txid", [=] () {
QGuiApplication::clipboard()->setText(txid);
ui->statusBar->showMessage("Copied to clipboard", 3 * 1000);
});
if (!addr.isEmpty()) {
6 years ago
menu.addAction("Copy address", [=] () {
QGuiApplication::clipboard()->setText(addr);
ui->statusBar->showMessage("Copied to clipboard", 3 * 1000);
});
}
menu.addAction("View on block explorer", [=] () {
QString url;
if (Settings::getInstance()->isTestnet()) {
url = "https://explorer.testnet.z.cash/tx/" + txid;
} else {
url = "https://explorer.zcha.in/transactions/" + txid;
}
QDesktopServices::openUrl(QUrl(url));
6 years ago
});
if (!memo.isEmpty()) {
menu.addAction("View Memo", [=] () {
QMessageBox::information(this, "Memo", memo, QMessageBox::Ok);
});
}
6 years ago
menu.exec(ui->transactionsTable->viewport()->mapToGlobal(pos));
});
}
void MainWindow::addNewZaddr(bool sapling) {
rpc->newZaddr(sapling, [=] (json reply) {
QString addr = QString::fromStdString(reply.get<json::string_t>());
// Make sure the RPC class reloads the z-addrs for future use
rpc->refreshAddresses();
// Just double make sure the z-address is still checked
if (( sapling && ui->rdioZSAddr->isChecked()) ||
(!sapling && ui->rdioZAddr->isChecked())) {
ui->listRecieveAddresses->insertItem(0, addr);
ui->listRecieveAddresses->setCurrentIndex(0);
ui->statusBar->showMessage(QString::fromStdString("Created new zAddr") %
(sapling ? "(Sapling)" : "(Sprout)"),
10 * 1000);
}
});
}
// Adds sapling or sprout z-addresses to the combo box. Technically, returns a
// lambda, which can be connected to the appropriate signal
std::function<void(bool)> MainWindow::addZAddrsToComboList(bool sapling) {
return [=] (bool checked) {
if (checked && this->rpc->getAllZAddresses() != nullptr) {
auto addrs = this->rpc->getAllZAddresses();
ui->listRecieveAddresses->clear();
std::for_each(addrs->begin(), addrs->end(), [=] (auto addr) {
if ( (sapling && Settings::getInstance()->isSaplingAddress(addr)) ||
(!sapling && !Settings::getInstance()->isSaplingAddress(addr)))
ui->listRecieveAddresses->addItem(addr);
});
// If z-addrs are empty, then create a new one.
if (addrs->isEmpty()) {
addNewZaddr(sapling);
}
}
};
}
6 years ago
void MainWindow::setupRecieveTab() {
auto addNewTAddr = [=] () {
rpc->newTaddr([=] (json reply) {
QString addr = QString::fromStdString(reply.get<json::string_t>());
// Just double make sure the t-address is still checked
6 years ago
if (ui->rdioTAddr->isChecked()) {
ui->listRecieveAddresses->insertItem(0, addr);
ui->listRecieveAddresses->setCurrentIndex(0);
ui->statusBar->showMessage("Created new t-Addr", 10 * 1000);
6 years ago
}
});
};
// Connect t-addr radio button
QObject::connect(ui->rdioTAddr, &QRadioButton::toggled, [=] (bool checked) {
// Whenever the t-address is selected, we generate a new address, because we don't
// want to reuse t-addrs
6 years ago
if (checked && this->rpc->getUTXOs() != nullptr) {
auto utxos = this->rpc->getUTXOs();
ui->listRecieveAddresses->clear();
std::for_each(utxos->begin(), utxos->end(), [=] (auto& utxo) {
auto addr = utxo.address;
if (addr.startsWith("t") && ui->listRecieveAddresses->findText(addr) < 0) {
ui->listRecieveAddresses->addItem(addr);
}
});
addNewTAddr();
}
});
// zAddr toggle button, one for sprout and one for sapling
QObject::connect(ui->rdioZAddr, &QRadioButton::toggled, addZAddrsToComboList(false));
QObject::connect(ui->rdioZSAddr, &QRadioButton::toggled, addZAddrsToComboList(true));
6 years ago
// Explicitly get new address button.
QObject::connect(ui->btnRecieveNewAddr, &QPushButton::clicked, [=] () {
if (!rpc->getConnection())
return;
6 years ago
if (ui->rdioZAddr->isChecked()) {
QString syncMsg = !Settings::getInstance()->isSaplingActive() ? "Please wait for your node to finish syncing to create Sapling addresses.\n\n" : "";
auto confirm = QMessageBox::question(this, "Sprout Address",
syncMsg + "Sprout addresses are inefficient, and will be deprecated in the future in favour of Sapling addresses.\n"
"Are you sure you want to create a new Sprout address?", QMessageBox::Yes, QMessageBox::No);
if (confirm != QMessageBox::Yes)
return;
addNewZaddr(false);
} else if (ui->rdioZSAddr->isChecked()) {
addNewZaddr(true);
6 years ago
} else if (ui->rdioTAddr->isChecked()) {
addNewTAddr();
}
});
// Focus enter for the Receive Tab
6 years ago
QObject::connect(ui->tabWidget, &QTabWidget::currentChanged, [=] (int tab) {
if (tab == 2) {
// Switched to receive tab, so update everything.
6 years ago
// Hide Sapling radio button if Sapling is not active
if (Settings::getInstance()->isSaplingActive()) {
ui->rdioZSAddr->setVisible(true);
ui->rdioZSAddr->setChecked(true);
ui->rdioZAddr->setText("z-Addr(Sprout)");
} else {
ui->rdioZSAddr->setVisible(false);
ui->rdioZAddr->setChecked(true);
ui->rdioZAddr->setText("z-Addr"); // Don't use the "Sprout" label if there's no Sapling
}
6 years ago
// And then select the first one
ui->listRecieveAddresses->setCurrentIndex(0);
}
});
// Select item in address list
6 years ago
QObject::connect(ui->listRecieveAddresses,
QOverload<const QString &>::of(&QComboBox::currentIndexChanged), [=] (const QString& addr) {
if (addr.isEmpty()) {
// Draw empty stuff
ui->txtRecieve->clear();
ui->qrcodeDisplay->clear();
return;
}
ui->txtRecieve->setPlainText(addr);
ui->qrcodeDisplay->setAddress(addr);
6 years ago
});
}
MainWindow::~MainWindow()
{
delete ui;
delete rpc;
delete labelCompleter;
delete loadingMovie;
delete logger;
6 years ago
}