Browse Source

Merge branch 'master' of github.com:ZcashFoundation/zecwallet

import_zecw
adityapk00 5 years ago
parent
commit
8fc285a533
  1. 1
      .gitignore
  2. 6
      README.md
  3. 1
      application.qrc
  4. BIN
      res/paymentreq.gif
  5. 140
      src/mainwindow.cpp
  6. 11
      src/mainwindow.h
  7. 46
      src/mainwindow.ui
  8. 9
      src/memodialog.ui
  9. 52
      src/memoedit.cpp
  10. 23
      src/memoedit.h
  11. 2
      src/precompiled.h
  12. 139
      src/requestdialog.cpp
  13. 27
      src/requestdialog.h
  14. 279
      src/requestdialog.ui
  15. 116
      src/scripts/codesign.sh
  16. 5
      src/scripts/signbinaries.sh
  17. 31
      src/sendtab.cpp
  18. 61
      src/settings.cpp
  19. 12
      src/settings.h
  20. 22
      src/settings.ui
  21. 22
      src/txtablemodel.cpp
  22. 2
      src/version.h
  23. 2
      src/websockets.cpp
  24. 11
      zec-qt-wallet.pro

1
.gitignore

@ -36,3 +36,4 @@ workspace.code-workspace
*.mak
zcashd
IDEWorkspaceChecks.plist
*.sln

6
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

1
application.qrc

@ -5,6 +5,7 @@
<qresource prefix="/icons">
<file>res/connected.gif</file>
<file>res/loading.gif</file>
<file>res/paymentreq.gif</file>
<file>res/icon.ico</file>
</qresource>
<qresource prefix="/img">

BIN
res/paymentreq.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

140
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:<addr>?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:<addr>?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;

11
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;
};

46
src/mainwindow.ui

@ -697,13 +697,6 @@
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="rdioZAddr">
<property name="text">
<string>z-Addr(Legacy Sprout)</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
@ -730,28 +723,6 @@
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="lblSproutWarning">
<property name="styleSheet">
<string notr="true">color: red;</string>
</property>
<property name="text">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;You should suspend trust in the receipt of funds to Sprout z-addresses until you upgrade to zcashd v2.0.4. See &lt;a href=&quot;https://z.cash/support/security/announcements/security-announcement-2019-03-19/&quot;&gt;&lt;span style=&quot; text-decoration: underline; color:#0000ff;&quot;&gt;Security Announcement&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::TextBrowserInteraction</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
@ -1058,6 +1029,7 @@
<property name="title">
<string>&amp;File</string>
</property>
<addaction name="actionRequest_zcash"/>
<addaction name="actionPay_URI"/>
<addaction name="separator"/>
<addaction name="actionImport_Private_Key"/>
@ -1183,19 +1155,24 @@
<string>Ctrl+M</string>
</property>
</action>
<action name="actionRequest_zcash">
<property name="text">
<string>Request zcash...</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>
<customwidget>
<class>QRCodeLabel</class>
<extends>QLabel</extends>
<header>qrcodelabel.h</header>
</customwidget>
<customwidget>
<class>AddressCombo</class>
<extends>QComboBox</extends>
<header>addresscombo.h</header>
</customwidget>
<customwidget>
<class>QRCodeLabel</class>
<extends>QLabel</extends>
<header>qrcodelabel.h</header>
</customwidget>
<customwidget>
<class>FilledIconLabel</class>
<extends>QLabel</extends>
@ -1215,7 +1192,6 @@
<tabstop>cancelSendButton</tabstop>
<tabstop>rdioZSAddr</tabstop>
<tabstop>rdioTAddr</tabstop>
<tabstop>rdioZAddr</tabstop>
<tabstop>listRecieveAddresses</tabstop>
<tabstop>btnRecieveNewAddr</tabstop>
<tabstop>txtRecieve</tabstop>

9
src/memodialog.ui

@ -35,7 +35,7 @@
</widget>
</item>
<item row="1" column="0" colspan="3">
<widget class="QPlainTextEdit" name="memoTxt"/>
<widget class="MemoEdit" name="memoTxt"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
@ -70,6 +70,13 @@
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>MemoEdit</class>
<extends>QPlainTextEdit</extends>
<header>memoedit.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections>
<connection>

52
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);
}

23
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

2
src/precompiled.h

@ -32,6 +32,7 @@
#include <QDir>
#include <QMenu>
#include <QCompleter>
#include <QPushButton>
#include <QDateTime>
#include <QTimer>
#include <QSettings>
@ -44,6 +45,7 @@
#include <QStandardPaths>
#include <QMainWindow>
#include <QPushButton>
#include <QPlainTextEdit>
#include <QLabel>
#include <QDialog>
#include <QInputDialog>

139
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:<addr>?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());
}
}

27
src/requestdialog.h

@ -0,0 +1,27 @@
#ifndef REQUESTDIALOG_H
#define REQUESTDIALOG_H
#include <QDialog>
#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

279
src/requestdialog.ui

@ -0,0 +1,279 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>RequestDialog</class>
<widget class="QDialog" name="RequestDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>663</width>
<height>529</height>
</rect>
</property>
<property name="windowTitle">
<string>Payment Request</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="18" column="1" colspan="3">
<widget class="AddressCombo" name="cmbMyAddress"/>
</item>
<item row="2" column="3">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btnAddressBook">
<property name="text">
<string>AddressBook</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="21" column="0" colspan="4">
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="lblAddress">
<property name="text">
<string>Request From</string>
</property>
</widget>
</item>
<item row="15" column="1">
<widget class="QLabel" name="label_2">
<property name="text">
<string>My Address</string>
</property>
</widget>
</item>
<item row="5" column="1" colspan="3">
<widget class="QLabel" name="lblSaplingWarning">
<property name="styleSheet">
<string notr="true">color: red;</string>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLabel" name="lblAmount">
<property name="text">
<string>Amount in </string>
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="QLabel" name="label">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="24" column="2" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
<item row="14" column="1">
<widget class="QLabel" name="label_4">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="4" column="1" colspan="3">
<widget class="QLineEdit" name="txtFrom">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
<property name="placeholderText">
<string>z address</string>
</property>
</widget>
</item>
<item row="1" column="1" colspan="3">
<widget class="Line" name="line_3">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="12" column="1" colspan="3">
<widget class="MemoEdit" name="txtMemo">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="7" column="1" colspan="3">
<widget class="QLineEdit" name="txtAmount">
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
<property name="placeholderText">
<string>Amount</string>
</property>
</widget>
</item>
<item row="13" column="1" colspan="3">
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="15" column="3">
<widget class="QLabel" name="lblAddressInfo">
<property name="text">
<string>The recipient will see this address in the &quot;to&quot; field when they pay your request.</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="11" column="3">
<widget class="QLabel" name="lblMemoLen">
<property name="text">
<string notr="true">0 / 512</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="9" column="1">
<widget class="QLabel" name="txtAmountUSD">
<property name="text">
<string>Amount USD</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="20" column="1">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item row="11" column="1">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Memo</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="lblPixmap">
<property name="text">
<string>TextLabel</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
</widget>
</item>
<item row="0" column="2" colspan="2">
<widget class="QLabel" name="lblHeader">
<property name="text">
<string>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.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>MemoEdit</class>
<extends>QPlainTextEdit</extends>
<header>memoedit.h</header>
</customwidget>
<customwidget>
<class>AddressCombo</class>
<extends>QComboBox</extends>
<header>addresscombo.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>txtAmount</tabstop>
<tabstop>txtMemo</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>RequestDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>RequestDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

116
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[@]}

5
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

31
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

61
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}");

12
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);

22
src/settings.ui

@ -274,7 +274,7 @@
<string>Troubleshooting</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_2">
<item row="3" column="0">
<item row="5" column="0">
<widget class="QCheckBox" name="chkReindex">
<property name="text">
<string>Reindex</string>
@ -305,7 +305,7 @@
</property>
</widget>
</item>
<item row="6" column="0">
<item row="9" column="0">
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
@ -318,7 +318,14 @@
</property>
</spacer>
</item>
<item row="4" column="0">
<item row="2" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>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</string>
@ -328,13 +335,20 @@
</property>
</widget>
</item>
<item row="5" column="0">
<item row="8" column="0">
<widget class="Line" name="line_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_13">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</widget>

22
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);

2
src/version.h

@ -1 +1 @@
#define APP_VERSION "0.6.3"
#define APP_VERSION "0.6.4"

2
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,

11
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 \

Loading…
Cancel
Save