diff --git a/Silentdragonlite b/Silentdragonlite index cabaf68..f5cbaa5 100755 Binary files a/Silentdragonlite and b/Silentdragonlite differ diff --git a/Silentdragonlite_resource.rc b/Silentdragonlite_resource.rc new file mode 100644 index 0000000..188c3ed --- /dev/null +++ b/Silentdragonlite_resource.rc @@ -0,0 +1,37 @@ +#include + +IDI_ICON1 ICON DISCARDABLE "/home/denio/SilentDragonLite/res/icon.ico" + +VS_VERSION_INFO VERSIONINFO + FILEVERSION 0,0,0,0 + PRODUCTVERSION 0,0,0,0 + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L + BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "\0" + VALUE "FileDescription", "\0" + VALUE "FileVersion", "0.0.0.0\0" + VALUE "LegalCopyright", "\0" + VALUE "OriginalFilename", "Silentdragonlite.exe\0" + VALUE "ProductName", "Silentdragonlite\0" + VALUE "ProductVersion", "0.0.0.0\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0409, 1200 + END + END +/* End of Version info */ + diff --git a/src/connection.cpp b/src/connection.cpp index 87f853f..7e90358 100644 --- a/src/connection.cpp +++ b/src/connection.cpp @@ -174,7 +174,7 @@ void Executor::run() { QString reply = litelib_process_response(resp); - qDebug() << "RPC Reply=" << reply; + //qDebug() << "RPC Reply=" << reply; auto parsed = json::parse(reply.toStdString().c_str(), nullptr, false); if (parsed.is_discarded() || parsed.is_null()) { emit handleError(reply); @@ -213,7 +213,7 @@ void Connection::doRPC(const QString cmd, const QString args, const std::functio return; } - qDebug() << "Doing RPC: " << cmd; + //qDebug() << "Doing RPC: " << cmd; // Create a runner. auto runner = new Executor(cmd, args); diff --git a/src/controller.cpp b/src/controller.cpp index 3c0dff6..69f1102 100644 --- a/src/controller.cpp +++ b/src/controller.cpp @@ -473,6 +473,9 @@ void Controller::unlockIfEncrypted(std::function cb, std::functionunlockWallet(password, [=](json reply) { if (isJsonSuccess(reply)) { cb(); + + // Refresh the wallet so the encryption status is now in sync. + refresh(true); } else { QMessageBox::critical(main, main->tr("Wallet Decryption Failed"), QString::fromStdString(reply["error"].get()), @@ -539,7 +542,7 @@ void Controller::checkForUpdate(bool silent) { if (!zrpc->haveConnection()) return noConnection(); - QUrl cmcURL("https://api.github.com/repos/MyHush/SilentDragonLite/releases"); + QUrl cmcURL("https://api.github.com/repos/DenioD/SilentDragonLite/releases"); QNetworkRequest req; req.setUrl(cmcURL); @@ -587,7 +590,7 @@ void Controller::checkForUpdate(bool silent) { .arg(currentVersion.toString()), QMessageBox::Yes, QMessageBox::Cancel); if (ans == QMessageBox::Yes) { - QDesktopServices::openUrl(QUrl("https://github.com/MyHush/SilentDragonLite/releases")); + QDesktopServices::openUrl(QUrl("https://github.com/DenioD/SilentDragonLite/releases")); } else { // If the user selects cancel, don't bother them again for this version s.setValue("update/lastversion", maxVersion.toString()); diff --git a/src/controller.h b/src/controller.h index 39b2233..8dd9ff9 100644 --- a/src/controller.h +++ b/src/controller.h @@ -80,6 +80,7 @@ public: cb({ {"error", "Failed to unlock wallet"} }); }); } + void fetchAllPrivKeys(const std::function cb) { unlockIfEncrypted([=] () { zrpc->fetchAllPrivKeys(cb); @@ -89,6 +90,15 @@ public: }); } + void fetchSeed(const std::function cb) { + unlockIfEncrypted([=] () { + zrpc->fetchSeed(cb); + }, + [=]() { + cb({ {"error", "Failed to unlock wallet"} }); + }); + } + // void importZPrivKey(QString addr, bool rescan, const std::function& cb) { zrpc->importZPrivKey(addr, rescan, cb); } // void importTPrivKey(QString addr, bool rescan, const std::function& cb) { zrpc->importTPrivKey(addr, rescan, cb); } diff --git a/src/liteinterface.cpp b/src/liteinterface.cpp index 1dfc874..82f2dd1 100644 --- a/src/liteinterface.cpp +++ b/src/liteinterface.cpp @@ -56,6 +56,13 @@ void LiteInterface::fetchPrivKey(QString addr, const std::function& conn->doRPCWithDefaultErrorHandling("export", addr, cb); } +void LiteInterface::fetchSeed(const std::function& cb) { + if (conn == nullptr) + return; + + conn->doRPCWithDefaultErrorHandling("seed", "", cb); +} + void LiteInterface::fetchBalance(const std::function& cb) { if (conn == nullptr) return; diff --git a/src/liteinterface.h b/src/liteinterface.h index f318f8b..33c3446 100644 --- a/src/liteinterface.h +++ b/src/liteinterface.h @@ -53,6 +53,7 @@ public: void fetchPrivKey(QString addr, const std::function& cb); void fetchAllPrivKeys(const std::function); + void fetchSeed(const std::function&); void saveWallet(const std::function& cb); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index db11a78..3e7bc02 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -58,7 +58,7 @@ MainWindow::MainWindow(QWidget *parent) : // File a bug QObject::connect(ui->actionFile_a_bug, &QAction::triggered, [=]() { - QDesktopServices::openUrl(QUrl("https://github.com/MyHush/silentdragonlite/issues/new")); + QDesktopServices::openUrl(QUrl("https://github.com/DenioD/SilentDragonLite/issues/new")); }); // Set up check for updates action @@ -95,7 +95,7 @@ MainWindow::MainWindow(QWidget *parent) : 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); + QObject::connect(ui->actionExport_Seed, &QAction::triggered, this, &MainWindow::exportSeed); // Export transactions QObject::connect(ui->actionExport_transactions, &QAction::triggered, this, &MainWindow::exportTransactions); @@ -279,6 +279,9 @@ void MainWindow::encryptWallet() { fnShowError(tr("Wallet Encryption Failed"), reply); } }); + + // And then refresh the UI + rpc->refresh(true); } else { fnShowError(tr("Wallet Encryption Failed"), res); } @@ -297,8 +300,22 @@ void MainWindow::removeWalletEncryption() { return; } + bool ok; QString password = QInputDialog::getText(this, tr("Wallet Password"), - tr("Please enter your wallet password"), QLineEdit::Password); + tr("Please enter your wallet password"), QLineEdit::Password, "", &ok); + + // If cancel was pressed, just return + if (!ok) { + return; + } + + if (password.isEmpty()) { + QMessageBox::critical(this, tr("Wallet Decryption Failed"), + tr("Please enter a password to decrypt your wallet!"), + QMessageBox::Ok + ); + return; + } rpc->removeWalletEncryption(password, [=] (json res) { if (isJsonSuccess(res)) { @@ -316,6 +333,9 @@ void MainWindow::removeWalletEncryption() { ); } }); + + // And then refresh the UI + rpc->refresh(true); } else { QMessageBox::critical(this, tr("Wallet Decryption Failed"), QString::fromStdString(res["error"].get()), @@ -641,44 +661,71 @@ void MainWindow::exportTransactions() { } /** - * 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 hushd, non-standard locations, custom params passed to hushd, many others + * Export the seed phrase. */ -void MainWindow::backupWalletDat() { +void MainWindow::exportSeed() { if (!rpc->getConnection()) return; + + + 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(tr("This might take several minutes. Loading...")); + pui.privKeyTxt->setReadOnly(true); + pui.privKeyTxt->setLineWrapMode(QPlainTextEdit::LineWrapMode::NoWrap); + + pui.helpLbl->setText(tr("This is your wallet seed. Please back it up carefully and safely.")); + + // 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-seed.txt"); + QFile file(fileName); + if (!file.open(QIODevice::WriteOnly)) { + QMessageBox::information(this, tr("Unable to open file"), file.errorString()); + return; + } + QTextStream out(&file); + out << pui.privKeyTxt->toPlainText(); + }); + + rpc->fetchSeed([=](json reply) { + if (isJsonError(reply)) { + pui.privKeyTxt->setPlainText(tr("Error loading wallet seed: ") + QString::fromStdString(reply["error"])); + pui.buttonBox->button(QDialogButtonBox::Save)->setEnabled(false); + + return; + } + + pui.privKeyTxt->setPlainText(QString::fromStdString(reply.dump())); + pui.buttonBox->button(QDialogButtonBox::Save)->setEnabled(true); + }); + + + d.exec(); } - // QDir hushdir(rpc->getConnection()->config->hushDir); - // QString backupDefaultName = "hush-wallet-backup-" + QDateTime::currentDateTime().toString("yyyyMMdd") + ".dat"; - - // if (Settings::getInstance()->isTestnet()) { - // hushdir.cd("testnet3"); - // backupDefaultName = "testnet-" + backupDefaultName; - // } - - // QFile wallet(hushdir.filePath("wallet.dat")); - // if (!wallet.exists()) { - // QMessageBox::critical(this, tr("No wallet.dat"), tr("Couldn't find the wallet.dat on this computer") + "\n" + - // tr("You need to back it up from the machine hushd is running on"), QMessageBox::Ok); - // return; - // } - - // QUrl backupName = QFileDialog::getSaveFileUrl(this, tr("Backup wallet.dat"), backupDefaultName, "Data file (*.dat)"); - // if (backupName.isEmpty()) - // return; - - // if (!wallet.copy(backupName.toLocalFile())) { - // QMessageBox::critical(this, tr("Couldn't backup"), tr("Couldn't backup the wallet.dat file.") + - // tr("You need to back it up manually."), QMessageBox::Ok); - // } - - void MainWindow::exportAllKeys() { exportKeys(""); } void MainWindow::exportKeys(QString addr) { + if (!rpc->getConnection()) + return; + bool allKeys = addr.isEmpty() ? true : false; QDialog d(this); diff --git a/src/mainwindow.h b/src/mainwindow.h index 7051085..0af7ad3 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -122,7 +122,7 @@ private: void importPrivKey(); void exportAllKeys(); void exportKeys(QString addr = ""); - void backupWalletDat(); + void exportSeed(); void exportTransactions(); void doImport(QList* keys); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index eb8d799..b83a923 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -1099,7 +1099,7 @@ - + @@ -1190,9 +1190,9 @@ Ctrl+B - + - &Backup wallet.dat + &Export seed phrase diff --git a/src/sendtab.cpp b/src/sendtab.cpp index 1da4577..f9b3e9f 100644 --- a/src/sendtab.cpp +++ b/src/sendtab.cpp @@ -616,6 +616,7 @@ void MainWindow::sendButton() { Tx tx = createTxFromSendPage(); QString error = doSendTxValidations(tx); + if (!error.isEmpty()) { // Something went wrong, so show an error and exit QMessageBox msg(QMessageBox::Critical, tr("Transaction Error"), error, @@ -709,6 +710,9 @@ void MainWindow::sendButton() { } QString MainWindow::doSendTxValidations(Tx tx) { + // Check to see if we have enough verified funds to send the Tx. + + CAmount total; for (auto toAddr : tx.toAddrs) { if (!Settings::isValidAddress(toAddr.addr)) { QString addr = (toAddr.addr.length() > 100 ? toAddr.addr.left(100) + "..." : toAddr.addr); @@ -720,6 +724,16 @@ QString MainWindow::doSendTxValidations(Tx tx) { if (toAddr.amount.toqint64() < 0) { return QString(tr("Amount for address '%1' is invalid!").arg(toAddr.addr)); } + + total = total + toAddr.amount; + } + total = total + tx.fee; + + auto available = rpc->getModel()->getAvailableBalance(); + + if (available < total) { + return tr("Not enough available funds to send this transaction\n\nHave: %1\nNeed: %2\n\nNote: Funds need 5 confirmations before they can be spent") + .arg(available.toDecimalhushString(), total.toDecimalhushString()); } return ""; diff --git a/src/settings.h b/src/settings.h index 5d1f1c2..6e29b88 100644 --- a/src/settings.h +++ b/src/settings.h @@ -127,4 +127,10 @@ inline bool isJsonSuccess(const json& res) { QString::fromStdString(res["result"].get()) == "success"; } +inline bool isJsonError(const json& res) { + return res.find("result") != res.end() && + QString::fromStdString(res["result"].get()) == "error"; +} + + #endif // SETTINGS_H