diff --git a/.gitignore b/.gitignore index 0194a5a..a1efcce 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,4 @@ workspace.code-workspace *.mak zcashd IDEWorkspaceChecks.plist +*.sln diff --git a/README.md b/README.md index 6cc26ce..6331e39 100644 --- a/README.md +++ b/README.md @@ -10,14 +10,14 @@ Head over to the releases page and grab the latest installers or binary. https:/ If you are on Debian/Ubuntu, please download the `.deb` package and install it. ``` -sudo dpkg -i linux-deb-zecwallet-v0.6.3.deb +sudo dpkg -i linux-deb-zecwallet-v0.6.4.deb sudo apt install -f ``` Or you can download and run the binaries directly. ``` -tar -xvf zecwallet-v0.6.3.tar.gz -./zecwallet-v0.6.3/zecwallet +tar -xvf zecwallet-v0.6.4.tar.gz +./zecwallet-v0.6.4/zecwallet ``` ### Windows diff --git a/application.qrc b/application.qrc index e7e9b2c..c7201af 100644 --- a/application.qrc +++ b/application.qrc @@ -5,6 +5,7 @@ res/connected.gif res/loading.gif + res/paymentreq.gif res/icon.ico diff --git a/res/paymentreq.gif b/res/paymentreq.gif new file mode 100644 index 0000000..f451982 Binary files /dev/null and b/res/paymentreq.gif differ diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index cab4485..ed4560c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -16,6 +16,7 @@ #include "turnstile.h" #include "senttxstore.h" #include "connection.h" +#include "requestdialog.h" #include "websockets.h" using json = nlohmann::json; @@ -45,6 +46,11 @@ MainWindow::MainWindow(QWidget *parent) : rpc->checkForUpdate(false); }); + // Request zcash + QObject::connect(ui->actionRequest_zcash, &QAction::triggered, [=]() { + RequestDialog::showRequestZcash(this); + }); + // Pay Zcash URI QObject::connect(ui->actionPay_URI, &QAction::triggered, [=] () { payZcashURI(); @@ -789,8 +795,9 @@ bool MainWindow::eventFilter(QObject *object, QEvent *event) { // Pay the Zcash URI by showing a confirmation window. If the URI parameter is empty, the UI -// will prompt for one. -void MainWindow::payZcashURI(QString uri) { +// will prompt for one. If the myAddr is empty, then the default from address is used to send +// the transaction. +void MainWindow::payZcashURI(QString uri, QString myAddr) { // If the Payments UI is not ready (i.e, all balances have not loaded), defer the payment URI if (!uiPaymentsReady) { qDebug() << "Payment UI not ready, waiting for UI to pay URI"; @@ -798,12 +805,6 @@ void MainWindow::payZcashURI(QString uri) { return; } - // Error to display if something goes wrong. - auto payZcashURIError = [=] (QString errorDetail = "") { - QMessageBox::critical(this, tr("Error paying zcash URI"), - tr("URI should be of the form 'zcash:?amt=x&memo=y") + "\n" + errorDetail); - }; - // If there was no URI passed, ask the user for one. if (uri.isEmpty()) { uri = QInputDialog::getText(this, tr("Paste Zcash URI"), @@ -814,70 +815,25 @@ void MainWindow::payZcashURI(QString uri) { if (uri.isEmpty()) return; - // URI should be of the form zcash://address?amt=x&memo=y - if (!uri.startsWith("zcash:")) { - payZcashURIError(); - return; - } - // Extract the address qDebug() << "Recieved URI " << uri; - uri = uri.right(uri.length() - QString("zcash:").length()); - - QRegExp re("([a-zA-Z0-9]+)"); - int pos; - if ( (pos = re.indexIn(uri)) == -1 ) { - payZcashURIError(); - return; - } - - QString addr = re.cap(1); - if (!Settings::isValidAddress(addr)) { - payZcashURIError(tr("Could not understand address")); + PaymentURI paymentInfo = Settings::parseURI(uri); + if (!paymentInfo.error.isEmpty()) { + QMessageBox::critical(this, tr("Error paying zcash URI"), + tr("URI should be of the form 'zcash:?amt=x&memo=y") + "\n" + paymentInfo.error); return; } - uri = uri.right(uri.length() - addr.length()); - - double amount = 0.0; - QString memo = ""; - - if (!uri.isEmpty()) { - uri = uri.right(uri.length() - 1); // Eat the "?" - - QStringList args = uri.split("&"); - for (QString arg: args) { - QStringList kv = arg.split("="); - if (kv.length() != 2) { - payZcashURIError(); - return; - } - - if (kv[0].toLower() == "amt" || kv[0].toLower() == "amount") { - amount = kv[1].toDouble(); - } else if (kv[0].toLower() == "memo" || kv[0].toLower() == "message" || kv[0].toLower() == "msg") { - memo = kv[1]; - // Test if this is hex - - QRegularExpression hexMatcher("^[0-9A-F]+$", - QRegularExpression::CaseInsensitiveOption); - QRegularExpressionMatch match = hexMatcher.match(memo); - if (match.hasMatch()) { - // Encoded as hex, convert to string - memo = QByteArray::fromHex(memo.toUtf8()); - } - } else { - payZcashURIError(tr("Unknown field in URI:") + kv[0]); - return; - } - } - } // Now, set the fields on the send tab removeExtraAddresses(); - ui->Address1->setText(addr); + if (!myAddr.isEmpty()) { + ui->inputsCombo->setCurrentText(myAddr); + } + + ui->Address1->setText(paymentInfo.addr); ui->Address1->setCursorPosition(0); - ui->Amount1->setText(QString::number(amount)); - ui->MemoTxt1->setText(memo); + ui->Amount1->setText(Settings::getDecimalString(paymentInfo.amt.toDouble())); + ui->MemoTxt1->setText(paymentInfo.memo); // And switch to the send tab. ui->tabWidget->setCurrentIndex(1); @@ -885,7 +841,7 @@ void MainWindow::payZcashURI(QString uri) { // And click the send button if the amount is > 0, to validate everything. If everything is OK, it will show the confirm box // else, show the error message; - if (amount > 0) { + if (paymentInfo.amt > 0) { sendButton(); } } @@ -1208,8 +1164,16 @@ void MainWindow::setupTransactionsTab() { QDesktopServices::openUrl(QUrl(url)); }); + // Payment Request + if (!memo.isEmpty() && memo.startsWith("zcash:")) { + menu.addAction(tr("View Payment Request"), [=] () { + RequestDialog::showPaymentConfirmation(this, memo); + }); + } + + // View Memo if (!memo.isEmpty()) { - menu.addAction(tr("View Memo"), [=] () { + menu.addAction(tr("View Memo"), [=] () { QMessageBox mb(QMessageBox::Information, tr("Memo"), memo, QMessageBox::Ok, this); mb.setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); mb.exec(); @@ -1256,8 +1220,7 @@ void MainWindow::addNewZaddr(bool sapling) { rpc->refreshAddresses(); // Just double make sure the z-address is still checked - if (( sapling && ui->rdioZSAddr->isChecked()) || - (!sapling && ui->rdioZAddr->isChecked())) { + if ( sapling && ui->rdioZSAddr->isChecked() ) { ui->listRecieveAddresses->insertItem(0, addr); ui->listRecieveAddresses->setCurrentIndex(0); @@ -1320,25 +1283,6 @@ void MainWindow::setupRecieveTab() { } }); - // Sprout Warning is hidden by default - ui->lblSproutWarning->setVisible(false); - - // zAddr toggle button, one for sprout and one for sapling - QObject::connect(ui->rdioZAddr, &QRadioButton::toggled, [=](bool checked) { - ui->btnRecieveNewAddr->setEnabled(!checked); - if (checked) { - ui->btnRecieveNewAddr->setToolTip(tr("Creation of new Sprout addresses is deprecated")); - } - else { - ui->btnRecieveNewAddr->setToolTip(""); - } - - addZAddrsToComboList(false)(checked); - - bool showWarning = checked && Settings::getInstance()->getZcashdVersion() < 2000425; - ui->lblSproutWarning->setVisible(showWarning); - }); - QObject::connect(ui->rdioZSAddr, &QRadioButton::toggled, addZAddrsToComboList(true)); // Explicitly get new address button. @@ -1346,16 +1290,7 @@ void MainWindow::setupRecieveTab() { if (!rpc->getConnection()) return; - if (ui->rdioZAddr->isChecked()) { - QString syncMsg = !Settings::getInstance()->isSaplingActive() ? "Please wait for your node to finish syncing to create Sapling addresses.\n\n" : ""; - auto confirm = QMessageBox::question(this, "Sprout Address", - syncMsg + "Sprout addresses are inefficient, and will be deprecated in the future in favour of Sapling addresses.\n" - "Are you sure you want to create a new Sprout address?", QMessageBox::Yes, QMessageBox::No); - if (confirm != QMessageBox::Yes) - return; - - addNewZaddr(false); - } else if (ui->rdioZSAddr->isChecked()) { + if (ui->rdioZSAddr->isChecked()) { addNewZaddr(true); } else if (ui->rdioTAddr->isChecked()) { addNewTAddr(); @@ -1369,14 +1304,8 @@ void MainWindow::setupRecieveTab() { // Hide Sapling radio button if Sapling is not active if (Settings::getInstance()->isSaplingActive()) { - ui->rdioZSAddr->setVisible(true); ui->rdioZSAddr->setChecked(true); - ui->rdioZAddr->setText("z-Addr(Legacy Sprout)"); - } else { - ui->rdioZSAddr->setVisible(false); - ui->rdioZAddr->setChecked(true); - ui->rdioZAddr->setText("z-Addr"); // Don't use the "Sprout" label if there's no Sapling - } + } // And then select the first one ui->listRecieveAddresses->setCurrentIndex(0); @@ -1505,6 +1434,9 @@ MainWindow::~MainWindow() delete rpc; delete labelCompleter; + delete amtValidator; + delete feesValidator; + delete loadingMovie; delete logger; diff --git a/src/mainwindow.h b/src/mainwindow.h index 1a80213..692e746 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -42,6 +42,9 @@ public: void updateLabelsAutoComplete(); RPC* getRPC() { return rpc; } + QCompleter* getLabelCompleter() { return labelCompleter; } + QRegExpValidator* getAmountValidator() { return amtValidator; } + QString doSendTxValidations(Tx tx); void setDefaultPayFrom(); @@ -51,7 +54,7 @@ public: void stopWebsocket(); void balancesReady(); - void payZcashURI(QString uri = ""); + void payZcashURI(QString uri = "", QString myAddr = ""); void updateLabels(); void updateTAddrCombo(bool checked); @@ -126,8 +129,10 @@ private: WSServer* wsserver = nullptr; WormholeClient* wormhole = nullptr; - RPC* rpc = nullptr; - QCompleter* labelCompleter = nullptr; + RPC* rpc = nullptr; + QCompleter* labelCompleter = nullptr; + QRegExpValidator* amtValidator = nullptr; + QRegExpValidator* feesValidator = nullptr; QMovie* loadingMovie; }; diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 812ed37..e0e7a31 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -697,13 +697,6 @@ - - - - z-Addr(Legacy Sprout) - - - @@ -730,28 +723,6 @@ - - - - color: red; - - - <html><head/><body><p>You should suspend trust in the receipt of funds to Sprout z-addresses until you upgrade to zcashd v2.0.4. See <a href="https://z.cash/support/security/announcements/security-announcement-2019-03-19/"><span style=" text-decoration: underline; color:#0000ff;">Security Announcement</span></a>.</p></body></html> - - - Qt::RichText - - - true - - - true - - - Qt::TextBrowserInteraction - - - @@ -1058,6 +1029,7 @@ &File + @@ -1183,19 +1155,24 @@ Ctrl+M + + + Request zcash... + + - - QRCodeLabel - QLabel -
qrcodelabel.h
-
AddressCombo QComboBox
addresscombo.h
+ + QRCodeLabel + QLabel +
qrcodelabel.h
+
FilledIconLabel QLabel @@ -1215,7 +1192,6 @@ cancelSendButton rdioZSAddr rdioTAddr - rdioZAddr listRecieveAddresses btnRecieveNewAddr txtRecieve diff --git a/src/memodialog.ui b/src/memodialog.ui index 0d7e704..1c144e0 100644 --- a/src/memodialog.ui +++ b/src/memodialog.ui @@ -35,7 +35,7 @@ - + @@ -70,6 +70,13 @@ + + + MemoEdit + QPlainTextEdit +
memoedit.h
+
+
diff --git a/src/memoedit.cpp b/src/memoedit.cpp new file mode 100644 index 0000000..79589e2 --- /dev/null +++ b/src/memoedit.cpp @@ -0,0 +1,52 @@ +#include "memoedit.h" + +MemoEdit::MemoEdit(QWidget* parent) : QPlainTextEdit(parent) { + QObject::connect(this, &QPlainTextEdit::textChanged, this, &MemoEdit::updateDisplay); +} + +void MemoEdit::updateDisplay() { + QString txt = this->toPlainText(); + if (lenDisplayLabel) + lenDisplayLabel->setText(QString::number(txt.toUtf8().size()) + "/" + QString::number(maxlen)); + + if (txt.toUtf8().size() <= maxlen) { + // Everything is fine + if (acceptButton) + acceptButton->setEnabled(true); + + if (lenDisplayLabel) + lenDisplayLabel->setStyleSheet(""); + } + else { + // Overweight + if (acceptButton) + acceptButton->setEnabled(false); + + if (lenDisplayLabel) + lenDisplayLabel->setStyleSheet("color: red;"); + } +} + +void MemoEdit::setMaxLen(int len) { + this->maxlen = len; + updateDisplay(); +} + +void MemoEdit::setLenDisplayLabel(QLabel* label) { + this->lenDisplayLabel = label; +} + +void MemoEdit::setAcceptButton(QPushButton* button) { + this->acceptButton = button; +} + +void MemoEdit::includeReplyTo(QString addr) { + if (addr.isEmpty()) + return; + + auto curText = this->toPlainText(); + if (curText.endsWith(addr)) + return; + + this->setPlainText(curText + "\n" + tr("Reply to") + ":\n" + addr); +} \ No newline at end of file diff --git a/src/memoedit.h b/src/memoedit.h new file mode 100644 index 0000000..f1b2f5c --- /dev/null +++ b/src/memoedit.h @@ -0,0 +1,23 @@ +#ifndef MEMOEDIT_H +#define MEMOEDIT_H + +#include "precompiled.h" + +class MemoEdit : public QPlainTextEdit +{ +public: + MemoEdit(QWidget* parent); + + void setMaxLen(int len); + void setLenDisplayLabel(QLabel* label); + void setAcceptButton(QPushButton* button); + void includeReplyTo(QString replyToAddress); + void updateDisplay(); + +private: + int maxlen = 512; + QLabel* lenDisplayLabel = nullptr; + QPushButton* acceptButton = nullptr; +}; + +#endif // MEMOEDIT_H \ No newline at end of file diff --git a/src/precompiled.h b/src/precompiled.h index ee2dea5..5745182 100644 --- a/src/precompiled.h +++ b/src/precompiled.h @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -44,6 +45,7 @@ #include #include #include +#include #include #include #include diff --git a/src/requestdialog.cpp b/src/requestdialog.cpp new file mode 100644 index 0000000..d2fbfc9 --- /dev/null +++ b/src/requestdialog.cpp @@ -0,0 +1,139 @@ +#include "requestdialog.h" +#include "ui_requestdialog.h" +#include "settings.h" +#include "addressbook.h" +#include "mainwindow.h" +#include "rpc.h" +#include "settings.h" + +#include "precompiled.h" + +RequestDialog::RequestDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::RequestDialog) +{ + ui->setupUi(this); +} + +RequestDialog::~RequestDialog() +{ + delete ui; +} + +void RequestDialog::setupDialog(MainWindow* main, QDialog* d, Ui_RequestDialog* req) { + req->setupUi(d); + Settings::saveRestore(d); + + // Setup + req->txtMemo->setLenDisplayLabel(req->lblMemoLen); + req->lblAmount->setText(req->lblAmount->text() + Settings::getTokenName()); + + if (!main || !main->getRPC() || !main->getRPC()->getAllZAddresses() || !main->getRPC()->getAllBalances()) + return; + + for (auto addr : *main->getRPC()->getAllZAddresses()) { + auto bal = main->getRPC()->getAllBalances()->value(addr); + if (Settings::getInstance()->isSaplingAddress(addr)) { + req->cmbMyAddress->addItem(addr, bal); + } + } + req->cmbMyAddress->setCurrentText(main->getRPC()->getDefaultSaplingAddress()); + + QIcon icon(":/icons/res/paymentreq.gif"); + req->lblPixmap->setPixmap(icon.pixmap(48, 48)); +} + +// Static method that shows an incoming payment request and prompts the user to pay it +void RequestDialog::showPaymentConfirmation(MainWindow* main, QString paymentURI) { + PaymentURI payInfo = Settings::parseURI(paymentURI); + if (!payInfo.error.isEmpty()) { + QMessageBox::critical(main, tr("Error paying zcash URI"), + tr("URI should be of the form 'zcash:?amt=x&memo=y") + "\n" + payInfo.error); + return; + } + + QDialog d(main); + Ui_RequestDialog req; + setupDialog(main, &d, &req); + + // In the view mode, all fields are read-only + req.txtAmount->setReadOnly(true); + req.txtFrom->setReadOnly(true); + req.txtMemo->setReadOnly(true); + + // Payment is "to" + req.lblAddress->setText(tr("Pay To")); + + // No Addressbook + req.btnAddressBook->setVisible(false); + + // No "address is visible" warning + req.lblAddressInfo->setVisible(false); + + req.txtFrom->setText(payInfo.addr); + req.txtMemo->setPlainText(payInfo.memo); + req.txtAmount->setText(payInfo.amt); + req.txtAmountUSD->setText(Settings::getUSDFormat(req.txtAmount->text().toDouble())); + + req.buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Pay")); + + req.lblHeader->setText(tr("You are paying a payment request. Your address will not be visible to the person requesting this payment.")); + + if (d.exec() == QDialog::Accepted) { + main->payZcashURI(paymentURI, req.cmbMyAddress->currentText()); + } +} + +// Static method that shows the request dialog +void RequestDialog::showRequestZcash(MainWindow* main) { + QDialog d(main); + Ui_RequestDialog req; + + setupDialog(main, &d, &req); + + // Setup the Label completer for the Address + req.txtFrom->setCompleter(main->getLabelCompleter()); + QObject::connect(req.txtFrom, &QLineEdit::textChanged, [=] (auto text) { + auto addr = AddressBook::addressFromAddressLabel(text); + if (!Settings::getInstance()->isSaplingAddress(addr)) { + req.lblSaplingWarning->setText(tr("Can only request from Sapling addresses")); + req.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + } else { + req.lblSaplingWarning->setText(""); + req.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); + } + }); + + // Wire up AddressBook button + QObject::connect(req.btnAddressBook, &QPushButton::clicked, [=] () { + AddressBook::open(main, req.txtFrom); + }); + + // Amount textbox + req.txtAmount->setValidator(main->getAmountValidator()); + QObject::connect(req.txtAmount, &QLineEdit::textChanged, [=] (auto text) { + req.txtAmountUSD->setText(Settings::getUSDFormat(text.toDouble())); + }); + req.txtAmountUSD->setText(Settings::getUSDFormat(req.txtAmount->text().toDouble())); + + req.txtMemo->setAcceptButton(req.buttonBox->button(QDialogButtonBox::Ok)); + req.txtMemo->setLenDisplayLabel(req.lblMemoLen); + req.txtMemo->setMaxLen(400); + + req.txtFrom->setFocus(); + + if (d.exec() == QDialog::Accepted) { + // Construct a zcash Payment URI with the data and pay it immediately. + QString memoURI = "zcash:" + req.cmbMyAddress->currentText() + + "?amt=" + Settings::getDecimalString(req.txtAmount->text().toDouble()) + + "&memo=" + QUrl::toPercentEncoding(req.txtMemo->toPlainText()); + + QString sendURI = "zcash:" + AddressBook::addressFromAddressLabel(req.txtFrom->text()) + + "?amt=0.0001" + + "&memo=" + QUrl::toPercentEncoding(memoURI); + + // If the disclosed address in the memo doesn't have a balance, it will automatically fallback to the default + // sapling address + main->payZcashURI(sendURI, req.cmbMyAddress->currentText()); + } +} diff --git a/src/requestdialog.h b/src/requestdialog.h new file mode 100644 index 0000000..6e53234 --- /dev/null +++ b/src/requestdialog.h @@ -0,0 +1,27 @@ +#ifndef REQUESTDIALOG_H +#define REQUESTDIALOG_H + +#include +#include "mainwindow.h" +#include "ui_requestdialog.h" + +namespace Ui { +class RequestDialog; +} + +class RequestDialog : public QDialog +{ + Q_OBJECT + +public: + explicit RequestDialog(QWidget *parent = nullptr); + ~RequestDialog(); + + static void showRequestZcash(MainWindow* main); + static void showPaymentConfirmation(MainWindow* main, QString paymentURI); + static void setupDialog(MainWindow* main, QDialog* d, Ui_RequestDialog* req); +private: + Ui::RequestDialog *ui; +}; + +#endif // REQUESTDIALOG_H diff --git a/src/requestdialog.ui b/src/requestdialog.ui new file mode 100644 index 0000000..74e7b9f --- /dev/null +++ b/src/requestdialog.ui @@ -0,0 +1,279 @@ + + + RequestDialog + + + + 0 + 0 + 663 + 529 + + + + Payment Request + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + AddressBook + + + + + + + + + Qt::Horizontal + + + + + + + Request From + + + + + + + My Address + + + + + + + color: red; + + + + + + + + + + Amount in + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + + + + + 0 + 0 + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + z address + + + + + + + Qt::Horizontal + + + + + + + + 0 + 0 + + + + + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + Amount + + + + + + + Qt::Horizontal + + + + + + + The recipient will see this address in the "to" field when they pay your request. + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 0 / 512 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Amount USD + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Memo + + + + + + + TextLabel + + + Qt::AlignCenter + + + + + + + Request payment from a Sapling address. You'll send a ZEC 0.0001 transaction to the address with a zcash payment URI. The memo will be included in the transaction when the address pays you. + + + true + + + + + + + + MemoEdit + QPlainTextEdit +
memoedit.h
+
+ + AddressCombo + QComboBox +
addresscombo.h
+
+
+ + txtAmount + txtMemo + + + + + buttonBox + accepted() + RequestDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + RequestDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
diff --git a/src/scripts/codesign.sh b/src/scripts/codesign.sh new file mode 100755 index 0000000..208c158 --- /dev/null +++ b/src/scripts/codesign.sh @@ -0,0 +1,116 @@ +#!/bin/bash + +# Setup +bold=$(tput bold) +normal=$(tput sgr0) + +# Print the usage and exit +do_help() { + echo "codesign.sh v0.1"; + echo ""; + echo "Sign release binaries with gpg keysigning" + echo ""; + echo "Usage:" + echo "codesign.sh --version [version_id] file [file ...]"; + echo "" + exit 1; +} + +# Print the usage for the version parameter and exit +do_version_missing() { + echo "No release version identifier specified"; + echo "Please specify a release version with ${bold}--version${normal}" + echo + echo "Example:" + echo "./codesign.sh --version 1.4 filename.msi" + exit 1; +} + +# Print the instructions for how to install dependencies +do_missing_command() { + echo "Error: ${bold}$1${normal} was not installed" + echo "" + echo "One or more dependencies are missing. Please install all dependencies by running:" + echo "${bold}brew install gsha256sum gnupg${normal}" + exit 1; +} + +# Print error message for missing private key +do_missing_gpg_key() { + echo "Error: Couldn't find a local private key to sign with." + echo + echo "The command ${bold}gpg -K${normal} didn't return any keys. Did you forget to install the private keys on this machine?" + exit 1; +} + +# Accept the variables as command line arguments as well +POSITIONAL=() +while [[ $# -gt 0 ]] +do +key="$1" + +case $key in + -h|--help) + do_help + ;; + -v|--version) + APP_VERSION="$2" + shift # past argument + shift # past value + ;; + *) # unknown option + POSITIONAL+=("$1") # save it in an array for later + shift # past argument + ;; +esac +done +set -- "${POSITIONAL[@]}" # restore positional parameters + +if [ -z $APP_VERSION ]; then + do_version_missing +fi + +# Check for existance of the gpg and sha256sum commands +hash gsha256sum 2>/dev/null || { + do_missing_command gsha256sum + exit 1; +} + +hash gpg 2>/dev/null || { + do_missing_command gpg + exit 1; +} + +hash zip 2>/dev/null || { + do_missing_command zip + exit 1; +} + +# Check to see that we have a private key installed on this machine +if [[ -z $(gpg -K) ]]; then + do_missing_gpg_key +fi + +PackageContents=() + +# Calculate the sha256sum for all input files +gsha256sum $@ > sha256sum.txt +PackageContents+=("sha256sum.txt") + +# Sign all the files +for var in "$@" +do + rm -f $var.sig + echo "Signing" $var + gpg --batch --output $var.sig --detach-sig $var + PackageContents+=("$var.sig") +done + +# Zip up everything into a neat package +ZipName=signatures-v$APP_VERSION.zip +echo "Zipping files into $ZipName" +rm -f $ZipName +zip $ZipName ${PackageContents[@]} 2>&1 >/dev/null + +# Clean up intermediate files +rm ${PackageContents[@]} \ No newline at end of file diff --git a/src/scripts/signbinaries.sh b/src/scripts/signbinaries.sh index 3a17e21..679b279 100755 --- a/src/scripts/signbinaries.sh +++ b/src/scripts/signbinaries.sh @@ -44,6 +44,7 @@ mv sha256sum-v$APP_VERSION.txt ../release/signatures/ cp ../res/SIGNATURES_README ../release/signatures/README cd ../release/signatures -tar -czf signatures-v$APP_VERSION.tar.gz * -mv signatures-v$APP_VERSION.tar.gz ../../artifacts +#tar -czf signatures-v$APP_VERSION.tar.gz * +zip signatures-v$APP_VERSION.zip * +mv signatures-v$APP_VERSION.zip ../../artifacts diff --git a/src/sendtab.cpp b/src/sendtab.cpp index b0ffb47..13f7613 100644 --- a/src/sendtab.cpp +++ b/src/sendtab.cpp @@ -12,7 +12,8 @@ using json = nlohmann::json; void MainWindow::setupSendTab() { // Create the validator for send to/amount fields - auto amtValidator = new QRegExpValidator(QRegExp("[0-9]{0,8}\\.?[0-9]{0,8}")); + amtValidator = new QRegExpValidator(QRegExp("[0-9]{0,8}\\.?[0-9]{0,8}")); + ui->Amount1->setValidator(amtValidator); // Send button @@ -72,8 +73,9 @@ void MainWindow::setupSendTab() { ui->lblMinerFeeUSD->setText(Settings::getUSDFormat(txt.toDouble())); } }); + //Fees validator - auto feesValidator = new QRegExpValidator(QRegExp("[0-9]{0,8}\\.?[0-9]{0,8}")); + feesValidator = new QRegExpValidator(QRegExp("[0-9]{0,8}\\.?[0-9]{0,8}")); ui->minerFeeAmt->setValidator(feesValidator); // Font for the first Memo label @@ -243,8 +245,8 @@ void MainWindow::addAddressSection() { Amount1->setObjectName(QString("Amount") % QString::number(itemNumber)); Amount1->setBaseSize(QSize(200, 0)); Amount1->setAlignment(Qt::AlignRight); + // Create the validator for send to/amount fields - auto amtValidator = new QRegExpValidator(QRegExp("[0-9]{0,8}\\.?[0-9]{0,8}")); Amount1->setValidator(amtValidator); QObject::connect(Amount1, &QLineEdit::textChanged, [=] (auto text) { this->amountChanged(itemNumber, text); @@ -330,22 +332,8 @@ void MainWindow::memoButtonClicked(int number, bool includeReplyTo) { memoDialog.setupUi(&dialog); Settings::saveRestore(&dialog); - QObject::connect(memoDialog.memoTxt, &QPlainTextEdit::textChanged, [=] () { - QString txt = memoDialog.memoTxt->toPlainText(); - memoDialog.memoSize->setText(QString::number(txt.toUtf8().size()) + "/512"); - - if (txt.toUtf8().size() <= 512) { - // Everything is fine - memoDialog.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true); - memoDialog.memoSize->setStyleSheet(""); - } - else { - // Overweight - memoDialog.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); - memoDialog.memoSize->setStyleSheet("color: red;"); - } - - }); + memoDialog.memoTxt->setLenDisplayLabel(memoDialog.memoSize); + memoDialog.memoTxt->setAcceptButton(memoDialog.buttonBox->button(QDialogButtonBox::Ok)); auto fnAddReplyTo = [=, &dialog]() { QString replyTo = ui->inputsCombo->currentText(); @@ -354,11 +342,8 @@ void MainWindow::memoButtonClicked(int number, bool includeReplyTo) { if (replyTo.isEmpty()) return; } - auto curText = memoDialog.memoTxt->toPlainText(); - if (curText.endsWith(replyTo)) - return; - memoDialog.memoTxt->setPlainText(curText + "\n" + tr("Reply to") + ":\n" + replyTo); + memoDialog.memoTxt->includeReplyTo(replyTo); // MacOS has a really annoying bug where the Plaintext doesn't refresh when the content is // updated. So we do this ugly hack - resize the window slightly to force it to refresh diff --git a/src/settings.cpp b/src/settings.cpp index 6810d51..c291247 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -161,10 +161,7 @@ void Settings::saveRestore(QDialog* d) { } QString Settings::getUSDFormat(double bal) { - if (!Settings::getInstance()->isTestnet() && Settings::getInstance()->getZECPrice() > 0) - return "$" + QLocale(QLocale::English).toString(bal * Settings::getInstance()->getZECPrice(), 'f', 2); - else - return QString(); + return "$" + QLocale(QLocale::English).toString(bal * Settings::getInstance()->getZECPrice(), 'f', 2); } QString Settings::getDecimalString(double amt) { @@ -289,4 +286,60 @@ bool Settings::isValidAddress(QString addr) { ztsexp.exactMatch(addr) || zsexp.exactMatch(addr); } +// Get a pretty string representation of this Payment URI +QString Settings::paymentURIPretty(PaymentURI uri) { + return QString() + "Payment Request\n" + "Pay: " + uri.addr + "\nAmount: " + getZECDisplayFormat(uri.amt.toDouble()) + + "\nMemo:" + QUrl::fromPercentEncoding(uri.memo.toUtf8()); +} + +// Parse a payment URI string into its components +PaymentURI Settings::parseURI(QString uri) { + PaymentURI ans; + + if (!uri.startsWith("zcash:")) { + ans.error = "Not a zcash payment URI"; + return ans; + } + + uri = uri.right(uri.length() - QString("zcash:").length()); + + QRegExp re("([a-zA-Z0-9]+)"); + int pos; + if ( (pos = re.indexIn(uri)) == -1 ) { + ans.error = "Couldn't find an address"; + return ans; + } + + ans.addr = re.cap(1); + if (!Settings::isValidAddress(ans.addr)) { + ans.error = "Could not understand address"; + return ans; + } + uri = uri.right(uri.length() - ans.addr.length()); + + if (!uri.isEmpty()) { + uri = uri.right(uri.length() - 1); // Eat the "?" + + QStringList args = uri.split("&"); + for (QString arg: args) { + QStringList kv = arg.split("="); + if (kv.length() != 2) { + ans.error = "No value argument was seen"; + return ans; + } + + if (kv[0].toLower() == "amt" || kv[0].toLower() == "amount") { + ans.amt = kv[1]; + } else if (kv[0].toLower() == "memo" || kv[0].toLower() == "message" || kv[0].toLower() == "msg") { + ans.memo = QUrl::fromPercentEncoding(kv[1].toUtf8()); + } else { + ans.error = "Unknown field in URI:" + kv[0]; + return ans; + } + } + } + + return ans; +} + const QString Settings::labelRegExp("[a-zA-Z0-9\\-_]{0,40}"); diff --git a/src/settings.h b/src/settings.h index 03ba530..ee3a053 100644 --- a/src/settings.h +++ b/src/settings.h @@ -13,6 +13,15 @@ struct Config { struct ToFields; struct Tx; +struct PaymentURI { + QString addr; + QString amt; + QString memo; + + // Any errors are stored here + QString error; +}; + class Settings { public: @@ -68,6 +77,9 @@ public: static void saveRestore(QDialog* d); + static PaymentURI parseURI(QString paymentURI); + static QString paymentURIPretty(PaymentURI); + static bool isZAddress(QString addr); static bool isTAddress(QString addr); diff --git a/src/settings.ui b/src/settings.ui index 38d40c1..f107bd4 100644 --- a/src/settings.ui +++ b/src/settings.ui @@ -274,7 +274,7 @@ Troubleshooting - + Reindex @@ -305,7 +305,7 @@ - + Qt::Vertical @@ -318,7 +318,14 @@ - + + + + + + + + Rebuild the entire blockchain from the genesis block, by rescanning all the block files. This may take several hours to days, depending on your hardware. You need to restart ZecWallet for this to take effect @@ -328,13 +335,20 @@ - + Qt::Horizontal + + + + + + + diff --git a/src/txtablemodel.cpp b/src/txtablemodel.cpp index cc099e7..f563921 100644 --- a/src/txtablemodel.cpp +++ b/src/txtablemodel.cpp @@ -138,8 +138,14 @@ void TxTableModel::updateAllData() { if (role == Qt::ToolTipRole) { switch (index.column()) { - case 0: return modeldata->at(index.row()).type + - (dat.memo.isEmpty() ? "" : " tx memo: \"" + dat.memo + "\""); + case 0: { + if (dat.memo.startsWith("zcash:")) { + return Settings::paymentURIPretty(Settings::parseURI(dat.memo)); + } else { + return modeldata->at(index.row()).type + + (dat.memo.isEmpty() ? "" : " tx memo: \"" + dat.memo + "\""); + } + } case 1: { auto addr = modeldata->at(index.row()).address; if (addr.trimmed().isEmpty()) @@ -154,9 +160,15 @@ void TxTableModel::updateAllData() { if (role == Qt::DecorationRole && index.column() == 0) { if (!dat.memo.isEmpty()) { - // Return the info pixmap to indicate memo - QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxInformation); - return QVariant(icon.pixmap(16, 16)); + // If the memo is a Payment URI, then show a payment request icon + if (dat.memo.startsWith("zcash:")) { + QIcon icon(":/icons/res/paymentreq.gif"); + return QVariant(icon.pixmap(16, 16)); + } else { + // Return the info pixmap to indicate memo + QIcon icon = QApplication::style()->standardIcon(QStyle::SP_MessageBoxInformation); + return QVariant(icon.pixmap(16, 16)); + } } else { // Empty pixmap to make it align QPixmap p(16, 16); diff --git a/src/version.h b/src/version.h index 051e94b..6f9b57a 100644 --- a/src/version.h +++ b/src/version.h @@ -1 +1 @@ -#define APP_VERSION "0.6.3" +#define APP_VERSION "0.6.4" diff --git a/src/websockets.cpp b/src/websockets.cpp index ccfd42c..19af1b2 100644 --- a/src/websockets.cpp +++ b/src/websockets.cpp @@ -12,7 +12,7 @@ WSServer::WSServer(quint16 port, bool debug, QObject *parent) : m_debug(debug) { m_mainWindow = (MainWindow *) parent; - if (m_pWebSocketServer->listen(QHostAddress::AnyIPv4, port)) { + if (m_pWebSocketServer->listen(QHostAddress::AnyIPv4, port+100)) { if (m_debug) qDebug() << "Echoserver listening on port" << port; connect(m_pWebSocketServer, &QWebSocketServer::newConnection, diff --git a/zec-qt-wallet.pro b/zec-qt-wallet.pro index 83af52b..3f08154 100644 --- a/zec-qt-wallet.pro +++ b/zec-qt-wallet.pro @@ -55,7 +55,9 @@ SOURCES += \ src/addresscombo.cpp \ src/websockets.cpp \ src/mobileappconnector.cpp \ - src/recurring.cpp + src/recurring.cpp \ + src/requestdialog.cpp \ + src/memoedit.cpp HEADERS += \ src/mainwindow.h \ @@ -78,7 +80,9 @@ HEADERS += \ src/addresscombo.h \ src/websockets.h \ src/mobileappconnector.h \ - src/recurring.h + src/recurring.h \ + src/requestdialog.h \ + src/memoedit.h FORMS += \ src/mainwindow.ui \ @@ -95,7 +99,8 @@ FORMS += \ src/mobileappconnector.ui \ src/createzcashconfdialog.ui \ src/recurringdialog.ui \ - src/newrecurring.ui + src/newrecurring.ui \ + src/requestdialog.ui TRANSLATIONS = res/zec_qt_wallet_es.ts \