Browse Source

Add transactions fetch

pull/19/head
Aditya Kulkarni 5 years ago
parent
commit
e3ac42adfc
  1. 124
      src/controller.cpp
  2. 2
      src/controller.h
  3. 8
      src/liteinterface.cpp
  4. 15
      src/liteinterface.h
  5. 12
      src/mainwindow.cpp
  6. 115
      src/senttxstore.cpp
  7. 20
      src/senttxstore.h
  8. 134
      src/txtablemodel.cpp
  9. 10
      src/txtablemodel.h
  10. 2
      zecwallet-lite.pro

124
src/controller.cpp

@ -2,7 +2,6 @@
#include "addressbook.h"
#include "settings.h"
#include "senttxstore.h"
#include "version.h"
#include "websockets.h"
@ -143,9 +142,7 @@ void Controller::noConnection() {
// Clear Transactions table.
QList<TransactionItem> emptyTxs;
transactionsTableModel->addTData(emptyTxs);
transactionsTableModel->addZRecvData(emptyTxs);
transactionsTableModel->addZSentData(emptyTxs);
transactionsTableModel->replaceData(emptyTxs);
// Clear balances
ui->balSheilded->setText("");
@ -160,28 +157,6 @@ void Controller::noConnection() {
ui->inputsCombo->clear();
}
// Refresh received z txs by calling z_listreceivedbyaddress/gettransaction
void Controller::refreshReceivedZTrans(QList<QString> zaddrs) {
if (!zrpc->haveConnection())
return noConnection();
// We'll only refresh the received Z txs if settings allows us.
if (!Settings::getInstance()->getSaveZtxs()) {
QList<TransactionItem> emptylist;
transactionsTableModel->addZRecvData(emptylist);
return;
}
zrpc->fetchReceivedZTrans(zaddrs,
[=] (QString addr) {
model->markAddressUsed(addr);
},
[=] (QList<TransactionItem> txdata) {
transactionsTableModel->addZRecvData(txdata);
}
);
}
/// This will refresh all the balance data from zcashd
void Controller::refresh(bool force) {
if (!zrpc->haveConnection())
@ -190,7 +165,6 @@ void Controller::refresh(bool force) {
getInfoThenRefresh(force);
}
void Controller::getInfoThenRefresh(bool force) {
if (!zrpc->haveConnection())
return noConnection();
@ -275,8 +249,7 @@ void Controller::refreshAddresses() {
model->replaceTaddresses(newtaddresses);
// Refresh the sent and received txs from all these z-addresses
refreshSentZTrans();
refreshReceivedZTrans(model->getAllZAddresses());
refreshTransactions();
});
}
@ -362,57 +335,64 @@ void Controller::refreshTransactions() {
zrpc->fetchTransactions([=] (json reply) {
QList<TransactionItem> txdata;
for (auto& it : reply.get<json::array_t>()) {
double fee = 0;
if (!it["fee"].is_null()) {
fee = it["fee"].get<json::number_float_t>();
}
QString address = (it["address"].is_null() ? "" : QString::fromStdString(it["address"]));
TransactionItem tx{
QString::fromStdString(it["category"]),
(qint64)it["time"].get<json::number_unsigned_t>(),
address,
QString::fromStdString(it["txid"]),
it["amount"].get<json::number_float_t>() + fee,
static_cast<long>(it["confirmations"].get<json::number_unsigned_t>()),
"", "" };
QString address;
qint64 total_amount;
QList<TransactionItemDetail> items;
txdata.push_back(tx);
if (!address.isEmpty())
model->markAddressUsed(address);
}
// Update model data, which updates the table view
transactionsTableModel->addTData(txdata);
});
}
// First, check if there's outgoing metadata
if (!it["outgoing_metadata"].is_null()) {
for (auto o: it["outgoing_metadata"].get<json::array_t>()) {
QString address = QString::fromStdString(o["address"]);
qint64 amount = o["value"].get<json::number_unsigned_t>();
QString memo;
if (!o["memo"].is_null()) {
memo = QString::fromStdString(o["memo"]);
}
// Read sent Z transactions from the file.
void Controller::refreshSentZTrans() {
if (!zrpc->haveConnection())
return noConnection();
items.push_back(TransactionItemDetail{address, amount, memo});
total_amount += amount;
}
auto sentZTxs = SentTxStore::readSentTxFile();
if (items.length() == 1) {
address = items[0].address;
} else {
address = "(Multiple)";
}
// If there are no sent z txs, then empty the table.
// This happens when you clear history.
if (sentZTxs.isEmpty()) {
transactionsTableModel->addZSentData(sentZTxs);
return;
}
txdata.push_back(TransactionItem{
"Sent",
it["block_height"].get<json::number_unsigned_t>(),
address,
QString::fromStdString(it["txid"]),
1,
items
});
} else {
// Incoming Transaction
address = (it["address"].is_null() ? "" : QString::fromStdString(it["address"]));
model->markAddressUsed(address);
QList<QString> txids;
TransactionItem tx{
"Receive",
it["block_height"].get<json::number_unsigned_t>(),
address,
QString::fromStdString(it["txid"]),
1,
items
};
for (auto sentTx: sentZTxs) {
txids.push_back(sentTx.txid);
}
txdata.push_back(tx);
}
}
// Look up all the txids to get the confirmation count for them.
zrpc->fetchReceivedTTrans(txids, sentZTxs, [=](auto newSentZTxs) {
transactionsTableModel->addZSentData(newSentZTxs);
// Update model data, which updates the table view
transactionsTableModel->replaceData(txdata);
});
}
@ -485,8 +465,6 @@ void Controller::watchTxStatus() {
if (status == "success") {
auto txid = QString::fromStdString(it["result"]["txid"]);
SentTxStore::addToSentTx(watchingOps[id].tx, txid);
auto wtx = watchingOps[id];
watchingOps.remove(id);

2
src/controller.h

@ -86,8 +86,6 @@ private:
void refreshBalances();
void refreshTransactions();
void refreshSentZTrans();
void refreshReceivedZTrans(QList<QString> zaddresses);
bool processUnspent (const json& reply, QMap<QString, qint64>* newBalances, QList<UnspentOutput>* newUtxos);
void updateUI (bool anyUnconfirmed);

8
src/liteinterface.cpp

@ -155,13 +155,7 @@ void LiteInterface::fetchTransactions(const std::function<void(json)>& cb) {
if (conn == nullptr)
return;
// json payload = {
// {"jsonrpc", "1.0"},
// {"id", "someid"},
// {"method", "listtransactions"}
// };
// conn->doRPCWithDefaultErrorHandling(payload, cb);
conn->doRPCWithDefaultErrorHandling("list", "", cb);
}
void LiteInterface::sendZTransaction(json params, const std::function<void(json)>& cb,

15
src/liteinterface.h

@ -7,15 +7,24 @@
using json = nlohmann::json;
// Since each transaction can contain multiple outputs, we separate them out individually
// into a struct with address, amount, memo
struct TransactionItemDetail {
QString address;
qint64 amount;
QString memo;
};
// Represents a row in the transactions table. Note that each transaction can contain
// multiple addresses (i.e., Multiple TransctionItemDetail)
struct TransactionItem {
QString type;
qint64 datetime;
QString address;
QString txid;
double amount;
long confirmations;
QString fromAddr;
QString memo;
QList<TransactionItemDetail> items;
};

12
src/mainwindow.cpp

@ -16,7 +16,6 @@
#include "balancestablemodel.h"
#include "settings.h"
#include "version.h"
#include "senttxstore.h"
#include "connection.h"
#include "requestdialog.h"
#include "websockets.h"
@ -275,17 +274,6 @@ void MainWindow::setupSettingsModal() {
Settings::getInstance()->setSaveZtxs(checked);
});
// Setup clear button
QObject::connect(settings.btnClearSaved, &QCheckBox::clicked, [=]() {
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?",
QMessageBox::Yes, QMessageBox::Cancel)) {
SentTxStore::deleteHistory();
// Reload after the clear button so existing txs disappear
rpc->refresh(true);
}
});
// Setup theme combo
int theme_index = settings.comboBoxTheme->findText(Settings::getInstance()->get_theme_name(), Qt::MatchExactly);
settings.comboBoxTheme->setCurrentIndex(theme_index);

115
src/senttxstore.cpp

@ -1,115 +0,0 @@
#include "senttxstore.h"
#include "settings.h"
/// Get the location of the app data file to be written.
QString SentTxStore::writeableFile() {
auto filename = QStringLiteral("senttxstore.dat");
auto dir = QDir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation));
if (!dir.exists())
QDir().mkpath(dir.absolutePath());
if (Settings::getInstance()->isTestnet()) {
return dir.filePath("testnet-" % filename);
} else {
return dir.filePath(filename);
}
}
// delete the sent history.
void SentTxStore::deleteHistory() {
QFile data(writeableFile());
data.remove();
data.close();
}
QList<TransactionItem> SentTxStore::readSentTxFile() {
QFile data(writeableFile());
if (!data.exists()) {
return QList<TransactionItem>();
}
QJsonDocument jsonDoc;
data.open(QFile::ReadOnly);
jsonDoc = QJsonDocument::fromJson(data.readAll());
data.close();
QList<TransactionItem> items;
for (auto i : jsonDoc.array()) {
auto sentTx = i.toObject();
TransactionItem t{"send", (qint64)sentTx["datetime"].toVariant().toLongLong(),
sentTx["address"].toString(),
sentTx["txid"].toString(),
sentTx["amount"].toDouble() + sentTx["fee"].toDouble(),
0, sentTx["from"].toString(), ""};
items.push_back(t);
}
return items;
}
void SentTxStore::addToSentTx(Tx tx, QString txid) {
// Save transactions only if the settings are allowed
if (!Settings::getInstance()->getSaveZtxs())
return;
// Also, only store outgoing txs where the from address is a z-Addr. Else, regular zcashd
// stores it just fine
if (! Settings::isZAddress(tx.fromAddr))
return;
QFile data(writeableFile());
QJsonDocument jsonDoc;
// If data doesn't exist, then create a blank one
if (!data.exists()) {
QJsonArray a;
jsonDoc.setArray(a);
QFile newFile(writeableFile());
newFile.open(QFile::WriteOnly);
newFile.write(jsonDoc.toJson());
newFile.close();
} else {
data.open(QFile::ReadOnly);
jsonDoc = QJsonDocument().fromJson(data.readAll());
data.close();
}
// Calculate total amount in this tx
double totalAmount = 0;
for (auto i : tx.toAddrs) {
totalAmount += i.amount;
}
QString toAddresses;
if (tx.toAddrs.length() == 1) {
toAddresses = tx.toAddrs[0].addr;
} else {
// Concatenate all the toAddresses
for (auto a : tx.toAddrs) {
toAddresses += a.addr % "(" % Settings::getZECDisplayFormat(a.amount) % ") ";
}
}
auto list = jsonDoc.array();
QJsonObject txItem;
txItem["type"] = "sent";
txItem["from"] = tx.fromAddr;
txItem["datetime"] = QDateTime::currentMSecsSinceEpoch() / (qint64)1000;
txItem["address"] = toAddresses;
txItem["txid"] = txid;
txItem["amount"] = -totalAmount;
txItem["fee"] = -tx.fee;
list.append(txItem);
jsonDoc.setArray(list);
QFile writer(writeableFile());
if (writer.open(QFile::WriteOnly | QFile::Truncate)) {
writer.write(jsonDoc.toJson());
}
writer.close();
}

20
src/senttxstore.h

@ -1,20 +0,0 @@
#ifndef SENTTXSTORE_H
#define SENTTXSTORE_H
#include "precompiled.h"
#include "mainwindow.h"
#include "controller.h"
class SentTxStore {
public:
static void deleteHistory();
static QList<TransactionItem> readSentTxFile();
static void addToSentTx(Tx tx, QString txid);
private:
static QString writeableFile();
};
#endif // SENTTXSTORE_H

134
src/txtablemodel.cpp

@ -9,34 +9,15 @@ TxTableModel::TxTableModel(QObject *parent)
TxTableModel::~TxTableModel() {
delete modeldata;
delete tTrans;
delete zsTrans;
delete zrTrans;
}
void TxTableModel::addZSentData(const QList<TransactionItem>& data) {
delete zsTrans;
zsTrans = new QList<TransactionItem>();
std::copy(data.begin(), data.end(), std::back_inserter(*zsTrans));
updateAllData();
}
void TxTableModel::addZRecvData(const QList<TransactionItem>& data) {
delete zrTrans;
zrTrans = new QList<TransactionItem>();
std::copy(data.begin(), data.end(), std::back_inserter(*zrTrans));
updateAllData();
}
void TxTableModel::addTData(const QList<TransactionItem>& data) {
delete tTrans;
tTrans = new QList<TransactionItem>();
std::copy(data.begin(), data.end(), std::back_inserter(*tTrans));
void TxTableModel::replaceData(const QList<TransactionItem>& data) {
delete modeldata;
modeldata = new QList<TransactionItem>();
std::copy(data.begin(), data.end(), std::back_inserter(*modeldata));
updateAllData();
dataChanged(index(0, 0), index(modeldata->size()-1, columnCount(index(0,0))-1));
layoutChanged();
}
bool TxTableModel::exportToCsv(QString fileName) const {
@ -61,8 +42,9 @@ bool TxTableModel::exportToCsv(QString fileName) const {
for (int col = 0; col < headers.length(); col++) {
out << "\"" << data(index(row, col), Qt::DisplayRole).toString() << "\",";
}
// Memo
out << "\"" << modeldata->at(row).memo << "\"";
out << "\"" << this->getMemo(row) << "\"";
out << endl;
}
@ -70,25 +52,6 @@ bool TxTableModel::exportToCsv(QString fileName) const {
return true;
}
void TxTableModel::updateAllData() {
auto newmodeldata = new QList<TransactionItem>();
if (tTrans != nullptr) std::copy( tTrans->begin(), tTrans->end(), std::back_inserter(*newmodeldata));
if (zsTrans != nullptr) std::copy(zsTrans->begin(), zsTrans->end(), std::back_inserter(*newmodeldata));
if (zrTrans != nullptr) std::copy(zrTrans->begin(), zrTrans->end(), std::back_inserter(*newmodeldata));
// Sort by reverse time
std::sort(newmodeldata->begin(), newmodeldata->end(), [=] (auto a, auto b) {
return a.datetime > b.datetime; // reverse sort
});
// And then swap out the modeldata with the new one.
delete modeldata;
modeldata = newmodeldata;
dataChanged(index(0, 0), index(modeldata->size()-1, columnCount(index(0,0))-1));
layoutChanged();
}
int TxTableModel::rowCount(const QModelIndex&) const
{
@ -135,18 +98,31 @@ void TxTableModel::updateAllData() {
}
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);
case Column::Amount: {
// Sum up all the amounts
qint64 total = 0;
for (int i=0; i < dat.items.length(); i++) {
total += dat.items[i].amount;
}
return Settings::getZECDisplayFormat(total);
}
}
}
if (role == Qt::ToolTipRole) {
switch (index.column()) {
case Column::Type: {
if (dat.memo.startsWith("zcash:")) {
return Settings::paymentURIPretty(Settings::parseURI(dat.memo));
// If there are multiple memos, then mark them as such
if (dat.items.length() == 1) {
auto memo = dat.items[0].memo;
if (memo.startsWith("zcash:")) {
return Settings::paymentURIPretty(Settings::parseURI(memo));
} else {
return modeldata->at(index.row()).type +
(memo.isEmpty() ? "" : " tx memo: \"" + memo + "\"");
}
} else {
return modeldata->at(index.row()).type +
(dat.memo.isEmpty() ? "" : " tx memo: \"" + dat.memo + "\"");
return "Multiple";
}
}
case Column::Address: {
@ -158,21 +134,33 @@ void TxTableModel::updateAllData() {
}
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);
case Column::Amount: {
// Sum up all the amounts
qint64 total = 0;
for (int i=0; i < dat.items.length(); i++) {
total += dat.items[i].amount;
}
return Settings::getInstance()->getUSDFromZecAmount(total);
}
}
}
if (role == Qt::DecorationRole && index.column() == 0) {
if (!dat.memo.isEmpty()) {
// 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));
}
bool hasMemo = false;
for (int i=0; i < dat.items.length(); i++) {
if (!dat.items[i].memo.isEmpty()) {
hasMemo = true;
}
}
// If the memo is a Payment URI, then show a payment request icon
if (dat.items.length() == 1 && dat.items[0].memo.startsWith("zcash:")) {
QIcon icon(":/icons/res/paymentreq.gif");
return QVariant(icon.pixmap(16, 16));
} else if (hasMemo) {
// 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);
@ -208,7 +196,21 @@ QString TxTableModel::getTxId(int row) const {
}
QString TxTableModel::getMemo(int row) const {
return modeldata->at(row).memo;
auto dat = modeldata->at(row);
bool hasMemo = false;
for (int i=0; i < dat.items.length(); i++) {
if (!dat.items[i].memo.isEmpty()) {
hasMemo = true;
}
}
if (dat.items.length() == 1) {
return dat.items[0].memo;
} else if (hasMemo) {
return "(Multiple)";
} else {
return "";
}
}
qint64 TxTableModel::getConfirmations(int row) const {
@ -228,5 +230,11 @@ QString TxTableModel::getType(int row) const {
}
QString TxTableModel::getAmt(int row) const {
return Settings::getDecimalString(modeldata->at(row).amount);
auto dat = modeldata->at(row);
qint64 total = 0;
for (int i=0; i < dat.items.length(); i++) {
total += dat.items[i].amount;
}
return Settings::getDecimalString(total);
}

10
src/txtablemodel.h

@ -20,9 +20,7 @@ public:
Amount = 4
};
void addTData (const QList<TransactionItem>& data);
void addZSentData(const QList<TransactionItem>& data);
void addZRecvData(const QList<TransactionItem>& data);
void replaceData (const QList<TransactionItem>& data);
QString getTxId(int row) const;
QString getMemo(int row) const;
@ -40,12 +38,6 @@ public:
QVariant headerData(int section, Qt::Orientation orientation, int role) const;
private:
void updateAllData();
QList<TransactionItem>* tTrans = nullptr;
QList<TransactionItem>* zrTrans = nullptr; // Z received
QList<TransactionItem>* zsTrans = nullptr; // Z sent
QList<TransactionItem>* modeldata = nullptr;
QList<QString> headers;

2
zecwallet-lite.pro

@ -44,7 +44,6 @@ SOURCES += \
src/3rdparty/qrcode/QrSegment.cpp \
src/settings.cpp \
src/sendtab.cpp \
src/senttxstore.cpp \
src/txtablemodel.cpp \
src/qrcodelabel.cpp \
src/connection.cpp \
@ -73,7 +72,6 @@ HEADERS += \
src/3rdparty/json/json.hpp \
src/settings.h \
src/txtablemodel.h \
src/senttxstore.h \
src/qrcodelabel.h \
src/connection.h \
src/fillediconlabel.h \

Loading…
Cancel
Save