Browse Source

formatting

import_zecw
adityapk00 6 years ago
parent
commit
e1b9ca0150
  1. 192
      src/balancestablemodel.cpp
  2. 10
      src/main.cpp
  3. 280
      src/mainwindow.cpp
  4. 22
      src/mainwindow.h
  5. 210
      src/rpc.cpp
  6. 10
      src/rpc.h
  7. 144
      src/sendtab.cpp
  8. 10
      src/senttxstore.cpp
  9. 222
      src/settings.cpp
  10. 66
      src/settings.h
  11. 546
      src/turnstile.cpp
  12. 74
      src/turnstile.h
  13. 26
      src/txtablemodel.cpp
  14. 26
      src/utils.h

192
src/balancestablemodel.cpp

@ -3,128 +3,128 @@
#include "utils.h" #include "utils.h"
BalancesTableModel::BalancesTableModel(QObject *parent) BalancesTableModel::BalancesTableModel(QObject *parent)
: QAbstractTableModel(parent) { : QAbstractTableModel(parent) {
} }
void BalancesTableModel::setNewData(const QMap<QString, double>* balances, void BalancesTableModel::setNewData(const QMap<QString, double>* balances,
const QList<UnspentOutput>* outputs) const QList<UnspentOutput>* outputs)
{ {
loading = false; loading = false;
int currentRows = rowCount(QModelIndex()); int currentRows = rowCount(QModelIndex());
// Copy over the utxos for our use // Copy over the utxos for our use
delete utxos; delete utxos;
utxos = new QList<UnspentOutput>(); utxos = new QList<UnspentOutput>();
// This is a QList deep copy. // This is a QList deep copy.
*utxos = *outputs; *utxos = *outputs;
// Process the address balances into a list // Process the address balances into a list
delete modeldata; delete modeldata;
modeldata = new QList<std::tuple<QString, QString>>(); modeldata = new QList<std::tuple<QString, QString>>();
std::for_each(balances->keyBegin(), balances->keyEnd(), [=] (auto keyIt) { std::for_each(balances->keyBegin(), balances->keyEnd(), [=] (auto keyIt) {
modeldata->push_back(std::make_tuple(keyIt, QString::number(balances->value(keyIt), 'g', 8))); modeldata->push_back(std::make_tuple(keyIt, QString::number(balances->value(keyIt), 'g', 8)));
}); });
// And then update the data // And then update the data
dataChanged(index(0, 0), index(modeldata->size()-1, columnCount(index(0,0))-1)); dataChanged(index(0, 0), index(modeldata->size()-1, columnCount(index(0,0))-1));
// Change the layout only if the number of rows changed // Change the layout only if the number of rows changed
if (modeldata && modeldata->size() != currentRows) if (modeldata && modeldata->size() != currentRows)
layoutChanged(); layoutChanged();
} }
BalancesTableModel::~BalancesTableModel() { BalancesTableModel::~BalancesTableModel() {
delete modeldata; delete modeldata;
delete utxos; delete utxos;
} }
int BalancesTableModel::rowCount(const QModelIndex&) const int BalancesTableModel::rowCount(const QModelIndex&) const
{ {
if (modeldata == nullptr) { if (modeldata == nullptr) {
if (loading) if (loading)
return 1; return 1;
else else
return 0; return 0;
} }
return modeldata->size(); return modeldata->size();
} }
int BalancesTableModel::columnCount(const QModelIndex&) const int BalancesTableModel::columnCount(const QModelIndex&) const
{ {
return 2; return 2;
} }
QVariant BalancesTableModel::data(const QModelIndex &index, int role) const QVariant BalancesTableModel::data(const QModelIndex &index, int role) const
{ {
if (loading) { if (loading) {
if (role == Qt::DisplayRole) if (role == Qt::DisplayRole)
return "Loading..."; return "Loading...";
else else
return QVariant(); return QVariant();
} }
if (role == Qt::TextAlignmentRole && index.column() == 1) return QVariant(Qt::AlignRight | Qt::AlignVCenter); if (role == Qt::TextAlignmentRole && index.column() == 1) return QVariant(Qt::AlignRight | Qt::AlignVCenter);
if (role == Qt::ForegroundRole) { if (role == Qt::ForegroundRole) {
// If any of the UTXOs for this address has zero confirmations, paint it in red // If any of the UTXOs for this address has zero confirmations, paint it in red
const auto& addr = std::get<0>(modeldata->at(index.row())); const auto& addr = std::get<0>(modeldata->at(index.row()));
for (auto utxo : *utxos) { for (auto utxo : *utxos) {
if (utxo.address == addr && utxo.confirmations == 0) { if (utxo.address == addr && utxo.confirmations == 0) {
QBrush b; QBrush b;
b.setColor(Qt::red); b.setColor(Qt::red);
return b; return b;
} }
} }
// Else, just return the default brush // Else, just return the default brush
QBrush b; QBrush b;++
b.setColor(Qt::black); b.setColor(Qt::black);
return b; return b;
} }
if (role == Qt::DisplayRole) { if (role == Qt::DisplayRole) {
switch (index.column()) { switch (index.column()) {
case 0: return std::get<0>(modeldata->at(index.row())); case 0: return std::get<0>(modeldata->at(index.row()));
case 1: return Settings::getInstance()->getZECDisplayFormat(std::get<1>(modeldata->at(index.row())).toDouble()); case 1: return Settings::getInstance()->getZECDisplayFormat(std::get<1>(modeldata->at(index.row())).toDouble());
} }
} }
if(role == Qt::ToolTipRole) { if(role == Qt::ToolTipRole) {
switch (index.column()) { switch (index.column()) {
case 0: return std::get<0>(modeldata->at(index.row())); case 0: return std::get<0>(modeldata->at(index.row()));
case 1: { case 1: {
auto bal = std::get<1>(modeldata->at(index.row())).toDouble(); auto bal = std::get<1>(modeldata->at(index.row())).toDouble();
return Settings::getInstance()->getUSDFormat(bal); return Settings::getInstance()->getUSDFormat(bal);
} }
} }
} }
return QVariant(); return QVariant();
} }
QVariant BalancesTableModel::headerData(int section, Qt::Orientation orientation, int role) const QVariant BalancesTableModel::headerData(int section, Qt::Orientation orientation, int role) const
{ {
if (role == Qt::TextAlignmentRole && section == 1) { if (role == Qt::TextAlignmentRole && section == 1) {
return QVariant(Qt::AlignRight | Qt::AlignVCenter); return QVariant(Qt::AlignRight | Qt::AlignVCenter);
} }
if (role == Qt::FontRole) { if (role == Qt::FontRole) {
QFont f; QFont f;
f.setBold(true); f.setBold(true);
return f; return f;
} }
if (role != Qt::DisplayRole) if (role != Qt::DisplayRole)
return QVariant(); return QVariant();
if (orientation == Qt::Horizontal) { if (orientation == Qt::Horizontal) {
switch (section) { switch (section) {
case 0: return tr("Address"); case 0: return tr("Address");
case 1: return tr("Amount"); case 1: return tr("Amount");
default: return QVariant(); default: return QVariant();
} }
} }
return QVariant(); return QVariant();
} }

10
src/main.cpp

@ -6,8 +6,8 @@
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); QCoreApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QApplication a(argc, argv); QApplication a(argc, argv);
QIcon icon(":/icons/res/icon.ico"); QIcon icon(":/icons/res/icon.ico");
@ -18,10 +18,10 @@ int main(int argc, char *argv[])
qApp->setFont(QFont("Ubuntu", 11, QFont::Normal, false)); qApp->setFont(QFont("Ubuntu", 11, QFont::Normal, false));
#endif #endif
std::srand(std::time(nullptr)); std::srand(std::time(nullptr));
QCoreApplication::setOrganizationName("zec-qt-wallet-org"); QCoreApplication::setOrganizationName("zec-qt-wallet-org");
QCoreApplication::setApplicationName("zec-qt-wallet"); QCoreApplication::setApplicationName("zec-qt-wallet");
Settings::init(); Settings::init();

280
src/mainwindow.cpp

@ -22,11 +22,11 @@ MainWindow::MainWindow(QWidget *parent) :
{ {
ui->setupUi(this); ui->setupUi(this);
// Status Bar // Status Bar
setupStatusBar(); setupStatusBar();
// Settings editor // Settings editor
setupSettingsModal(); setupSettingsModal();
// Set up exit action // Set up exit action
QObject::connect(ui->actionExit, &QAction::triggered, this, &MainWindow::close); QObject::connect(ui->actionExit, &QAction::triggered, this, &MainWindow::close);
@ -45,10 +45,10 @@ MainWindow::MainWindow(QWidget *parent) :
QObject::connect(ui->actionAbout, &QAction::triggered, [=] () { QObject::connect(ui->actionAbout, &QAction::triggered, [=] () {
QDialog aboutDialog(this); QDialog aboutDialog(this);
Ui_about about; Ui_about about;
about.setupUi(&aboutDialog); about.setupUi(&aboutDialog);
QString version = QString("Version ") % QString(APP_VERSION) % " (" % QString(__DATE__) % ")"; QString version = QString("Version ") % QString(APP_VERSION) % " (" % QString(__DATE__) % ")";
about.versionLabel->setText(version); about.versionLabel->setText(version);
aboutDialog.exec(); aboutDialog.exec();
}); });
@ -67,25 +67,25 @@ MainWindow::MainWindow(QWidget *parent) :
rpc->refresh(true); // Force refresh first time rpc->refresh(true); // Force refresh first time
restoreSavedStates(); restoreSavedStates();
} }
void MainWindow::restoreSavedStates() { void MainWindow::restoreSavedStates() {
QSettings s; QSettings s;
restoreGeometry(s.value("geometry").toByteArray()); restoreGeometry(s.value("geometry").toByteArray());
ui->balancesTable->horizontalHeader()->restoreState(s.value("baltablegeometry").toByteArray()); ui->balancesTable->horizontalHeader()->restoreState(s.value("baltablegeometry").toByteArray());
ui->transactionsTable->horizontalHeader()->restoreState(s.value("tratablegeometry").toByteArray()); ui->transactionsTable->horizontalHeader()->restoreState(s.value("tratablegeometry").toByteArray());
} }
void MainWindow::closeEvent(QCloseEvent* event) { void MainWindow::closeEvent(QCloseEvent* event) {
QSettings s; QSettings s;
s.setValue("geometry", saveGeometry()); s.setValue("geometry", saveGeometry());
s.setValue("baltablegeometry", ui->balancesTable->horizontalHeader()->saveState()); s.setValue("baltablegeometry", ui->balancesTable->horizontalHeader()->saveState());
s.setValue("tratablegeometry", ui->transactionsTable->horizontalHeader()->saveState()); s.setValue("tratablegeometry", ui->transactionsTable->horizontalHeader()->saveState());
QMainWindow::closeEvent(event); QMainWindow::closeEvent(event);
} }
void MainWindow::turnstileProgress() { void MainWindow::turnstileProgress() {
@ -106,8 +106,8 @@ void MainWindow::turnstileProgress() {
auto nextTxBlock = curProgress.nextBlock - Settings::getInstance()->getBlockNumber(); auto nextTxBlock = curProgress.nextBlock - Settings::getInstance()->getBlockNumber();
progress.fromAddr->setText(curProgress.from); progress.fromAddr->setText(curProgress.from);
progress.toAddr->setText(curProgress.to); progress.toAddr->setText(curProgress.to);
if (curProgress.step == curProgress.totalSteps) { if (curProgress.step == curProgress.totalSteps) {
auto txt = QString("Turnstile migration finished"); auto txt = QString("Turnstile migration finished");
@ -272,126 +272,126 @@ void MainWindow::setupTurnstileDialog() {
} }
void MainWindow::setupStatusBar() { void MainWindow::setupStatusBar() {
// Status Bar // Status Bar
loadingLabel = new QLabel(); loadingLabel = new QLabel();
loadingMovie = new QMovie(":/icons/res/loading.gif"); loadingMovie = new QMovie(":/icons/res/loading.gif");
loadingMovie->setScaledSize(QSize(32, 16)); loadingMovie->setScaledSize(QSize(32, 16));
loadingMovie->start(); loadingMovie->start();
loadingLabel->setAttribute(Qt::WA_NoSystemBackground); loadingLabel->setAttribute(Qt::WA_NoSystemBackground);
loadingLabel->setMovie(loadingMovie); loadingLabel->setMovie(loadingMovie);
ui->statusBar->addPermanentWidget(loadingLabel); ui->statusBar->addPermanentWidget(loadingLabel);
loadingLabel->setVisible(false); loadingLabel->setVisible(false);
// Custom status bar menu // Custom status bar menu
ui->statusBar->setContextMenuPolicy(Qt::CustomContextMenu); ui->statusBar->setContextMenuPolicy(Qt::CustomContextMenu);
QObject::connect(ui->statusBar, &QStatusBar::customContextMenuRequested, [=](QPoint pos) { QObject::connect(ui->statusBar, &QStatusBar::customContextMenuRequested, [=](QPoint pos) {
auto msg = ui->statusBar->currentMessage(); auto msg = ui->statusBar->currentMessage();
QMenu menu(this); QMenu menu(this);
if (!msg.isEmpty() && msg.startsWith(Utils::txidStatusMessage)) { if (!msg.isEmpty() && msg.startsWith(Utils::txidStatusMessage)) {
auto txid = msg.split(":")[1].trimmed(); auto txid = msg.split(":")[1].trimmed();
menu.addAction("Copy txid", [=]() { menu.addAction("Copy txid", [=]() {
QGuiApplication::clipboard()->setText(txid); QGuiApplication::clipboard()->setText(txid);
}); });
menu.addAction("View tx on block explorer", [=]() { menu.addAction("View tx on block explorer", [=]() {
QString url; QString url;
if (Settings::getInstance()->isTestnet()) { if (Settings::getInstance()->isTestnet()) {
url = "https://explorer.testnet.z.cash/tx/" + txid; url = "https://explorer.testnet.z.cash/tx/" + txid;
} }
else { else {
url = "https://explorer.zcha.in/transactions/" + txid; url = "https://explorer.zcha.in/transactions/" + txid;
} }
QDesktopServices::openUrl(QUrl(url)); QDesktopServices::openUrl(QUrl(url));
}); });
} }
menu.addAction("Refresh", [=]() { menu.addAction("Refresh", [=]() {
rpc->refresh(true); rpc->refresh(true);
}); });
QPoint gpos(mapToGlobal(pos).x(), mapToGlobal(pos).y() + this->height() - ui->statusBar->height()); QPoint gpos(mapToGlobal(pos).x(), mapToGlobal(pos).y() + this->height() - ui->statusBar->height());
menu.exec(gpos); menu.exec(gpos);
}); });
statusLabel = new QLabel(); statusLabel = new QLabel();
ui->statusBar->addPermanentWidget(statusLabel); ui->statusBar->addPermanentWidget(statusLabel);
statusIcon = new QLabel(); statusIcon = new QLabel();
ui->statusBar->addPermanentWidget(statusIcon); ui->statusBar->addPermanentWidget(statusIcon);
} }
void MainWindow::setupSettingsModal() { void MainWindow::setupSettingsModal() {
// Set up File -> Settings action // Set up File -> Settings action
QObject::connect(ui->actionSettings, &QAction::triggered, [=]() { QObject::connect(ui->actionSettings, &QAction::triggered, [=]() {
QDialog settingsDialog(this); QDialog settingsDialog(this);
Ui_Settings settings; Ui_Settings settings;
settings.setupUi(&settingsDialog); settings.setupUi(&settingsDialog);
// Setup save sent check box // Setup save sent check box
QObject::connect(settings.chkSaveTxs, &QCheckBox::stateChanged, [=](auto checked) { QObject::connect(settings.chkSaveTxs, &QCheckBox::stateChanged, [=](auto checked) {
Settings::getInstance()->setSaveZtxs(checked); Settings::getInstance()->setSaveZtxs(checked);
}); });
// Setup clear button // Setup clear button
QObject::connect(settings.btnClearSaved, &QCheckBox::clicked, [=]() { QObject::connect(settings.btnClearSaved, &QCheckBox::clicked, [=]() {
if (QMessageBox::warning(this, "Clear saved history?", 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 ?", "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 ?",
QMessageBox::Yes, QMessageBox::Cancel)) { QMessageBox::Yes, QMessageBox::Cancel)) {
SentTxStore::deleteHistory(); SentTxStore::deleteHistory();
// Reload after the clear button so existing txs disappear // Reload after the clear button so existing txs disappear
rpc->refresh(true); rpc->refresh(true);
} }
}); });
// Save sent transactions // Save sent transactions
settings.chkSaveTxs->setChecked(Settings::getInstance()->getSaveZtxs()); settings.chkSaveTxs->setChecked(Settings::getInstance()->getSaveZtxs());
// Connection Settings // Connection Settings
QIntValidator validator(0, 65535); QIntValidator validator(0, 65535);
settings.port->setValidator(&validator); settings.port->setValidator(&validator);
// Load current values into the dialog // Load current values into the dialog
settings.hostname->setText(Settings::getInstance()->getHost()); settings.hostname->setText(Settings::getInstance()->getHost());
settings.port->setText(Settings::getInstance()->getPort()); settings.port->setText(Settings::getInstance()->getPort());
settings.rpcuser->setText(Settings::getInstance()->getUsernamePassword().split(":")[0]); settings.rpcuser->setText(Settings::getInstance()->getUsernamePassword().split(":")[0]);
settings.rpcpassword->setText(Settings::getInstance()->getUsernamePassword().split(":")[1]); settings.rpcpassword->setText(Settings::getInstance()->getUsernamePassword().split(":")[1]);
// If values are coming from zcash.conf, then disable all the fields // If values are coming from zcash.conf, then disable all the fields
auto zcashConfLocation = Settings::getInstance()->getZcashdConfLocation(); auto zcashConfLocation = Settings::getInstance()->getZcashdConfLocation();
if (!zcashConfLocation.isEmpty()) { if (!zcashConfLocation.isEmpty()) {
settings.confMsg->setText("Settings are being read from \n" + zcashConfLocation); settings.confMsg->setText("Settings are being read from \n" + zcashConfLocation);
settings.hostname->setEnabled(false); settings.hostname->setEnabled(false);
settings.port->setEnabled(false); settings.port->setEnabled(false);
settings.rpcuser->setEnabled(false); settings.rpcuser->setEnabled(false);
settings.rpcpassword->setEnabled(false); settings.rpcpassword->setEnabled(false);
} }
else { else {
settings.confMsg->setText("No local zcash.conf found. Please configure connection manually."); settings.confMsg->setText("No local zcash.conf found. Please configure connection manually.");
settings.hostname->setEnabled(true); settings.hostname->setEnabled(true);
settings.port->setEnabled(true); settings.port->setEnabled(true);
settings.rpcuser->setEnabled(true); settings.rpcuser->setEnabled(true);
settings.rpcpassword->setEnabled(true); settings.rpcpassword->setEnabled(true);
} }
// Connection tab by default // Connection tab by default
settings.tabWidget->setCurrentIndex(0); settings.tabWidget->setCurrentIndex(0);
if (settingsDialog.exec() == QDialog::Accepted) { if (settingsDialog.exec() == QDialog::Accepted) {
if (zcashConfLocation.isEmpty()) { if (zcashConfLocation.isEmpty()) {
// Save settings // Save settings
Settings::getInstance()->saveSettings( Settings::getInstance()->saveSettings(
settings.hostname->text(), settings.hostname->text(),
settings.port->text(), settings.port->text(),
settings.rpcuser->text(), settings.rpcuser->text(),
settings.rpcpassword->text()); settings.rpcpassword->text());
this->rpc->reloadConnectionInfo(); this->rpc->reloadConnectionInfo();
} }
// Then refresh everything. // Then refresh everything.
this->rpc->refresh(true); this->rpc->refresh(true);
}; };
}); });
} }
@ -504,18 +504,18 @@ void MainWindow::setupBalancesTab() {
rpc->getTPrivKey(addr, fnCB); rpc->getTPrivKey(addr, fnCB);
}); });
menu.addAction("Send from " % addr.left(40) % (addr.size() > 40 ? "..." : ""), [=]() { menu.addAction("Send from " % addr.left(40) % (addr.size() > 40 ? "..." : ""), [=]() {
// Find the inputs combo // Find the inputs combo
for (int i = 0; i < ui->inputsCombo->count(); i++) { for (int i = 0; i < ui->inputsCombo->count(); i++) {
if (ui->inputsCombo->itemText(i).startsWith(addr)) { if (ui->inputsCombo->itemText(i).startsWith(addr)) {
ui->inputsCombo->setCurrentIndex(i); ui->inputsCombo->setCurrentIndex(i);
break; break;
} }
} }
// And switch to the send tab. // And switch to the send tab.
ui->tabWidget->setCurrentIndex(1); ui->tabWidget->setCurrentIndex(1);
}); });
if (addr.startsWith("t")) { if (addr.startsWith("t")) {
menu.addAction("View on block explorer", [=] () { menu.addAction("View on block explorer", [=] () {

22
src/mainwindow.h

@ -43,7 +43,7 @@ public:
QLabel* loadingLabel; QLabel* loadingLabel;
private: private:
void closeEvent(QCloseEvent* event); void closeEvent(QCloseEvent* event);
void setupSendTab(); void setupSendTab();
void setupTransactionsTab(); void setupTransactionsTab();
@ -51,11 +51,11 @@ private:
void setupBalancesTab(); void setupBalancesTab();
void setupTurnstileDialog(); void setupTurnstileDialog();
void setupSettingsModal(); void setupSettingsModal();
void setupStatusBar(); void setupStatusBar();
void removeExtraAddresses(); void removeExtraAddresses();
void setDefaultPayFrom(); void setDefaultPayFrom();
Tx createTxFromSendPage(); Tx createTxFromSendPage();
bool confirmTx(Tx tx, ToFields devFee); bool confirmTx(Tx tx, ToFields devFee);
@ -63,11 +63,11 @@ private:
void turnstileDoMigration(QString fromAddr = ""); void turnstileDoMigration(QString fromAddr = "");
void turnstileProgress(); void turnstileProgress();
void cancelButton(); void cancelButton();
void sendButton(); void sendButton();
void inputComboTextChanged(const QString& text); void inputComboTextChanged(const QString& text);
void addAddressSection(); void addAddressSection();
void maxAmountChecked(int checked); void maxAmountChecked(int checked);
void addressChanged(int number, const QString& text); void addressChanged(int number, const QString& text);
void amountChanged (int numer, const QString& text); void amountChanged (int numer, const QString& text);
@ -84,7 +84,7 @@ private:
void importPrivKey(); void importPrivKey();
void doImport(QList<QString>* keys); void doImport(QList<QString>* keys);
void restoreSavedStates(); void restoreSavedStates();
RPC* rpc; RPC* rpc;

210
src/rpc.cpp

@ -7,9 +7,9 @@
using json = nlohmann::json; using json = nlohmann::json;
RPC::RPC(QNetworkAccessManager* client, MainWindow* main) { RPC::RPC(QNetworkAccessManager* client, MainWindow* main) {
this->restclient = client; this->restclient = client;
this->main = main; this->main = main;
this->ui = main->ui; this->ui = main->ui;
this->turnstile = new Turnstile(this, main); this->turnstile = new Turnstile(this, main);
@ -26,7 +26,7 @@ RPC::RPC(QNetworkAccessManager* client, MainWindow* main) {
main->ui->transactionsTable->setColumnWidth(2, 200); main->ui->transactionsTable->setColumnWidth(2, 200);
main->ui->transactionsTable->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Stretch); main->ui->transactionsTable->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Stretch);
reloadConnectionInfo(); reloadConnectionInfo();
// Set up timer to refresh Price // Set up timer to refresh Price
priceTimer = new QTimer(main); priceTimer = new QTimer(main);
@ -35,12 +35,12 @@ RPC::RPC(QNetworkAccessManager* client, MainWindow* main) {
}); });
priceTimer->start(Utils::priceRefreshSpeed); // Every hour priceTimer->start(Utils::priceRefreshSpeed); // Every hour
// Set up a timer to refresh the UI every few seconds // Set up a timer to refresh the UI every few seconds
timer = new QTimer(main); timer = new QTimer(main);
QObject::connect(timer, &QTimer::timeout, [=]() { QObject::connect(timer, &QTimer::timeout, [=]() {
refresh(); refresh();
}); });
timer->start(Utils::updateSpeed); timer->start(Utils::updateSpeed);
// Set up the timer to watch for tx status // Set up the timer to watch for tx status
txTimer = new QTimer(main); txTimer = new QTimer(main);
@ -68,9 +68,9 @@ RPC::~RPC() {
} }
void RPC::reloadConnectionInfo() { void RPC::reloadConnectionInfo() {
// Reset for any errors caused. // Reset for any errors caused.
firstTime = true; firstTime = true;
QUrl myurl; QUrl myurl;
myurl.setScheme("http"); //https also applicable myurl.setScheme("http"); //https also applicable
myurl.setHost(Settings::getInstance()->getHost()); myurl.setHost(Settings::getInstance()->getHost());
@ -230,35 +230,35 @@ void RPC::getTransactions(const std::function<void(json)>& cb) {
} }
void RPC::doSendRPC(const json& payload, const std::function<void(json)>& cb, const std::function<void(QString)>& err) { void RPC::doSendRPC(const json& payload, const std::function<void(json)>& cb, const std::function<void(QString)>& err) {
QNetworkReply *reply = restclient->post(request, QByteArray::fromStdString(payload.dump())); QNetworkReply *reply = restclient->post(request, QByteArray::fromStdString(payload.dump()));
QObject::connect(reply, &QNetworkReply::finished, [=] { QObject::connect(reply, &QNetworkReply::finished, [=] {
reply->deleteLater(); reply->deleteLater();
if (reply->error() != QNetworkReply::NoError) { if (reply->error() != QNetworkReply::NoError) {
auto parsed = json::parse(reply->readAll(), nullptr, false); auto parsed = json::parse(reply->readAll(), nullptr, false);
if (!parsed.is_discarded() && !parsed["error"]["message"].is_null()) { if (!parsed.is_discarded() && !parsed["error"]["message"].is_null()) {
err(QString::fromStdString(parsed["error"]["message"])); err(QString::fromStdString(parsed["error"]["message"]));
} }
else { else {
err(reply->errorString()); err(reply->errorString());
} }
return; return;
} }
auto parsed = json::parse(reply->readAll(), nullptr, false); auto parsed = json::parse(reply->readAll(), nullptr, false);
if (parsed.is_discarded()) { if (parsed.is_discarded()) {
err("Unknown error"); err("Unknown error");
} }
cb(parsed["result"]); cb(parsed["result"]);
}); });
} }
// Default implementation of a Send RPC that default shows an error message box with the error. // Default implementation of a Send RPC that default shows an error message box with the error.
void RPC::doSendRPC(const json& payload, const std::function<void(json)>& cb) { void RPC::doSendRPC(const json& payload, const std::function<void(json)>& cb) {
doSendRPC(payload, cb, [=](auto error) { this->handleTxError(error); }); doSendRPC(payload, cb, [=](auto error) { this->handleTxError(error); });
} }
void RPC::sendZTransaction(json params, const std::function<void(json)>& cb) { void RPC::sendZTransaction(json params, const std::function<void(json)>& cb) {
@ -296,17 +296,17 @@ void RPC::handleConnectionError(const QString& error) {
% "\nIf you're connecting to a remote note, you can change the username/password in the " % "\nIf you're connecting to a remote note, you can change the username/password in the "
% "File->Settings menu."; % "File->Settings menu.";
} else if (error.contains("connection refused", Qt::CaseInsensitive)) { } else if (error.contains("connection refused", Qt::CaseInsensitive)) {
auto confLocation = Settings::getInstance()->getZcashdConfLocation(); auto confLocation = Settings::getInstance()->getZcashdConfLocation();
if (confLocation.isEmpty()) { if (confLocation.isEmpty()) {
explanation = QString() explanation = QString()
% "\n\nA zcash.conf was not found on this machine. If you are connecting to a remote/non-standard node " % "\n\nA zcash.conf was not found on this machine. If you are connecting to a remote/non-standard node "
% "please set the host/port and user/password in the File->Settings menu."; % "please set the host/port and user/password in the File->Settings menu.";
} }
else { else {
explanation = QString() explanation = QString()
% "\n\nA zcash.conf was found at\n" % confLocation % "\n\nA zcash.conf was found at\n" % confLocation
% "\nbut we can't connect to zcashd. Is rpcuser=<user> and rpcpassword=<pass> set in the zcash.conf file?"; % "\nbut we can't connect to zcashd. Is rpcuser=<user> and rpcpassword=<pass> set in the zcash.conf file?";
} }
} else if (error.contains("internal server error", Qt::CaseInsensitive) || } else if (error.contains("internal server error", Qt::CaseInsensitive) ||
error.contains("rewinding", Qt::CaseInsensitive) || error.contains("rewinding", Qt::CaseInsensitive) ||
error.contains("loading", Qt::CaseInsensitive)) { error.contains("loading", Qt::CaseInsensitive)) {
@ -351,7 +351,7 @@ void RPC::fillTxJsonParams(json& params, Tx tx) {
for (int i=0; i < tx.toAddrs.size(); i++) { for (int i=0; i < tx.toAddrs.size(); i++) {
auto toAddr = tx.toAddrs[i]; auto toAddr = tx.toAddrs[i];
// Construct the JSON params // Construct the JSON params
json rec = json::object(); json rec = json::object();
rec["address"] = toAddr.addr.toStdString(); rec["address"] = toAddr.addr.toStdString();
rec["amount"] = QString::number(toAddr.amount, 'f', 8).toDouble(); // Force it through string for rounding rec["amount"] = QString::number(toAddr.amount, 'f', 8).toDouble(); // Force it through string for rounding
@ -489,14 +489,14 @@ void RPC::getInfoThenRefresh(bool force) {
}; };
doRPC(payload, [=] (const json& reply) { doRPC(payload, [=] (const json& reply) {
// Testnet? // Testnet?
if (!reply["testnet"].is_null()) { if (!reply["testnet"].is_null()) {
Settings::getInstance()->setTestnet(reply["testnet"].get<json::boolean_t>()); Settings::getInstance()->setTestnet(reply["testnet"].get<json::boolean_t>());
}; };
// Connected, so display checkmark. // Connected, so display checkmark.
QIcon i(":/icons/res/connected.png"); QIcon i(":/icons/res/connected.png");
main->statusIcon->setPixmap(i.pixmap(16, 16)); main->statusIcon->setPixmap(i.pixmap(16, 16));
static int lastBlock = 0; static int lastBlock = 0;
int curBlock = reply["blocks"].get<json::number_integer_t>(); int curBlock = reply["blocks"].get<json::number_integer_t>();
@ -505,19 +505,19 @@ void RPC::getInfoThenRefresh(bool force) {
// Something changed, so refresh everything. // Something changed, so refresh everything.
lastBlock = curBlock; lastBlock = curBlock;
refreshBalances(); refreshBalances();
refreshAddresses(); // This calls refreshZSentTransactions() and refreshReceivedZTrans() refreshAddresses(); // This calls refreshZSentTransactions() and refreshReceivedZTrans()
refreshTransactions(); refreshTransactions();
} }
// Call to see if the blockchain is syncing. // Call to see if the blockchain is syncing.
json payload = { json payload = {
{"jsonrpc", "1.0"}, {"jsonrpc", "1.0"},
{"id", "someid"}, {"id", "someid"},
{"method", "getblockchaininfo"} {"method", "getblockchaininfo"}
}; };
doRPC(payload, [=](const json& reply) { doRPC(payload, [=](const json& reply) {
auto progress = reply["verificationprogress"].get<double>(); auto progress = reply["verificationprogress"].get<double>();
bool isSyncing = progress < 0.999; // 99.9% bool isSyncing = progress < 0.999; // 99.9%
int blockNumber = reply["blocks"].get<json::number_unsigned_t>(); int blockNumber = reply["blocks"].get<json::number_unsigned_t>();
@ -525,20 +525,20 @@ void RPC::getInfoThenRefresh(bool force) {
Settings::getInstance()->setSyncing(isSyncing); Settings::getInstance()->setSyncing(isSyncing);
Settings::getInstance()->setBlockNumber(blockNumber); Settings::getInstance()->setBlockNumber(blockNumber);
QString statusText = QString() % QString statusText = QString() %
(isSyncing ? "Syncing" : "Connected") % (isSyncing ? "Syncing" : "Connected") %
" (" % " (" %
(Settings::getInstance()->isTestnet() ? "testnet:" : "") % (Settings::getInstance()->isTestnet() ? "testnet:" : "") %
QString::number(blockNumber) % QString::number(blockNumber) %
(isSyncing ? ("/" % QString::number(progress*100, 'f', 0) % "%") : QString()) % (isSyncing ? ("/" % QString::number(progress*100, 'f', 0) % "%") : QString()) %
")"; ")";
main->statusLabel->setText(statusText); main->statusLabel->setText(statusText);
auto zecPrice = Settings::getInstance()->getUSDFormat(1); auto zecPrice = Settings::getInstance()->getUSDFormat(1);
if (!zecPrice.isEmpty()) { if (!zecPrice.isEmpty()) {
main->statusLabel->setToolTip("1 ZEC = " + zecPrice); main->statusLabel->setToolTip("1 ZEC = " + zecPrice);
main->statusIcon->setToolTip("1 ZEC = " + zecPrice); main->statusIcon->setToolTip("1 ZEC = " + zecPrice);
} }
}); });
}); });
} }
@ -569,43 +569,43 @@ void RPC::updateUI(bool anyUnconfirmed) {
main->ui->actionTurnstile_Migration->setVisible(false); main->ui->actionTurnstile_Migration->setVisible(false);
} }
ui->unconfirmedWarning->setVisible(anyUnconfirmed); ui->unconfirmedWarning->setVisible(anyUnconfirmed);
// Update balances model data, which will update the table too // Update balances model data, which will update the table too
balancesTableModel->setNewData(allBalances, utxos); balancesTableModel->setNewData(allBalances, utxos);
// Add all the addresses into the inputs combo box // Add all the addresses into the inputs combo box
auto lastFromAddr = ui->inputsCombo->currentText().split("(")[0].trimmed(); auto lastFromAddr = ui->inputsCombo->currentText().split("(")[0].trimmed();
ui->inputsCombo->clear(); ui->inputsCombo->clear();
auto i = allBalances->constBegin(); auto i = allBalances->constBegin();
while (i != allBalances->constEnd()) { while (i != allBalances->constEnd()) {
QString item = i.key() % "(" % QString::number(i.value(), 'g', 8) % " " % Utils::getTokenName() % ")"; QString item = i.key() % "(" % QString::number(i.value(), 'g', 8) % " " % Utils::getTokenName() % ")";
ui->inputsCombo->addItem(item); ui->inputsCombo->addItem(item);
if (item.startsWith(lastFromAddr)) ui->inputsCombo->setCurrentText(item); if (item.startsWith(lastFromAddr)) ui->inputsCombo->setCurrentText(item);
++i; ++i;
} }
}; };
// Function to process reply of the listunspent and z_listunspent API calls, used below. // Function to process reply of the listunspent and z_listunspent API calls, used below.
bool RPC::processUnspent(const json& reply) { bool RPC::processUnspent(const json& reply) {
bool anyUnconfirmed = false; bool anyUnconfirmed = false;
for (auto& it : reply.get<json::array_t>()) { for (auto& it : reply.get<json::array_t>()) {
QString qsAddr = QString::fromStdString(it["address"]); QString qsAddr = QString::fromStdString(it["address"]);
auto confirmations = it["confirmations"].get<json::number_unsigned_t>(); auto confirmations = it["confirmations"].get<json::number_unsigned_t>();
if (confirmations == 0) { if (confirmations == 0) {
anyUnconfirmed = true; anyUnconfirmed = true;
} }
utxos->push_back( utxos->push_back(
UnspentOutput{ qsAddr, QString::fromStdString(it["txid"]), UnspentOutput{ qsAddr, QString::fromStdString(it["txid"]),
QString::number(it["amount"].get<json::number_float_t>(), 'g', 8), QString::number(it["amount"].get<json::number_float_t>(), 'g', 8),
(int)confirmations, it["spendable"].get<json::boolean_t>() }); (int)confirmations, it["spendable"].get<json::boolean_t>() });
(*allBalances)[qsAddr] = (*allBalances)[qsAddr] + it["amount"].get<json::number_float_t>(); (*allBalances)[qsAddr] = (*allBalances)[qsAddr] + it["amount"].get<json::number_float_t>();
} }
return anyUnconfirmed; return anyUnconfirmed;
}; };
void RPC::refreshBalances() { void RPC::refreshBalances() {
@ -648,10 +648,10 @@ void RPC::refreshTransactions() {
QList<TransactionItem> txdata; QList<TransactionItem> txdata;
for (auto& it : reply.get<json::array_t>()) { for (auto& it : reply.get<json::array_t>()) {
double fee = 0; double fee = 0;
if (!it["fee"].is_null()) { if (!it["fee"].is_null()) {
fee = it["fee"].get<json::number_float_t>(); fee = it["fee"].get<json::number_float_t>();
} }
TransactionItem tx{ TransactionItem tx{
QString::fromStdString(it["category"]), QString::fromStdString(it["category"]),
@ -736,7 +736,7 @@ void RPC::watchTxStatus() {
QString status = QString::fromStdString(it["status"]); QString status = QString::fromStdString(it["status"]);
if (status == "success") { if (status == "success") {
auto txid = QString::fromStdString(it["result"]["txid"]); auto txid = QString::fromStdString(it["result"]["txid"]);
SentTxStore::addToSentTx(watchingOps.value(id), txid); SentTxStore::addToSentTx(watchingOps.value(id), txid);
main->ui->statusBar->showMessage(Utils::txidStatusMessage + " " + txid); main->ui->statusBar->showMessage(Utils::txidStatusMessage + " " + txid);

10
src/rpc.h

@ -15,7 +15,7 @@ class Turnstile;
struct TransactionItem { struct TransactionItem {
QString type; QString type;
qint64 datetime; qint64 datetime;
QString address; QString address;
QString txid; QString txid;
double amount; double amount;
@ -45,7 +45,7 @@ public:
const QList<UnspentOutput>* getUTXOs() { return utxos; } const QList<UnspentOutput>* getUTXOs() { return utxos; }
const QMap<QString, double>* getAllBalances() { return allBalances; } const QMap<QString, double>* getAllBalances() { return allBalances; }
void reloadConnectionInfo(); void reloadConnectionInfo();
void newZaddr(bool sapling, const std::function<void(json)>& cb); void newZaddr(bool sapling, const std::function<void(json)>& cb);
void newTaddr(const std::function<void(json)>& cb); void newTaddr(const std::function<void(json)>& cb);
@ -110,7 +110,7 @@ public:
private: private:
void doRPC (const json& payload, const std::function<void(json)>& cb); void doRPC (const json& payload, const std::function<void(json)>& cb);
void doSendRPC (const json& payload, const std::function<void(json)>& cb); void doSendRPC (const json& payload, const std::function<void(json)>& cb);
void doSendRPC(const json& payload, const std::function<void(json)>& cb, const std::function<void(QString)>& err); void doSendRPC(const json& payload, const std::function<void(json)>& cb, const std::function<void(QString)>& err);
void refreshBalances(); void refreshBalances();
@ -118,8 +118,8 @@ private:
void refreshSentZTrans (); void refreshSentZTrans ();
void refreshReceivedZTrans (QList<QString> zaddresses); void refreshReceivedZTrans (QList<QString> zaddresses);
bool processUnspent (const json& reply); bool processUnspent (const json& reply);
void updateUI (bool anyUnconfirmed); void updateUI (bool anyUnconfirmed);
void getInfoThenRefresh(bool force); void getInfoThenRefresh(bool force);

144
src/sendtab.cpp

@ -11,28 +11,28 @@
using json = nlohmann::json; using json = nlohmann::json;
void MainWindow::setupSendTab() { void MainWindow::setupSendTab() {
// Create the validator for send to/amount fields // Create the validator for send to/amount fields
auto amtValidator = new QDoubleValidator(0, 21000000, 8, ui->Amount1); auto amtValidator = new QDoubleValidator(0, 21000000, 8, ui->Amount1);
amtValidator->setNotation(QDoubleValidator::StandardNotation); amtValidator->setNotation(QDoubleValidator::StandardNotation);
ui->Amount1->setValidator(amtValidator); ui->Amount1->setValidator(amtValidator);
setDefaultPayFrom(); setDefaultPayFrom();
// Send button // Send button
QObject::connect(ui->sendTransactionButton, &QPushButton::clicked, this, &MainWindow::sendButton); QObject::connect(ui->sendTransactionButton, &QPushButton::clicked, this, &MainWindow::sendButton);
// Cancel Button // Cancel Button
QObject::connect(ui->cancelSendButton, &QPushButton::clicked, this, &MainWindow::cancelButton); QObject::connect(ui->cancelSendButton, &QPushButton::clicked, this, &MainWindow::cancelButton);
// Input Combobox current text changed // Input Combobox current text changed
QObject::connect(ui->inputsCombo, QOverload<const QString &>::of(&QComboBox::currentIndexChanged), QObject::connect(ui->inputsCombo, QOverload<const QString &>::of(&QComboBox::currentIndexChanged),
this, &MainWindow::inputComboTextChanged); this, &MainWindow::inputComboTextChanged);
// Hook up add address button click // Hook up add address button click
QObject::connect(ui->addAddressButton, &QPushButton::clicked, this, &MainWindow::addAddressSection); QObject::connect(ui->addAddressButton, &QPushButton::clicked, this, &MainWindow::addAddressSection);
// Max available Checkbox // Max available Checkbox
QObject::connect(ui->Max1, &QCheckBox::stateChanged, this, &MainWindow::maxAmountChecked); QObject::connect(ui->Max1, &QCheckBox::stateChanged, this, &MainWindow::maxAmountChecked);
// The first Memo button // The first Memo button
QObject::connect(ui->MemoBtn1, &QPushButton::clicked, [=] () { QObject::connect(ui->MemoBtn1, &QPushButton::clicked, [=] () {
@ -50,10 +50,10 @@ void MainWindow::setupSendTab() {
this->amountChanged(1, text); this->amountChanged(1, text);
}); });
// Font for the first Memo label // Font for the first Memo label
QFont f = ui->Address1->font(); QFont f = ui->Address1->font();
f.setPointSize(f.pointSize() - 1); f.setPointSize(f.pointSize() - 1);
ui->MemoTxt1->setFont(f); ui->MemoTxt1->setFont(f);
// Set up focus enter to set fees // Set up focus enter to set fees
QObject::connect(ui->tabWidget, &QTabWidget::currentChanged, [=] (int pos) { QObject::connect(ui->tabWidget, &QTabWidget::currentChanged, [=] (int pos) {
@ -287,14 +287,14 @@ void MainWindow::removeExtraAddresses() {
void MainWindow::maxAmountChecked(int checked) { void MainWindow::maxAmountChecked(int checked) {
if (checked == Qt::Checked) { if (checked == Qt::Checked) {
ui->Amount1->setReadOnly(true); ui->Amount1->setReadOnly(true);
if (rpc->getAllBalances() == nullptr) return; if (rpc->getAllBalances() == nullptr) return;
// Calculate maximum amount // Calculate maximum amount
double sumAllAmounts = 0.0; double sumAllAmounts = 0.0;
// Calculate all other amounts // Calculate all other amounts
int totalItems = ui->sendToWidgets->children().size() - 2; // The last one is a spacer, so ignore that int totalItems = ui->sendToWidgets->children().size() - 2; // The last one is a spacer, so ignore that
// Start counting the sum skipping the first one, because the MAX button is on the first one, and we don't // Start counting the sum skipping the first one, because the MAX button is on the first one, and we don't
// want to include it in the sum. // want to include it in the sum.
for (int i=1; i < totalItems; i++) { for (int i=1; i < totalItems; i++) {
auto amt = ui->sendToWidgets->findChild<QLineEdit*>(QString("Amount") % QString::number(i+1)); auto amt = ui->sendToWidgets->findChild<QLineEdit*>(QString("Amount") % QString::number(i+1));
sumAllAmounts += amt->text().toDouble(); sumAllAmounts += amt->text().toDouble();
@ -335,32 +335,32 @@ Tx MainWindow::createTxFromSendPage() {
bool MainWindow::confirmTx(Tx tx, ToFields devFee) { bool MainWindow::confirmTx(Tx tx, ToFields devFee) {
auto fnSplitAddressForWrap = [=] (const QString& a) -> QString { auto fnSplitAddressForWrap = [=] (const QString& a) -> QString {
if (!a.startsWith("z")) return a; if (!a.startsWith("z")) return a;
auto half = a.length() / 2; auto half = a.length() / 2;
auto splitted = a.left(half) + "\n" + a.right(a.length() - half); auto splitted = a.left(half) + "\n" + a.right(a.length() - half);
return splitted; return splitted;
}; };
// Show a confirmation dialog // Show a confirmation dialog
QDialog d(this); QDialog d(this);
Ui_confirm confirm; Ui_confirm confirm;
confirm.setupUi(&d); confirm.setupUi(&d);
// Remove all existing address/amt qlabels on the confirm dialog. // Remove all existing address/amt qlabels on the confirm dialog.
int totalConfirmAddrItems = confirm.sendToAddrs->children().size(); int totalConfirmAddrItems = confirm.sendToAddrs->children().size();
for (int i = 0; i < totalConfirmAddrItems / 3; i++) { for (int i = 0; i < totalConfirmAddrItems / 3; i++) {
auto addr = confirm.sendToAddrs->findChild<QLabel*>(QString("Addr") % QString::number(i+1)); auto addr = confirm.sendToAddrs->findChild<QLabel*>(QString("Addr") % QString::number(i+1));
auto amt = confirm.sendToAddrs->findChild<QLabel*>(QString("Amt") % QString::number(i+1)); auto amt = confirm.sendToAddrs->findChild<QLabel*>(QString("Amt") % QString::number(i+1));
auto memo = confirm.sendToAddrs->findChild<QLabel*>(QString("Memo") % QString::number(i+1)); auto memo = confirm.sendToAddrs->findChild<QLabel*>(QString("Memo") % QString::number(i+1));
auto amtUSD = confirm.sendToAddrs->findChild<QLabel*>(QString("AmtUSD") % QString::number(i+1)); auto amtUSD = confirm.sendToAddrs->findChild<QLabel*>(QString("AmtUSD") % QString::number(i+1));
delete memo; delete memo;
delete addr; delete addr;
delete amt; delete amt;
delete amtUSD; delete amtUSD;
} }
// Remove the fee labels // Remove the fee labels
delete confirm.sendToAddrs->findChild<QLabel*>("labelMinerFee"); delete confirm.sendToAddrs->findChild<QLabel*>("labelMinerFee");
@ -371,25 +371,25 @@ bool MainWindow::confirmTx(Tx tx, ToFields devFee) {
delete confirm.sendToAddrs->findChild<QLabel*>("devFee"); delete confirm.sendToAddrs->findChild<QLabel*>("devFee");
delete confirm.sendToAddrs->findChild<QLabel*>("devFeeUSD"); delete confirm.sendToAddrs->findChild<QLabel*>("devFeeUSD");
// For each addr/amt/memo, construct the JSON and also build the confirm dialog box // For each addr/amt/memo, construct the JSON and also build the confirm dialog box
for (int i=0; i < tx.toAddrs.size(); i++) { for (int i=0; i < tx.toAddrs.size(); i++) {
auto toAddr = tx.toAddrs[i]; auto toAddr = tx.toAddrs[i];
// Add new Address widgets instead of the same one. // Add new Address widgets instead of the same one.
{ {
// Address // Address
auto Addr = new QLabel(confirm.sendToAddrs); auto Addr = new QLabel(confirm.sendToAddrs);
Addr->setObjectName(QString("Addr") % QString::number(i + 1)); Addr->setObjectName(QString("Addr") % QString::number(i + 1));
Addr->setWordWrap(true); Addr->setWordWrap(true);
Addr->setText(fnSplitAddressForWrap(toAddr.addr)); Addr->setText(fnSplitAddressForWrap(toAddr.addr));
confirm.gridLayout->addWidget(Addr, i*2, 0, 1, 1); confirm.gridLayout->addWidget(Addr, i*2, 0, 1, 1);
// Amount (ZEC) // Amount (ZEC)
auto Amt = new QLabel(confirm.sendToAddrs); auto Amt = new QLabel(confirm.sendToAddrs);
Amt->setObjectName(QString("Amt") % QString::number(i + 1)); Amt->setObjectName(QString("Amt") % QString::number(i + 1));
Amt->setText(Settings::getInstance()->getZECDisplayFormat(toAddr.amount)); Amt->setText(Settings::getInstance()->getZECDisplayFormat(toAddr.amount));
Amt->setAlignment(Qt::AlignRight | Qt::AlignTrailing | Qt::AlignVCenter); Amt->setAlignment(Qt::AlignRight | Qt::AlignTrailing | Qt::AlignVCenter);
confirm.gridLayout->addWidget(Amt, i*2, 1, 1, 1); confirm.gridLayout->addWidget(Amt, i*2, 1, 1, 1);
// Amount (USD) // Amount (USD)
auto AmtUSD = new QLabel(confirm.sendToAddrs); auto AmtUSD = new QLabel(confirm.sendToAddrs);
@ -410,7 +410,7 @@ bool MainWindow::confirmTx(Tx tx, ToFields devFee) {
confirm.gridLayout->addWidget(Memo, (i*2)+1, 0, 1, 3); confirm.gridLayout->addWidget(Memo, (i*2)+1, 0, 1, 3);
} }
} }
} }
// Add two rows for fees // Add two rows for fees
@ -423,16 +423,16 @@ bool MainWindow::confirmTx(Tx tx, ToFields devFee) {
labelMinerFee->setText("Miner Fee"); labelMinerFee->setText("Miner Fee");
auto minerFee = new QLabel(confirm.sendToAddrs); auto minerFee = new QLabel(confirm.sendToAddrs);
QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
minerFee->setSizePolicy(sizePolicy); minerFee->setSizePolicy(sizePolicy);
minerFee->setObjectName(QStringLiteral("minerFee")); minerFee->setObjectName(QStringLiteral("minerFee"));
minerFee->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); minerFee->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
confirm.gridLayout->addWidget(minerFee, i, 1, 1, 1); confirm.gridLayout->addWidget(minerFee, i, 1, 1, 1);
minerFee->setText(Settings::getInstance()->getZECDisplayFormat(tx.fee)); minerFee->setText(Settings::getInstance()->getZECDisplayFormat(tx.fee));
auto minerFeeUSD = new QLabel(confirm.sendToAddrs); auto minerFeeUSD = new QLabel(confirm.sendToAddrs);
QSizePolicy sizePolicy1(QSizePolicy::Minimum, QSizePolicy::Preferred); QSizePolicy sizePolicy1(QSizePolicy::Minimum, QSizePolicy::Preferred);
minerFeeUSD->setSizePolicy(sizePolicy1); minerFeeUSD->setSizePolicy(sizePolicy1);
minerFeeUSD->setObjectName(QStringLiteral("minerFeeUSD")); minerFeeUSD->setObjectName(QStringLiteral("minerFeeUSD"));
minerFeeUSD->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); minerFeeUSD->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
confirm.gridLayout->addWidget(minerFeeUSD, i, 2, 1, 1); confirm.gridLayout->addWidget(minerFeeUSD, i, 2, 1, 1);
@ -451,7 +451,7 @@ bool MainWindow::confirmTx(Tx tx, ToFields devFee) {
fee ->setText(Settings::getInstance()->getZECDisplayFormat(Utils::getDevFee())); fee ->setText(Settings::getInstance()->getZECDisplayFormat(Utils::getDevFee()));
auto devFeeUSD = new QLabel(confirm.sendToAddrs); auto devFeeUSD = new QLabel(confirm.sendToAddrs);
devFeeUSD->setSizePolicy(sizePolicy1); devFeeUSD->setSizePolicy(sizePolicy1);
devFeeUSD->setObjectName(QStringLiteral("devFeeUSD")); devFeeUSD->setObjectName(QStringLiteral("devFeeUSD"));
devFeeUSD->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); devFeeUSD->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
confirm.gridLayout->addWidget(devFeeUSD, i+1, 2, 1, 1); confirm.gridLayout->addWidget(devFeeUSD, i+1, 2, 1, 1);
@ -459,17 +459,17 @@ bool MainWindow::confirmTx(Tx tx, ToFields devFee) {
} }
} }
// And FromAddress in the confirm dialog // And FromAddress in the confirm dialog
confirm.sendFrom->setText(fnSplitAddressForWrap(tx.fromAddr)); confirm.sendFrom->setText(fnSplitAddressForWrap(tx.fromAddr));
// Show the dialog and submit it if the user confirms // Show the dialog and submit it if the user confirms
if (d.exec() == QDialog::Accepted) { if (d.exec() == QDialog::Accepted) {
// Then delete the additional fields from the sendTo tab // Then delete the additional fields from the sendTo tab
removeExtraAddresses(); removeExtraAddresses();
return true; return true;
} else { } else {
return false; return false;
} }
} }
// Send button clicked // Send button clicked
@ -500,14 +500,14 @@ void MainWindow::sendButton() {
std::cout << std::setw(2) << params << std::endl; std::cout << std::setw(2) << params << std::endl;
// And send the Tx // And send the Tx
rpc->sendZTransaction(params, [=](const json& reply) { rpc->sendZTransaction(params, [=](const json& reply) {
QString opid = QString::fromStdString(reply.get<json::string_t>()); QString opid = QString::fromStdString(reply.get<json::string_t>());
ui->statusBar->showMessage("Computing Tx: " % opid); ui->statusBar->showMessage("Computing Tx: " % opid);
// And then start monitoring the transaction // And then start monitoring the transaction
rpc->addNewTxToWatch(tx, opid); rpc->addNewTxToWatch(tx, opid);
}); });
} }
} }
QString MainWindow::doSendTxValidations(Tx tx) { QString MainWindow::doSendTxValidations(Tx tx) {
@ -535,7 +535,7 @@ QString MainWindow::doSendTxValidations(Tx tx) {
} }
void MainWindow::cancelButton() { void MainWindow::cancelButton() {
removeExtraAddresses(); removeExtraAddresses();
// Back to the balances tab // Back to the balances tab
ui->tabWidget->setCurrentIndex(0); ui->tabWidget->setCurrentIndex(0);
} }

10
src/senttxstore.cpp

@ -41,8 +41,8 @@ QList<TransactionItem> SentTxStore::readSentTxFile() {
TransactionItem t{"send", (qint64)sentTx["datetime"].toVariant().toLongLong(), TransactionItem t{"send", (qint64)sentTx["datetime"].toVariant().toLongLong(),
sentTx["address"].toString(), sentTx["address"].toString(),
sentTx["txid"].toString(), sentTx["txid"].toString(),
sentTx["amount"].toDouble() + sentTx["fee"].toDouble(), sentTx["amount"].toDouble() + sentTx["fee"].toDouble(),
0, sentTx["from"].toString(), ""}; 0, sentTx["from"].toString(), ""};
items.push_back(t); items.push_back(t);
} }
@ -50,9 +50,9 @@ QList<TransactionItem> SentTxStore::readSentTxFile() {
} }
void SentTxStore::addToSentTx(Tx tx, QString txid) { void SentTxStore::addToSentTx(Tx tx, QString txid) {
// Save transactions only if the settings are allowed // Save transactions only if the settings are allowed
if (!Settings::getInstance()->getSaveZtxs()) if (!Settings::getInstance()->getSaveZtxs())
return; return;
// Also, only store outgoing Txs where the from address is a z-Addr. Else, regular zcashd // Also, only store outgoing Txs where the from address is a z-Addr. Else, regular zcashd
// stores it just fine // stores it just fine

222
src/settings.cpp

@ -6,59 +6,59 @@
Settings* Settings::instance = nullptr; Settings* Settings::instance = nullptr;
Settings::~Settings() { Settings::~Settings() {
delete defaults; delete defaults;
delete zcashconf; delete zcashconf;
delete uisettings; delete uisettings;
} }
bool Settings::getSaveZtxs() { bool Settings::getSaveZtxs() {
// Load from the QT Settings. // Load from the QT Settings.
return QSettings().value("options/savesenttx", true).toBool(); return QSettings().value("options/savesenttx", true).toBool();
} }
void Settings::setSaveZtxs(bool save) { void Settings::setSaveZtxs(bool save) {
QSettings().setValue("options/savesenttx", save); QSettings().setValue("options/savesenttx", save);
} }
Settings* Settings::init() { Settings* Settings::init() {
if (instance == nullptr) if (instance == nullptr)
instance = new Settings(); instance = new Settings();
// There are 3 possible configurations // There are 3 possible configurations
// 1. The defaults // 1. The defaults
instance->defaults = new Config{ "127.0.0.1", "8232", "", "" }; instance->defaults = new Config{ "127.0.0.1", "8232", "", "" };
// 2. From the UI settings // 2. From the UI settings
auto settingsFound = instance->loadFromSettings(); auto settingsFound = instance->loadFromSettings();
// 3. From the zcash.conf file // 3. From the zcash.conf file
auto confFound = instance->loadFromFile(); auto confFound = instance->loadFromFile();
// zcash.conf (#3) is first priority if it exists // zcash.conf (#3) is first priority if it exists
if (confFound) { if (confFound) {
instance->currentConfig = instance->zcashconf; instance->currentConfig = instance->zcashconf;
} }
else if (settingsFound) { else if (settingsFound) {
instance->currentConfig = instance->uisettings; instance->currentConfig = instance->uisettings;
} }
else { else {
instance->currentConfig = instance->defaults; instance->currentConfig = instance->defaults;
} }
return instance; return instance;
} }
Settings* Settings::getInstance() { Settings* Settings::getInstance() {
return instance; return instance;
} }
QString Settings::getHost() { QString Settings::getHost() {
return currentConfig->host; return currentConfig->host;
} }
QString Settings::getPort() { QString Settings::getPort() {
return currentConfig->port; return currentConfig->port;
} }
@ -67,155 +67,155 @@ QString Settings::getUsernamePassword() {
} }
bool Settings::loadFromSettings() { bool Settings::loadFromSettings() {
delete uisettings; delete uisettings;
// Load from the QT Settings. // Load from the QT Settings.
QSettings s; QSettings s;
auto host = s.value("connection/host").toString(); auto host = s.value("connection/host").toString();
auto port = s.value("connection/port").toString(); auto port = s.value("connection/port").toString();
auto username = s.value("connection/rpcuser").toString(); auto username = s.value("connection/rpcuser").toString();
auto password = s.value("connection/rpcpassword").toString(); auto password = s.value("connection/rpcpassword").toString();
uisettings = new Config{host, port, username, password}; uisettings = new Config{host, port, username, password};
return !username.isEmpty(); return !username.isEmpty();
} }
void Settings::saveSettings(const QString& host, const QString& port, const QString& username, const QString& password) { void Settings::saveSettings(const QString& host, const QString& port, const QString& username, const QString& password) {
QSettings s; QSettings s;
s.setValue("connection/host", host); s.setValue("connection/host", host);
s.setValue("connection/port", port); s.setValue("connection/port", port);
s.setValue("connection/rpcuser", username); s.setValue("connection/rpcuser", username);
s.setValue("connection/rpcpassword", password); s.setValue("connection/rpcpassword", password);
s.sync(); s.sync();
// re-init to load correct settings // re-init to load correct settings
init(); init();
} }
bool Settings::loadFromFile() { bool Settings::loadFromFile() {
delete zcashconf; delete zcashconf;
#ifdef Q_OS_LINUX #ifdef Q_OS_LINUX
confLocation = QStandardPaths::locate(QStandardPaths::HomeLocation, ".zcash/zcash.conf"); confLocation = QStandardPaths::locate(QStandardPaths::HomeLocation, ".zcash/zcash.conf");
#elif defined(Q_OS_DARWIN) #elif defined(Q_OS_DARWIN)
confLocation = QStandardPaths::locate(QStandardPaths::HomeLocation, "/Library/Application Support/Zcash/zcash.conf"); confLocation = QStandardPaths::locate(QStandardPaths::HomeLocation, "/Library/Application Support/Zcash/zcash.conf");
#else #else
confLocation = QStandardPaths::locate(QStandardPaths::AppDataLocation, "../../Zcash/zcash.conf"); confLocation = QStandardPaths::locate(QStandardPaths::AppDataLocation, "../../Zcash/zcash.conf");
#endif #endif
confLocation = QDir::cleanPath(confLocation); confLocation = QDir::cleanPath(confLocation);
if (confLocation.isNull()) { if (confLocation.isNull()) {
// No zcash file, just return with nothing // No zcash file, just return with nothing
return false; return false;
} }
QFile file(confLocation); QFile file(confLocation);
if (!file.open(QIODevice::ReadOnly)) { if (!file.open(QIODevice::ReadOnly)) {
qDebug() << file.errorString(); qDebug() << file.errorString();
return false; return false;
} }
QTextStream in(&file); QTextStream in(&file);
zcashconf = new Config(); zcashconf = new Config();
zcashconf->host = defaults->host; zcashconf->host = defaults->host;
while (!in.atEnd()) { while (!in.atEnd()) {
QString line = in.readLine(); QString line = in.readLine();
auto s = line.indexOf("="); auto s = line.indexOf("=");
QString name = line.left(s).trimmed().toLower(); QString name = line.left(s).trimmed().toLower();
QString value = line.right(line.length() - s - 1).trimmed(); QString value = line.right(line.length() - s - 1).trimmed();
if (name == "rpcuser") { if (name == "rpcuser") {
zcashconf->rpcuser = value; zcashconf->rpcuser = value;
} }
if (name == "rpcpassword") { if (name == "rpcpassword") {
zcashconf->rpcpassword = value; zcashconf->rpcpassword = value;
} }
if (name == "rpcport") { if (name == "rpcport") {
zcashconf->port = value; zcashconf->port = value;
} }
if (name == "testnet" && if (name == "testnet" &&
value == "1" && value == "1" &&
zcashconf->port.isEmpty()) { zcashconf->port.isEmpty()) {
zcashconf->port = "18232"; zcashconf->port = "18232";
} }
} }
// If rpcport is not in the file, and it was not set by the testnet=1 flag, then go to default // If rpcport is not in the file, and it was not set by the testnet=1 flag, then go to default
if (zcashconf->port.isEmpty()) zcashconf->port = defaults->port; if (zcashconf->port.isEmpty()) zcashconf->port = defaults->port;
file.close(); file.close();
return true; return true;
} }
bool Settings::isTestnet() { bool Settings::isTestnet() {
return _isTestnet; return _isTestnet;
} }
void Settings::setTestnet(bool isTestnet) { void Settings::setTestnet(bool isTestnet) {
this->_isTestnet = isTestnet; this->_isTestnet = isTestnet;
} }
bool Settings::isSaplingAddress(QString addr) { bool Settings::isSaplingAddress(QString addr) {
return ( isTestnet() && addr.startsWith("ztestsapling")) || return ( isTestnet() && addr.startsWith("ztestsapling")) ||
(!isTestnet() && addr.startsWith("zs")); (!isTestnet() && addr.startsWith("zs"));
} }
bool Settings::isSproutAddress(QString addr) { bool Settings::isSproutAddress(QString addr) {
return isZAddress(addr) && !isSaplingAddress(addr); return isZAddress(addr) && !isSaplingAddress(addr);
} }
bool Settings::isZAddress(QString addr) { bool Settings::isZAddress(QString addr) {
return addr.startsWith("z"); return addr.startsWith("z");
} }
bool Settings::isSyncing() { bool Settings::isSyncing() {
return _isSyncing; return _isSyncing;
} }
void Settings::setSyncing(bool syncing) { void Settings::setSyncing(bool syncing) {
this->_isSyncing = syncing; this->_isSyncing = syncing;
} }
int Settings::getBlockNumber() { int Settings::getBlockNumber() {
return this->_blockNumber; return this->_blockNumber;
} }
void Settings::setBlockNumber(int number) { void Settings::setBlockNumber(int number) {
this->_blockNumber = number; this->_blockNumber = number;
} }
bool Settings::isSaplingActive() { bool Settings::isSaplingActive() {
return (isTestnet() && getBlockNumber() > 280000) || return (isTestnet() && getBlockNumber() > 280000) ||
(!isTestnet() && getBlockNumber() > 419200); (!isTestnet() && getBlockNumber() > 419200);
} }
double Settings::getZECPrice() { double Settings::getZECPrice() {
return zecPrice; return zecPrice;
} }
QString Settings::getUSDFormat(double bal) { QString Settings::getUSDFormat(double bal) {
if (!isTestnet() && getZECPrice() > 0) if (!isTestnet() && getZECPrice() > 0)
return "$" + QLocale(QLocale::English).toString(bal * getZECPrice(), 'f', 2); return "$" + QLocale(QLocale::English).toString(bal * getZECPrice(), 'f', 2);
else else
return QString(); return QString();
} }
QString Settings::getZECDisplayFormat(double bal) { QString Settings::getZECDisplayFormat(double bal) {
return QString::number(bal, 'g', 8) % " " % Utils::getTokenName(); return QString::number(bal, 'g', 8) % " " % Utils::getTokenName();
} }
QString Settings::getZECUSDDisplayFormat(double bal) { QString Settings::getZECUSDDisplayFormat(double bal) {
auto usdFormat = getUSDFormat(bal); auto usdFormat = getUSDFormat(bal);
if (!usdFormat.isEmpty()) if (!usdFormat.isEmpty())
return getZECDisplayFormat(bal) % " (" % getUSDFormat(bal) % ")"; return getZECDisplayFormat(bal) % " (" % getUSDFormat(bal) % ")";
else else
return getZECDisplayFormat(bal); return getZECDisplayFormat(bal);
} }

66
src/settings.h

@ -4,10 +4,10 @@
#include "precompiled.h" #include "precompiled.h"
struct Config { struct Config {
QString host; QString host;
QString port; QString port;
QString rpcuser; QString rpcuser;
QString rpcpassword; QString rpcpassword;
}; };
class Settings class Settings
@ -17,61 +17,61 @@ public:
static Settings* getInstance(); static Settings* getInstance();
QString getUsernamePassword(); QString getUsernamePassword();
QString getHost(); QString getHost();
QString getPort(); QString getPort();
bool loadFromSettings(); bool loadFromSettings();
bool loadFromFile(); bool loadFromFile();
void saveSettings(const QString& host, const QString& port, const QString& username, const QString& password); void saveSettings(const QString& host, const QString& port, const QString& username, const QString& password);
bool isTestnet(); bool isTestnet();
void setTestnet(bool isTestnet); void setTestnet(bool isTestnet);
bool isSaplingAddress(QString addr); bool isSaplingAddress(QString addr);
bool isSproutAddress(QString addr); bool isSproutAddress(QString addr);
bool isZAddress(QString addr); bool isZAddress(QString addr);
bool isSyncing(); bool isSyncing();
void setSyncing(bool syncing); void setSyncing(bool syncing);
int getBlockNumber(); int getBlockNumber();
void setBlockNumber(int number); void setBlockNumber(int number);
bool getSaveZtxs(); bool getSaveZtxs();
void setSaveZtxs(bool save); void setSaveZtxs(bool save);
bool isSaplingActive(); bool isSaplingActive();
const QString& getZcashdConfLocation() { return confLocation; } const QString& getZcashdConfLocation() { return confLocation; }
void setZECPrice(double p) { zecPrice = p; } void setZECPrice(double p) { zecPrice = p; }
double getZECPrice(); double getZECPrice();
QString getUSDFormat (double bal); QString getUSDFormat (double bal);
QString getZECDisplayFormat (double bal); QString getZECDisplayFormat (double bal);
QString getZECUSDDisplayFormat (double bal); QString getZECUSDDisplayFormat (double bal);
private: private:
// This class can only be accessed through Settings::getInstance() // This class can only be accessed through Settings::getInstance()
Settings() = default; Settings() = default;
~Settings(); ~Settings();
static Settings* instance; static Settings* instance;
Config* currentConfig; Config* currentConfig;
Config* defaults = nullptr; Config* defaults = nullptr;
Config* zcashconf = nullptr; Config* zcashconf = nullptr;
Config* uisettings = nullptr; Config* uisettings = nullptr;
QString confLocation; QString confLocation;
bool _isTestnet = false; bool _isTestnet = false;
bool _isSyncing = false; bool _isSyncing = false;
int _blockNumber = 0; int _blockNumber = 0;
double zecPrice = 0.0; double zecPrice = 0.0;
}; };
#endif // SETTINGS_H #endif // SETTINGS_H

546
src/turnstile.cpp

@ -10,18 +10,18 @@
using json = nlohmann::json; using json = nlohmann::json;
Turnstile::Turnstile(RPC* _rpc, MainWindow* mainwindow) { Turnstile::Turnstile(RPC* _rpc, MainWindow* mainwindow) {
this->rpc = _rpc; this->rpc = _rpc;
this->mainwindow = mainwindow; this->mainwindow = mainwindow;
} }
Turnstile::~Turnstile() { Turnstile::~Turnstile() {
} }
void printPlan(QList<TurnstileMigrationItem> plan) { void printPlan(QList<TurnstileMigrationItem> plan) {
for (auto item : plan) { for (auto item : plan) {
//qDebug() << item.fromAddr << item.intTAddr //qDebug() << item.fromAddr << item.intTAddr
// << item.destAddr << item.amount << item.blockNumber << item.status; // << item.destAddr << item.amount << item.blockNumber << item.status;
} }
} }
QString Turnstile::writeableFile() { QString Turnstile::writeableFile() {
@ -39,333 +39,333 @@ QString Turnstile::writeableFile() {
} }
void Turnstile::removeFile() { void Turnstile::removeFile() {
QFile(writeableFile()).remove(); QFile(writeableFile()).remove();
} }
// Data stream write/read methods for migration items // Data stream write/read methods for migration items
QDataStream &operator<<(QDataStream& ds, const TurnstileMigrationItem& item) { QDataStream &operator<<(QDataStream& ds, const TurnstileMigrationItem& item) {
return ds << QString("v1") << item.fromAddr << item.intTAddr return ds << QString("v1") << item.fromAddr << item.intTAddr
<< item.destAddr << item.amount << item.blockNumber << item.status; << item.destAddr << item.amount << item.blockNumber << item.status;
} }
QDataStream &operator>>(QDataStream& ds, TurnstileMigrationItem& item) { QDataStream &operator>>(QDataStream& ds, TurnstileMigrationItem& item) {
QString version; QString version;
return ds >> version >> item.fromAddr >> item.intTAddr return ds >> version >> item.fromAddr >> item.intTAddr
>> item.destAddr >> item.amount >> item.blockNumber >> item.status; >> item.destAddr >> item.amount >> item.blockNumber >> item.status;
} }
void Turnstile::writeMigrationPlan(QList<TurnstileMigrationItem> plan) { void Turnstile::writeMigrationPlan(QList<TurnstileMigrationItem> plan) {
//qDebug() << QString("Writing plan"); //qDebug() << QString("Writing plan");
printPlan(plan); printPlan(plan);
QFile file(writeableFile()); QFile file(writeableFile());
file.open(QIODevice::ReadWrite | QIODevice::Truncate); file.open(QIODevice::ReadWrite | QIODevice::Truncate);
QDataStream out(&file); // we will serialize the data into the file QDataStream out(&file); // we will serialize the data into the file
out << plan; out << plan;
file.close(); file.close();
} }
QList<TurnstileMigrationItem> Turnstile::readMigrationPlan() { QList<TurnstileMigrationItem> Turnstile::readMigrationPlan() {
QFile file(writeableFile()); QFile file(writeableFile());
QList<TurnstileMigrationItem> plan; QList<TurnstileMigrationItem> plan;
if (!file.exists()) return plan; if (!file.exists()) return plan;
file.open(QIODevice::ReadOnly); file.open(QIODevice::ReadOnly);
QDataStream in(&file); // read the data serialized from the file QDataStream in(&file); // read the data serialized from the file
in >> plan; in >> plan;
file.close(); file.close();
// Sort to see when the next step is. // Sort to see when the next step is.
std::sort(plan.begin(), plan.end(), [&] (auto a, auto b) { std::sort(plan.begin(), plan.end(), [&] (auto a, auto b) {
return a.blockNumber < b.blockNumber; return a.blockNumber < b.blockNumber;
}); });
return plan; return plan;
} }
void Turnstile::planMigration(QString zaddr, QString destAddr, int numsplits, int numBlocks) { void Turnstile::planMigration(QString zaddr, QString destAddr, int numsplits, int numBlocks) {
// First, get the balance and split up the amounts // First, get the balance and split up the amounts
auto bal = rpc->getAllBalances()->value(zaddr); auto bal = rpc->getAllBalances()->value(zaddr);
auto splits = splitAmount(bal, numsplits); auto splits = splitAmount(bal, numsplits);
// Then, generate an intermediate t-Address for each part using getBatchRPC // Then, generate an intermediate t-Address for each part using getBatchRPC
rpc->getBatchRPC<double>(splits, rpc->getBatchRPC<double>(splits,
[=] (double /*unused*/) { [=] (double /*unused*/) {
json payload = { json payload = {
{"jsonrpc", "1.0"}, {"jsonrpc", "1.0"},
{"id", "someid"}, {"id", "someid"},
{"method", "getnewaddress"}, {"method", "getnewaddress"},
}; };
return payload; return payload;
}, },
[=] (QMap<double, json>* newAddrs) { [=] (QMap<double, json>* newAddrs) {
// Get block numbers // Get block numbers
auto curBlock = Settings::getInstance()->getBlockNumber(); auto curBlock = Settings::getInstance()->getBlockNumber();
auto blockNumbers = getBlockNumbers(curBlock, curBlock + numBlocks, splits.size()); auto blockNumbers = getBlockNumbers(curBlock, curBlock + numBlocks, splits.size());
// Assign the amounts to the addresses. // Assign the amounts to the addresses.
QList<TurnstileMigrationItem> migItems; QList<TurnstileMigrationItem> migItems;
for (int i=0; i < splits.size(); i++) { for (int i=0; i < splits.size(); i++) {
auto tAddr = newAddrs->values()[i].get<json::string_t>(); auto tAddr = newAddrs->values()[i].get<json::string_t>();
auto item = TurnstileMigrationItem { zaddr, QString::fromStdString(tAddr), destAddr, auto item = TurnstileMigrationItem { zaddr, QString::fromStdString(tAddr), destAddr,
blockNumbers[i], splits[i], blockNumbers[i], splits[i],
TurnstileMigrationItemStatus::NotStarted }; TurnstileMigrationItemStatus::NotStarted };
migItems.push_back(item); migItems.push_back(item);
} }
// The first migration is shifted to the current block, so the user sees something // The first migration is shifted to the current block, so the user sees something
// happening immediately // happening immediately
if (migItems.size() == 0) { if (migItems.size() == 0) {
// Show error and abort // Show error and abort
QMessageBox::warning(mainwindow, QMessageBox::warning(mainwindow,
"Locked funds", "Could not initiate migration.\nYou either have unconfirmed funds or the balance is too low for an automatic migration."); "Locked funds", "Could not initiate migration.\nYou either have unconfirmed funds or the balance is too low for an automatic migration.");
return; return;
} }
migItems[0].blockNumber = curBlock; migItems[0].blockNumber = curBlock;
std::sort(migItems.begin(), migItems.end(), [&] (auto a, auto b) { std::sort(migItems.begin(), migItems.end(), [&] (auto a, auto b) {
return a.blockNumber < b.blockNumber; return a.blockNumber < b.blockNumber;
}); });
writeMigrationPlan(migItems); writeMigrationPlan(migItems);
rpc->refresh(true); // Force refresh, to start the migration immediately rpc->refresh(true); // Force refresh, to start the migration immediately
} }
); );
} }
QList<int> Turnstile::getBlockNumbers(int start, int end, int count) { QList<int> Turnstile::getBlockNumbers(int start, int end, int count) {
QList<int> blocks; QList<int> blocks;
// Generate 'count' numbers between [start, end] // Generate 'count' numbers between [start, end]
for (int i=0; i < count; i++) { for (int i=0; i < count; i++) {
auto blk = (std::rand() % (end - start)) + start; auto blk = (std::rand() % (end - start)) + start;
blocks.push_back(blk); blocks.push_back(blk);
} }
return blocks; return blocks;
} }
// Need at least 0.0005 ZEC for this // Need at least 0.0005 ZEC for this
double Turnstile::minMigrationAmount = 0.0005; double Turnstile::minMigrationAmount = 0.0005;
QList<double> Turnstile::splitAmount(double amount, int parts) { QList<double> Turnstile::splitAmount(double amount, int parts) {
QList<double> amounts; QList<double> amounts;
if (amount < minMigrationAmount) if (amount < minMigrationAmount)
return amounts; return amounts;
fillAmounts(amounts, amount, parts); fillAmounts(amounts, amount, parts);
//qDebug() << amounts; //qDebug() << amounts;
// Ensure they all add up! // Ensure they all add up!
double sumofparts = 0; double sumofparts = 0;
for (auto a : amounts) { for (auto a : amounts) {
sumofparts += a; sumofparts += a;
} }
// Add the Tx fees // Add the Tx fees
sumofparts += amounts.size() * Utils::getMinerFee(); sumofparts += amounts.size() * Utils::getMinerFee();
//qDebug() << QString::number(sumofparts, 'f', 8) << QString::number(amount, 'f', 8); //qDebug() << QString::number(sumofparts, 'f', 8) << QString::number(amount, 'f', 8);
//Q_ASSERT(QString::number(sumofparts, 'f', 8) == QString::number(amount, 'f', 8)); //Q_ASSERT(QString::number(sumofparts, 'f', 8) == QString::number(amount, 'f', 8));
return amounts; return amounts;
} }
void Turnstile::fillAmounts(QList<double>& amounts, double amount, int count) { void Turnstile::fillAmounts(QList<double>& amounts, double amount, int count) {
if (count == 1 || amount < 0.01) { if (count == 1 || amount < 0.01) {
// Also account for the fees needed to send all these transactions // Also account for the fees needed to send all these transactions
auto actual = amount - (Utils::getMinerFee() * (amounts.size() + 1)); auto actual = amount - (Utils::getMinerFee() * (amounts.size() + 1));
amounts.push_back(actual); amounts.push_back(actual);
return; return;
} }
// Get a random amount off the amount (between half and full) and call recursively. // Get a random amount off the amount (between half and full) and call recursively.
// Multiply by hundered, because we'll operate on 0.01 ZEC minimum. We'll divide by 100 later // Multiply by hundered, because we'll operate on 0.01 ZEC minimum. We'll divide by 100 later
double curAmount = std::rand() % (int)std::floor(amount * 100); double curAmount = std::rand() % (int)std::floor(amount * 100);
// Try to round it off // Try to round it off
auto places = (int)std::floor(std::log10(curAmount)); auto places = (int)std::floor(std::log10(curAmount));
if (places > 0) { if (places > 0) {
auto a = std::pow(10, places); auto a = std::pow(10, places);
curAmount = std::floor(curAmount / a) * a; curAmount = std::floor(curAmount / a) * a;
} }
// And divide by 100 // And divide by 100
curAmount = curAmount / 100; curAmount = curAmount / 100;
if (curAmount > 0) if (curAmount > 0)
amounts.push_back(curAmount); amounts.push_back(curAmount);
fillAmounts(amounts, amount - curAmount, count - 1); fillAmounts(amounts, amount - curAmount, count - 1);
} }
QList<TurnstileMigrationItem>::Iterator QList<TurnstileMigrationItem>::Iterator
Turnstile::getNextStep(QList<TurnstileMigrationItem>& plan) { Turnstile::getNextStep(QList<TurnstileMigrationItem>& plan) {
// Get to the next unexecuted step // Get to the next unexecuted step
auto fnIsEligibleItem = [&] (auto item) { auto fnIsEligibleItem = [&] (auto item) {
return item.status == TurnstileMigrationItemStatus::NotStarted || return item.status == TurnstileMigrationItemStatus::NotStarted ||
item.status == TurnstileMigrationItemStatus::SentToT; item.status == TurnstileMigrationItemStatus::SentToT;
}; };
// Find the next step // Find the next step
auto nextStep = std::find_if(plan.begin(), plan.end(), fnIsEligibleItem); auto nextStep = std::find_if(plan.begin(), plan.end(), fnIsEligibleItem);
return nextStep; return nextStep;
} }
bool Turnstile::isMigrationPresent() { bool Turnstile::isMigrationPresent() {
auto plan = readMigrationPlan(); auto plan = readMigrationPlan();
if (plan.isEmpty()) return false; if (plan.isEmpty()) return false;
return true; return true;
} }
ProgressReport Turnstile::getPlanProgress() { ProgressReport Turnstile::getPlanProgress() {
auto plan = readMigrationPlan(); auto plan = readMigrationPlan();
auto nextStep = getNextStep(plan); auto nextStep = getNextStep(plan);
auto step = std::distance(plan.begin(), nextStep) * 2; // 2 steps per item auto step = std::distance(plan.begin(), nextStep) * 2; // 2 steps per item
if (nextStep != plan.end() && if (nextStep != plan.end() &&
nextStep->status == TurnstileMigrationItemStatus::SentToT) nextStep->status == TurnstileMigrationItemStatus::SentToT)
step++; step++;
auto total = plan.size(); auto total = plan.size();
auto nextBlock = nextStep == plan.end() ? 0 : nextStep->blockNumber; auto nextBlock = nextStep == plan.end() ? 0 : nextStep->blockNumber;
bool hasErrors = std::find_if(plan.begin(), plan.end(), [=] (auto i) { bool hasErrors = std::find_if(plan.begin(), plan.end(), [=] (auto i) {
return i.status == TurnstileMigrationItemStatus::NotEnoughBalance || return i.status == TurnstileMigrationItemStatus::NotEnoughBalance ||
i.status == TurnstileMigrationItemStatus::UnknownError; i.status == TurnstileMigrationItemStatus::UnknownError;
}) != plan.end(); }) != plan.end();
auto stepData = (nextStep == plan.end() ? std::prev(nextStep) : nextStep); auto stepData = (nextStep == plan.end() ? std::prev(nextStep) : nextStep);
return ProgressReport{(int)step, total*2, nextBlock, hasErrors, stepData->fromAddr, stepData->destAddr, stepData->intTAddr}; return ProgressReport{(int)step, total*2, nextBlock, hasErrors, stepData->fromAddr, stepData->destAddr, stepData->intTAddr};
} }
void Turnstile::executeMigrationStep() { void Turnstile::executeMigrationStep() {
auto plan = readMigrationPlan(); auto plan = readMigrationPlan();
//qDebug() << QString("Executing step"); //qDebug() << QString("Executing step");
printPlan(plan); printPlan(plan);
// Get to the next unexecuted step // Get to the next unexecuted step
auto fnIsEligibleItem = [&] (auto item) { auto fnIsEligibleItem = [&] (auto item) {
return item.status == TurnstileMigrationItemStatus::NotStarted || return item.status == TurnstileMigrationItemStatus::NotStarted ||
item.status == TurnstileMigrationItemStatus::SentToT; item.status == TurnstileMigrationItemStatus::SentToT;
}; };
// Fn to find if there are any unconfirmed funds for this address. // Fn to find if there are any unconfirmed funds for this address.
auto fnHasUnconfirmed = [=] (QString addr) { auto fnHasUnconfirmed = [=] (QString addr) {
auto utxoset = rpc->getUTXOs(); auto utxoset = rpc->getUTXOs();
return std::find_if(utxoset->begin(), utxoset->end(), [=] (auto utxo) { return std::find_if(utxoset->begin(), utxoset->end(), [=] (auto utxo) {
return utxo.address == addr && utxo.confirmations == 0 && utxo.spendable; return utxo.address == addr && utxo.confirmations == 0 && utxo.spendable;
}) != utxoset->end(); }) != utxoset->end();
}; };
// Find the next step // Find the next step
auto nextStep = std::find_if(plan.begin(), plan.end(), fnIsEligibleItem); auto nextStep = std::find_if(plan.begin(), plan.end(), fnIsEligibleItem);
if (nextStep == plan.end()) if (nextStep == plan.end())
return; // Nothing to do return; // Nothing to do
if (nextStep->blockNumber > Settings::getInstance()->getBlockNumber()) if (nextStep->blockNumber > Settings::getInstance()->getBlockNumber())
return; return;
// Is this the last step for this address? // Is this the last step for this address?
auto lastStep = std::find_if(std::next(nextStep), plan.end(), fnIsEligibleItem) == plan.end(); auto lastStep = std::find_if(std::next(nextStep), plan.end(), fnIsEligibleItem) == plan.end();
// Execute this step // Execute this step
if (nextStep->status == TurnstileMigrationItemStatus::NotStarted) { if (nextStep->status == TurnstileMigrationItemStatus::NotStarted) {
// Does this z addr have enough balance? // Does this z addr have enough balance?
if (fnHasUnconfirmed(nextStep->fromAddr)) { if (fnHasUnconfirmed(nextStep->fromAddr)) {
//qDebug() << QString("unconfirmed, waiting"); //qDebug() << QString("unconfirmed, waiting");
return; return;
} }
auto balance = rpc->getAllBalances()->value(nextStep->fromAddr); auto balance = rpc->getAllBalances()->value(nextStep->fromAddr);
if (nextStep->amount > balance) { if (nextStep->amount > balance) {
qDebug() << "Not enough balance!"; qDebug() << "Not enough balance!";
nextStep->status = TurnstileMigrationItemStatus::NotEnoughBalance; nextStep->status = TurnstileMigrationItemStatus::NotEnoughBalance;
writeMigrationPlan(plan); writeMigrationPlan(plan);
return; return;
} }
auto to = ToFields{ nextStep->intTAddr, nextStep->amount, "", "" }; auto to = ToFields{ nextStep->intTAddr, nextStep->amount, "", "" };
// If this is the last step, then send the remaining amount instead of the actual amount. // If this is the last step, then send the remaining amount instead of the actual amount.
if (lastStep) { if (lastStep) {
auto remainingAmount = balance - Utils::getMinerFee(); auto remainingAmount = balance - Utils::getMinerFee();
if (remainingAmount > 0) { if (remainingAmount > 0) {
to.amount = remainingAmount; to.amount = remainingAmount;
} }
} }
// Create the Tx // Create the Tx
auto tx = Tx{ nextStep->fromAddr, { to }, Utils::getMinerFee() }; auto tx = Tx{ nextStep->fromAddr, { to }, Utils::getMinerFee() };
// And send it // And send it
doSendTx(tx, [=] () { doSendTx(tx, [=] () {
// Update status and write plan to disk // Update status and write plan to disk
nextStep->status = TurnstileMigrationItemStatus::SentToT; nextStep->status = TurnstileMigrationItemStatus::SentToT;
writeMigrationPlan(plan); writeMigrationPlan(plan);
}); });
} else if (nextStep->status == TurnstileMigrationItemStatus::SentToT) { } else if (nextStep->status == TurnstileMigrationItemStatus::SentToT) {
// First thing to do is check to see if the funds are confirmed. // First thing to do is check to see if the funds are confirmed.
// We'll check both the original sprout address and the intermediate T addr for safety. // We'll check both the original sprout address and the intermediate T addr for safety.
if (fnHasUnconfirmed(nextStep->intTAddr) || fnHasUnconfirmed(nextStep->fromAddr)) { if (fnHasUnconfirmed(nextStep->intTAddr) || fnHasUnconfirmed(nextStep->fromAddr)) {
//qDebug() << QString("unconfirmed, waiting"); //qDebug() << QString("unconfirmed, waiting");
return; return;
} }
if (!rpc->getAllBalances()->keys().contains(nextStep->intTAddr)) { if (!rpc->getAllBalances()->keys().contains(nextStep->intTAddr)) {
qDebug() << QString("The intermediate Taddress doesn't have balance, even though it is confirmed"); qDebug() << QString("The intermediate Taddress doesn't have balance, even though it is confirmed");
return; return;
} }
// Send it to the final destination address. // Send it to the final destination address.
auto bal = rpc->getAllBalances()->value(nextStep->intTAddr); auto bal = rpc->getAllBalances()->value(nextStep->intTAddr);
auto sendAmt = bal - Utils::getMinerFee(); auto sendAmt = bal - Utils::getMinerFee();
if (sendAmt < 0) { if (sendAmt < 0) {
qDebug() << "Not enough balance!." << bal << ":" << sendAmt; qDebug() << "Not enough balance!." << bal << ":" << sendAmt;
nextStep->status = TurnstileMigrationItemStatus::NotEnoughBalance; nextStep->status = TurnstileMigrationItemStatus::NotEnoughBalance;
writeMigrationPlan(plan); writeMigrationPlan(plan);
return; return;
} }
QList<ToFields> to = { ToFields{ nextStep->destAddr, sendAmt, "", "" } }; QList<ToFields> to = { ToFields{ nextStep->destAddr, sendAmt, "", "" } };
// Create the Tx // Create the Tx
auto tx = Tx{ nextStep->intTAddr, to, Utils::getMinerFee() }; auto tx = Tx{ nextStep->intTAddr, to, Utils::getMinerFee() };
// And send it // And send it
doSendTx(tx, [=] () { doSendTx(tx, [=] () {
// Update status and write plan to disk // Update status and write plan to disk
nextStep->status = TurnstileMigrationItemStatus::SentToZS; nextStep->status = TurnstileMigrationItemStatus::SentToZS;
writeMigrationPlan(plan); writeMigrationPlan(plan);
}); });
} }
} }
void Turnstile::doSendTx(Tx tx, std::function<void(void)> cb) { void Turnstile::doSendTx(Tx tx, std::function<void(void)> cb) {
json params = json::array(); json params = json::array();
rpc->fillTxJsonParams(params, tx); rpc->fillTxJsonParams(params, tx);
std::cout << std::setw(2) << params << std::endl; std::cout << std::setw(2) << params << std::endl;
rpc->sendZTransaction(params, [=] (const json& reply) { rpc->sendZTransaction(params, [=] (const json& reply) {
QString opid = QString::fromStdString(reply.get<json::string_t>()); QString opid = QString::fromStdString(reply.get<json::string_t>());
//qDebug() << opid; //qDebug() << opid;
mainwindow->ui->statusBar->showMessage("Computing Tx: " % opid); mainwindow->ui->statusBar->showMessage("Computing Tx: " % opid);
// And then start monitoring the transaction // And then start monitoring the transaction
rpc->addNewTxToWatch(tx, opid); rpc->addNewTxToWatch(tx, opid);
cb(); cb();
}); });
} }

74
src/turnstile.h

@ -9,62 +9,62 @@ struct Tx;
struct TurnstileMigrationItem { struct TurnstileMigrationItem {
QString fromAddr; QString fromAddr;
QString intTAddr; QString intTAddr;
QString destAddr; QString destAddr;
int blockNumber; int blockNumber;
double amount; double amount;
int status; int status;
}; };
enum TurnstileMigrationItemStatus { enum TurnstileMigrationItemStatus {
NotStarted = 0, NotStarted = 0,
SentToT, SentToT,
SentToZS, SentToZS,
NotEnoughBalance, NotEnoughBalance,
UnknownError UnknownError
}; };
struct ProgressReport { struct ProgressReport {
int step; int step;
int totalSteps; int totalSteps;
int nextBlock; int nextBlock;
bool hasErrors; bool hasErrors;
QString from; QString from;
QString to; QString to;
QString via; QString via;
}; };
class Turnstile class Turnstile
{ {
public: public:
Turnstile(RPC* _rpc, MainWindow* mainwindow); Turnstile(RPC* _rpc, MainWindow* mainwindow);
~Turnstile(); ~Turnstile();
void planMigration(QString zaddr, QString destAddr, int splits, int numBlocks); void planMigration(QString zaddr, QString destAddr, int splits, int numBlocks);
QList<double> splitAmount(double amount, int parts); QList<double> splitAmount(double amount, int parts);
void fillAmounts(QList<double>& amounts, double amount, int count); void fillAmounts(QList<double>& amounts, double amount, int count);
QList<TurnstileMigrationItem> readMigrationPlan(); QList<TurnstileMigrationItem> readMigrationPlan();
void writeMigrationPlan(QList<TurnstileMigrationItem> plan); void writeMigrationPlan(QList<TurnstileMigrationItem> plan);
void removeFile(); void removeFile();
void executeMigrationStep(); void executeMigrationStep();
ProgressReport getPlanProgress(); ProgressReport getPlanProgress();
bool isMigrationPresent(); bool isMigrationPresent();
static double minMigrationAmount; static double minMigrationAmount;
private: private:
QList<int> getBlockNumbers(int start, int end, int count); QList<int> getBlockNumbers(int start, int end, int count);
QString writeableFile(); QString writeableFile();
void doSendTx(Tx tx, std::function<void(void)> cb); void doSendTx(Tx tx, std::function<void(void)> cb);
QList<TurnstileMigrationItem>::Iterator getNextStep(QList<TurnstileMigrationItem>& plan); QList<TurnstileMigrationItem>::Iterator getNextStep(QList<TurnstileMigrationItem>& plan);
RPC* rpc; RPC* rpc;
MainWindow* mainwindow; MainWindow* mainwindow;
}; };
#endif #endif

26
src/txtablemodel.cpp

@ -73,19 +73,19 @@ void TxTableModel::updateAllData() {
{ {
// Align column 4 (amount) right // Align column 4 (amount) right
if (role == Qt::TextAlignmentRole && index.column() == 3) return QVariant(Qt::AlignRight | Qt::AlignVCenter); if (role == Qt::TextAlignmentRole && index.column() == 3) return QVariant(Qt::AlignRight | Qt::AlignVCenter);
if (role == Qt::ForegroundRole) { if (role == Qt::ForegroundRole) {
if (modeldata->at(index.row()).confirmations == 0) { if (modeldata->at(index.row()).confirmations == 0) {
QBrush b; QBrush b;
b.setColor(Qt::red); b.setColor(Qt::red);
return b; return b;
} }
// Else, just return the default brush // Else, just return the default brush
QBrush b; QBrush b;
b.setColor(Qt::black); b.setColor(Qt::black);
return b; return b;
} }
auto dat = modeldata->at(index.row()); auto dat = modeldata->at(index.row());
if (role == Qt::DisplayRole) { if (role == Qt::DisplayRole) {
@ -98,7 +98,7 @@ void TxTableModel::updateAllData() {
else else
return addr; return addr;
} }
case 2: return QDateTime::fromMSecsSinceEpoch(modeldata->at(index.row()).datetime * (qint64)1000).toLocalTime().toString(); case 2: return QDateTime::fromMSecsSinceEpoch(modeldata->at(index.row()).datetime * (qint64)1000).toLocalTime().toString();
case 3: return Settings::getInstance()->getZECDisplayFormat(modeldata->at(index.row()).amount); case 3: return Settings::getInstance()->getZECDisplayFormat(modeldata->at(index.row()).amount);
} }
} }
@ -140,11 +140,11 @@ void TxTableModel::updateAllData() {
{ {
if (role == Qt::TextAlignmentRole && section == 3) return QVariant(Qt::AlignRight | Qt::AlignVCenter); if (role == Qt::TextAlignmentRole && section == 3) return QVariant(Qt::AlignRight | Qt::AlignVCenter);
if (role == Qt::FontRole) { if (role == Qt::FontRole) {
QFont f; QFont f;
f.setBold(true); f.setBold(true);
return f; return f;
} }
if (role == Qt::DisplayRole && orientation == Qt::Horizontal) { if (role == Qt::DisplayRole && orientation == Qt::Horizontal) {
return headers.at(section); return headers.at(section);

26
src/utils.h

@ -9,22 +9,22 @@ struct Tx;
class Utils class Utils
{ {
public: public:
static const QString txidStatusMessage; static const QString txidStatusMessage;
static const QString getTokenName(); static const QString getTokenName();
static const QString getDevSproutAddr(); static const QString getDevSproutAddr();
static const QString getDevAddr(Tx tx); static const QString getDevAddr(Tx tx);
static const QString getDonationAddr(bool sapling); static const QString getDonationAddr(bool sapling);
static double getMinerFee(); static double getMinerFee();
static double getDevFee(); static double getDevFee();
static double getTotalFee(); static double getTotalFee();
static const int updateSpeed = 20 * 1000; // 20 sec static const int updateSpeed = 20 * 1000; // 20 sec
static const int quickUpdateSpeed = 5 * 1000; // 5 sec static const int quickUpdateSpeed = 5 * 1000; // 5 sec
static const int priceRefreshSpeed = 60 * 60 * 1000; // 1 hr static const int priceRefreshSpeed = 60 * 60 * 1000; // 1 hr
private: private:
Utils() = delete; Utils() = delete;
}; };
#endif // UTILS_H #endif // UTILS_H
Loading…
Cancel
Save