Merge branch 'master' of github.com:zcashfoundation/zecwallet
This commit is contained in:
@@ -82,8 +82,9 @@ MainWindow::MainWindow(QWidget *parent) :
|
||||
// Export transactions
|
||||
QObject::connect(ui->actionExport_transactions, &QAction::triggered, this, &MainWindow::exportTransactions);
|
||||
|
||||
// Z-board seems to have been abandoned
|
||||
// z-Board.net
|
||||
QObject::connect(ui->actionz_board_net, &QAction::triggered, this, &MainWindow::postToZBoard);
|
||||
// QObject::connect(ui->actionz_board_net, &QAction::triggered, this, &MainWindow::postToZBoard);
|
||||
|
||||
// Validate Address
|
||||
QObject::connect(ui->actionValidate_Address, &QAction::triggered, this, &MainWindow::validateAddress);
|
||||
@@ -177,6 +178,11 @@ void MainWindow::restoreSavedStates() {
|
||||
|
||||
ui->balancesTable->horizontalHeader()->restoreState(s.value("baltablegeometry").toByteArray());
|
||||
ui->transactionsTable->horizontalHeader()->restoreState(s.value("tratablegeometry").toByteArray());
|
||||
|
||||
// Explicitly set the tx table resize headers, since some previous values may have made them
|
||||
// non-expandable.
|
||||
ui->transactionsTable->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Interactive);
|
||||
ui->transactionsTable->horizontalHeader()->setSectionResizeMode(4, QHeaderView::Interactive);
|
||||
}
|
||||
|
||||
void MainWindow::doClose() {
|
||||
@@ -637,8 +643,7 @@ void MainWindow::donate() {
|
||||
// Set up a donation to me :)
|
||||
clearSendForm();
|
||||
|
||||
ui->Address1->setText(Settings::getDonationAddr(
|
||||
Settings::getInstance()->isSaplingAddress(ui->inputsCombo->currentText())));
|
||||
ui->Address1->setText(Settings::getDonationAddr());
|
||||
ui->Address1->setCursorPosition(0);
|
||||
ui->Amount1->setText("0.01");
|
||||
ui->MemoTxt1->setText(tr("Thanks for supporting ZecWallet!"));
|
||||
@@ -710,12 +715,12 @@ void MainWindow::postToZBoard() {
|
||||
|
||||
QMap<QString, QString> topics;
|
||||
// Insert the main topic automatically
|
||||
topics.insert("#Main_Area", Settings::getInstance()->isTestnet() ? Settings::getDonationAddr(true) : Settings::getZboardAddr());
|
||||
topics.insert("#Main_Area", Settings::getInstance()->isTestnet() ? Settings::getDonationAddr() : Settings::getZboardAddr());
|
||||
zb.topicsList->addItem(topics.firstKey());
|
||||
// Then call the API to get topics, and if it returns successfully, then add the rest of the topics
|
||||
rpc->getZboardTopics([&](QMap<QString, QString> topicsMap) {
|
||||
for (auto t : topicsMap.keys()) {
|
||||
topics.insert(t, Settings::getInstance()->isTestnet() ? Settings::getDonationAddr(true) : topicsMap[t]);
|
||||
topics.insert(t, Settings::getInstance()->isTestnet() ? Settings::getDonationAddr() : topicsMap[t]);
|
||||
zb.topicsList->addItem(t);
|
||||
}
|
||||
});
|
||||
@@ -791,20 +796,7 @@ void MainWindow::postToZBoard() {
|
||||
tx.fee = Settings::getMinerFee();
|
||||
|
||||
// And send the Tx
|
||||
rpc->executeTransaction(tx, [=] (QString opid) {
|
||||
ui->statusBar->showMessage(tr("Computing Tx: ") % opid);
|
||||
},
|
||||
[=] (QString /*opid*/, QString txid) {
|
||||
ui->statusBar->showMessage(Settings::txidStatusMessage + " " + txid);
|
||||
},
|
||||
[=] (QString opid, QString errStr) {
|
||||
ui->statusBar->showMessage(QObject::tr(" Tx ") % opid % QObject::tr(" failed"), 15 * 1000);
|
||||
|
||||
if (!opid.isEmpty())
|
||||
errStr = QObject::tr("The transaction with id ") % opid % QObject::tr(" failed. The error was") + ":\n\n" + errStr;
|
||||
|
||||
QMessageBox::critical(this, QObject::tr("Transaction Error"), errStr, QMessageBox::Ok);
|
||||
});
|
||||
rpc->executeStandardUITransaction(tx);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -825,7 +817,7 @@ void MainWindow::doImport(QList<QString>* keys) {
|
||||
keys->pop_front();
|
||||
bool rescan = keys->isEmpty();
|
||||
|
||||
if (key.startsWith("S") ||
|
||||
if (key.startsWith("SK") ||
|
||||
key.startsWith("secret")) { // Z key
|
||||
rpc->importZPrivKey(key, rescan, [=] (auto) { this->doImport(keys); });
|
||||
} else {
|
||||
@@ -949,6 +941,16 @@ void MainWindow::importPrivKey() {
|
||||
return key.trimmed().split(" ")[0];
|
||||
});
|
||||
|
||||
// Special case.
|
||||
// Sometimes, when importing from a paperwallet or such, the key is split by newlines, and might have
|
||||
// been pasted like that. So check to see if the whole thing is one big private key
|
||||
if (Settings::getInstance()->isValidSaplingPrivateKey(keys->join(""))) {
|
||||
auto multiline = keys;
|
||||
keys = new QList<QString>();
|
||||
keys->append(multiline->join(""));
|
||||
delete multiline;
|
||||
}
|
||||
|
||||
// Start the import. The function takes ownership of keys
|
||||
QTimer::singleShot(1, [=]() {doImport(keys);});
|
||||
|
||||
@@ -1304,6 +1306,9 @@ std::function<void(bool)> MainWindow::addZAddrsToComboList(bool sapling) {
|
||||
return [=] (bool checked) {
|
||||
if (checked && this->rpc->getAllZAddresses() != nullptr) {
|
||||
auto addrs = this->rpc->getAllZAddresses();
|
||||
|
||||
// Save the current address, so we can update it later
|
||||
auto zaddr = ui->listReceiveAddresses->currentText();
|
||||
ui->listReceiveAddresses->clear();
|
||||
|
||||
std::for_each(addrs->begin(), addrs->end(), [=] (auto addr) {
|
||||
@@ -1315,6 +1320,10 @@ std::function<void(bool)> MainWindow::addZAddrsToComboList(bool sapling) {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (!zaddr.isEmpty() && Settings::isZAddress(zaddr)) {
|
||||
ui->listReceiveAddresses->setCurrentText(zaddr);
|
||||
}
|
||||
|
||||
// If z-addrs are empty, then create a new one.
|
||||
if (addrs->isEmpty()) {
|
||||
@@ -1508,6 +1517,10 @@ void MainWindow::setupReceiveTab() {
|
||||
void MainWindow::updateTAddrCombo(bool checked) {
|
||||
if (checked) {
|
||||
auto utxos = this->rpc->getUTXOs();
|
||||
|
||||
// Save the current address so we can restore it later
|
||||
auto currentTaddr = ui->listReceiveAddresses->currentText();
|
||||
|
||||
ui->listReceiveAddresses->clear();
|
||||
|
||||
// Maintain a set of addresses so we don't duplicate any, because we'll be adding
|
||||
@@ -1550,7 +1563,17 @@ void MainWindow::updateTAddrCombo(bool checked) {
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Add a last, disabled item if there are remaining items
|
||||
// 4. Add the previously selected t-address
|
||||
if (!currentTaddr.isEmpty() && Settings::isTAddress(currentTaddr)) {
|
||||
// Make sure the current taddr is in the list
|
||||
if (!addrs.contains(currentTaddr)) {
|
||||
auto bal = rpc->getAllBalances()->value(currentTaddr);
|
||||
ui->listReceiveAddresses->addItem(currentTaddr, bal);
|
||||
}
|
||||
ui->listReceiveAddresses->setCurrentText(currentTaddr);
|
||||
}
|
||||
|
||||
// 5. Add a last, disabled item if there are remaining items
|
||||
if (allTaddrs->size() > addrs.size()) {
|
||||
auto num = QString::number(allTaddrs->size() - addrs.size());
|
||||
ui->listReceiveAddresses->addItem("-- " + num + " more --", 0);
|
||||
|
||||
@@ -1149,14 +1149,6 @@
|
||||
<string>&Export all private keys</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionz_board_net">
|
||||
<property name="text">
|
||||
<string>&z-board.net</string>
|
||||
</property>
|
||||
<property name="shortcut">
|
||||
<string>Ctrl+A, Ctrl+Z</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_Address_Book">
|
||||
<property name="text">
|
||||
<string>Address &book</string>
|
||||
|
||||
28
src/rpc.cpp
28
src/rpc.cpp
@@ -27,8 +27,7 @@ RPC::RPC(MainWindow* main) {
|
||||
// Setup transactions table model
|
||||
transactionsTableModel = new TxTableModel(ui->transactionsTable);
|
||||
main->ui->transactionsTable->setModel(transactionsTableModel);
|
||||
main->ui->transactionsTable->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Stretch);
|
||||
|
||||
|
||||
// Set up timer to refresh Price
|
||||
priceTimer = new QTimer(main);
|
||||
QObject::connect(priceTimer, &QTimer::timeout, [=]() {
|
||||
@@ -220,7 +219,7 @@ void RPC::importTPrivKey(QString addr, bool rescan, const std::function<void(jso
|
||||
}
|
||||
|
||||
void RPC::validateAddress(QString address, const std::function<void(json)>& cb) {
|
||||
QString method = address.startsWith("z") ? "z_validateaddress" : "validateaddress";
|
||||
QString method = Settings::isZAddress(address) ? "z_validateaddress" : "validateaddress";
|
||||
|
||||
json payload = {
|
||||
{"jsonrpc", "1.0"},
|
||||
@@ -963,6 +962,29 @@ void RPC::addNewTxToWatch(const QString& newOpid, WatchedTx wtx) {
|
||||
watchTxStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a transaction with the standard UI. i.e., standard status bar message and standard error
|
||||
* handling
|
||||
*/
|
||||
void RPC::executeStandardUITransaction(Tx tx) {
|
||||
executeTransaction(tx,
|
||||
[=] (QString opid) {
|
||||
ui->statusBar->showMessage(QObject::tr("Computing Tx: ") % opid);
|
||||
},
|
||||
[=] (QString, QString txid) {
|
||||
ui->statusBar->showMessage(Settings::txidStatusMessage + " " + txid);
|
||||
},
|
||||
[=] (QString opid, QString errStr) {
|
||||
ui->statusBar->showMessage(QObject::tr(" Tx ") % opid % QObject::tr(" failed"), 15 * 1000);
|
||||
|
||||
if (!opid.isEmpty())
|
||||
errStr = QObject::tr("The transaction with id ") % opid % QObject::tr(" failed. The error was") + ":\n\n" + errStr;
|
||||
|
||||
QMessageBox::critical(main, QObject::tr("Transaction Error"), errStr, QMessageBox::Ok);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Execute a transaction!
|
||||
void RPC::executeTransaction(Tx tx,
|
||||
|
||||
@@ -58,6 +58,8 @@ public:
|
||||
void refreshZECPrice();
|
||||
void getZboardTopics(std::function<void(QMap<QString, QString>)> cb);
|
||||
|
||||
void executeStandardUITransaction(Tx tx);
|
||||
|
||||
void executeTransaction(Tx tx,
|
||||
const std::function<void(QString opid)> submitted,
|
||||
const std::function<void(QString opid, QString txid)> computed,
|
||||
|
||||
@@ -512,7 +512,12 @@ Tx MainWindow::createTxFromSendPage() {
|
||||
// If address is sprout, then we can't send change to sapling, because of turnstile.
|
||||
sendChangeToSapling = sendChangeToSapling && !Settings::getInstance()->isSproutAddress(addr);
|
||||
|
||||
double amt = ui->sendToWidgets->findChild<QLineEdit*>(QString("Amount") % QString::number(i+1))->text().trimmed().toDouble();
|
||||
QString amtStr = ui->sendToWidgets->findChild<QLineEdit*>(QString("Amount") % QString::number(i+1))->text().trimmed();
|
||||
if (amtStr.isEmpty()) {
|
||||
amtStr = "-1";; // The user didn't specify an amount
|
||||
}
|
||||
|
||||
double amt = amtStr.toDouble();
|
||||
totalAmt += amt;
|
||||
QString memo = ui->sendToWidgets->findChild<QLabel*>(QString("MemoTxt") % QString::number(i+1))->text().trimmed();
|
||||
|
||||
@@ -521,8 +526,7 @@ Tx MainWindow::createTxFromSendPage() {
|
||||
|
||||
if (Settings::getInstance()->getAllowCustomFees()) {
|
||||
tx.fee = ui->minerFeeAmt->text().toDouble();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
tx.fee = Settings::getMinerFee();
|
||||
}
|
||||
|
||||
@@ -731,6 +735,8 @@ bool MainWindow::confirmTx(Tx tx, RecurringPaymentInfo* rpi) {
|
||||
|
||||
// Send button clicked
|
||||
void MainWindow::sendButton() {
|
||||
// Create a Tx from the values on the send tab. Note that this Tx object
|
||||
// might not be valid yet.
|
||||
Tx tx = createTxFromSendPage();
|
||||
|
||||
QString error = doSendTxValidations(tx);
|
||||
@@ -810,7 +816,7 @@ QString MainWindow::doSendTxValidations(Tx tx) {
|
||||
// This technically shouldn't be possible, but issue #62 seems to have discovered a bug
|
||||
// somewhere, so just add a check to make sure.
|
||||
if (toAddr.amount < 0) {
|
||||
return QString(tr("Amount '%1' is invalid!").arg(toAddr.amount));
|
||||
return QString(tr("Amount for address '%1' is invalid!").arg(toAddr.addr));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -178,6 +178,7 @@ void Settings::saveRestore(QDialog* d) {
|
||||
|
||||
void Settings::saveRestoreTableHeader(QTableView* table, QDialog* d, QString tablename) {
|
||||
table->horizontalHeader()->restoreState(QSettings().value(tablename).toByteArray());
|
||||
table->horizontalHeader()->setStretchLastSection(true);
|
||||
|
||||
QObject::connect(d, &QDialog::finished, [=](auto) {
|
||||
QSettings().setValue(tablename, table->horizontalHeader()->saveState());
|
||||
@@ -250,17 +251,12 @@ QString Settings::getTokenName() {
|
||||
}
|
||||
}
|
||||
|
||||
QString Settings::getDonationAddr(bool sapling) {
|
||||
QString Settings::getDonationAddr() {
|
||||
if (Settings::getInstance()->isTestnet())
|
||||
if (sapling)
|
||||
return "ztestsapling1wn6889vznyu42wzmkakl2effhllhpe4azhu696edg2x6me4kfsnmqwpglaxzs7tmqsq7kudemp5";
|
||||
else
|
||||
return "ztn6fYKBii4Fp4vbGhkPgrtLU4XjXp4ZBMZgShtopmDGbn1L2JLTYbBp2b7SSkNr9F3rQeNZ9idmoR7s4JCVUZ7iiM5byhF";
|
||||
else
|
||||
if (sapling)
|
||||
return "zs1gv64eu0v2wx7raxqxlmj354y9ycznwaau9kduljzczxztvs4qcl00kn2sjxtejvrxnkucw5xx9u";
|
||||
else
|
||||
return "zcEgrceTwvoiFdEvPWcsJHAMrpLsprMF6aRJiQa3fan5ZphyXLPuHghnEPrEPRoEVzUy65GnMVyCTRdkT6BYBepnXh6NBYs";
|
||||
|
||||
}
|
||||
|
||||
bool Settings::addToZcashConf(QString confLocation, QString line) {
|
||||
@@ -320,13 +316,23 @@ double Settings::getZboardAmount() {
|
||||
|
||||
QString Settings::getZboardAddr() {
|
||||
if (Settings::getInstance()->isTestnet()) {
|
||||
return getDonationAddr(true);
|
||||
return getDonationAddr();
|
||||
}
|
||||
else {
|
||||
return "zs10m00rvkhfm4f7n23e4sxsx275r7ptnggx39ygl0vy46j9mdll5c97gl6dxgpk0njuptg2mn9w5s";
|
||||
}
|
||||
}
|
||||
|
||||
bool Settings::isValidSaplingPrivateKey(QString pk) {
|
||||
if (isTestnet()) {
|
||||
QRegExp zspkey("^secret-extended-key-test[0-9a-z]{278}$", Qt::CaseInsensitive);
|
||||
return zspkey.exactMatch(pk);
|
||||
} else {
|
||||
QRegExp zspkey("^secret-extended-key-main[0-9a-z]{278}$", Qt::CaseInsensitive);
|
||||
return zspkey.exactMatch(pk);
|
||||
}
|
||||
}
|
||||
|
||||
bool Settings::isValidAddress(QString addr) {
|
||||
QRegExp zcexp("^z[a-z0-9]{94}$", Qt::CaseInsensitive);
|
||||
QRegExp zsexp("^z[a-z0-9]{77}$", Qt::CaseInsensitive);
|
||||
|
||||
@@ -37,6 +37,8 @@ public:
|
||||
bool isSaplingAddress(QString addr);
|
||||
bool isSproutAddress(QString addr);
|
||||
|
||||
bool isValidSaplingPrivateKey(QString pk);
|
||||
|
||||
bool isSyncing();
|
||||
void setSyncing(bool syncing);
|
||||
|
||||
@@ -101,7 +103,7 @@ public:
|
||||
static QString getZECUSDDisplayFormat(double bal);
|
||||
|
||||
static QString getTokenName();
|
||||
static QString getDonationAddr(bool sapling);
|
||||
static QString getDonationAddr();
|
||||
|
||||
static double getMinerFee();
|
||||
static double getZboardAmount();
|
||||
|
||||
@@ -104,9 +104,9 @@ void TxTableModel::updateAllData() {
|
||||
|
||||
QVariant TxTableModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
// Align column 4,5 (confirmations, amount) right
|
||||
// Align numeric columns (confirmations, amount) right
|
||||
if (role == Qt::TextAlignmentRole &&
|
||||
(index.column() == 3 || index.column() == 4))
|
||||
(index.column() == Column::Confirmations || index.column() == Column::Amount))
|
||||
return QVariant(Qt::AlignRight | Qt::AlignVCenter);
|
||||
|
||||
auto dat = modeldata->at(index.row());
|
||||
@@ -125,23 +125,23 @@ void TxTableModel::updateAllData() {
|
||||
|
||||
if (role == Qt::DisplayRole) {
|
||||
switch (index.column()) {
|
||||
case 0: return dat.type;
|
||||
case 1: {
|
||||
case Column::Type: return dat.type;
|
||||
case Column::Address: {
|
||||
auto addr = dat.address;
|
||||
if (addr.trimmed().isEmpty())
|
||||
return "(Shielded)";
|
||||
else
|
||||
return addr;
|
||||
}
|
||||
case 2: return QDateTime::fromMSecsSinceEpoch(dat.datetime * (qint64)1000).toLocalTime().toString();
|
||||
case 3: return QString::number(dat.confirmations);
|
||||
case 4: return Settings::getZECDisplayFormat(dat.amount);
|
||||
case Column::Time: return QDateTime::fromMSecsSinceEpoch(dat.datetime * (qint64)1000).toLocalTime().toString();
|
||||
case Column::Confirmations: return QString::number(dat.confirmations);
|
||||
case Column::Amount: return Settings::getZECDisplayFormat(dat.amount);
|
||||
}
|
||||
}
|
||||
|
||||
if (role == Qt::ToolTipRole) {
|
||||
switch (index.column()) {
|
||||
case 0: {
|
||||
case Column::Type: {
|
||||
if (dat.memo.startsWith("zcash:")) {
|
||||
return Settings::paymentURIPretty(Settings::parseURI(dat.memo));
|
||||
} else {
|
||||
@@ -149,16 +149,16 @@ void TxTableModel::updateAllData() {
|
||||
(dat.memo.isEmpty() ? "" : " tx memo: \"" + dat.memo + "\"");
|
||||
}
|
||||
}
|
||||
case 1: {
|
||||
case Column::Address: {
|
||||
auto addr = modeldata->at(index.row()).address;
|
||||
if (addr.trimmed().isEmpty())
|
||||
return "(Shielded)";
|
||||
else
|
||||
return addr;
|
||||
}
|
||||
case 2: return QDateTime::fromMSecsSinceEpoch(modeldata->at(index.row()).datetime * (qint64)1000).toLocalTime().toString();
|
||||
case 3: return QString("%1 Network Confirmations").arg(QString::number(dat.confirmations));
|
||||
case 4: return Settings::getInstance()->getUSDFromZecAmount(modeldata->at(index.row()).amount);
|
||||
case Column::Time: return QDateTime::fromMSecsSinceEpoch(modeldata->at(index.row()).datetime * (qint64)1000).toLocalTime().toString();
|
||||
case Column::Confirmations: return QString("%1 Network Confirmations").arg(QString::number(dat.confirmations));
|
||||
case Column::Amount: return Settings::getInstance()->getUSDFromZecAmount(modeldata->at(index.row()).amount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,7 +187,7 @@ void TxTableModel::updateAllData() {
|
||||
|
||||
QVariant TxTableModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if (role == Qt::TextAlignmentRole && (section == 3 || section == 4))
|
||||
if (role == Qt::TextAlignmentRole && (section == Column::Confirmations || section == Column::Amount))
|
||||
return QVariant(Qt::AlignRight | Qt::AlignVCenter);
|
||||
|
||||
if (role == Qt::FontRole) {
|
||||
|
||||
@@ -11,6 +11,15 @@ public:
|
||||
TxTableModel(QObject* parent);
|
||||
~TxTableModel();
|
||||
|
||||
enum Column
|
||||
{
|
||||
Type = 0,
|
||||
Address = 1,
|
||||
Time = 2,
|
||||
Confirmations = 3,
|
||||
Amount = 4
|
||||
};
|
||||
|
||||
void addTData (const QList<TransactionItem>& data);
|
||||
void addZSentData(const QList<TransactionItem>& data);
|
||||
void addZRecvData(const QList<TransactionItem>& data);
|
||||
|
||||
Reference in New Issue
Block a user