mirror of
https://github.com/wownero/wowlet.git
synced 2026-10-30 04:39:57 +01:00
387 lines
15 KiB
C++
387 lines
15 KiB
C++
// SPDX-License-Identifier: BSD-3-Clause
|
|
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
|
|
|
|
#include "TransactionHistory.h"
|
|
#include "utils/Utils.h"
|
|
#include "utils/AppData.h"
|
|
#include "utils/config.h"
|
|
#include "constants.h"
|
|
#include "WalletManager.h"
|
|
#include "Transfer.h"
|
|
#include "Ring.h"
|
|
|
|
bool TransactionHistory::transaction(int index, std::function<void (TransactionRow &)> callback)
|
|
{
|
|
QReadLocker locker(&m_lock);
|
|
|
|
if (index < 0 || index >= m_rows.size()) {
|
|
qCritical("%s: no transaction info for index %d", __FUNCTION__, index);
|
|
qCritical("%s: there's %d transactions in backend", __FUNCTION__, this->count());
|
|
return false;
|
|
}
|
|
|
|
callback(*m_rows.value(index));
|
|
return true;
|
|
}
|
|
|
|
TransactionRow* TransactionHistory::transaction(const QString &id)
|
|
{
|
|
QReadLocker locker(&m_lock);
|
|
|
|
auto itr = std::find_if(m_rows.begin(), m_rows.end(),
|
|
[&](const TransactionRow * ti) {
|
|
return ti->hash() == id;
|
|
});
|
|
return itr != m_rows.end() ? *itr : nullptr;
|
|
}
|
|
|
|
TransactionRow* TransactionHistory::transaction(int index)
|
|
{
|
|
if (index < 0 || index >= m_rows.size()) {
|
|
return nullptr;
|
|
}
|
|
|
|
return m_rows[index];
|
|
}
|
|
|
|
void TransactionHistory::refresh()
|
|
{
|
|
QDateTime firstDateTime = QDate(2014, 4, 18).startOfDay();
|
|
QDateTime lastDateTime = QDateTime::currentDateTime().addDays(1); // tomorrow (guard against jitter and timezones)
|
|
|
|
emit refreshStarted();
|
|
|
|
{
|
|
QWriteLocker locker(&m_lock);
|
|
|
|
clearRows();
|
|
|
|
quint64 lastTxHeight = 0;
|
|
m_locked = false;
|
|
m_minutesToUnlock = 0;
|
|
|
|
|
|
uint64_t min_height = 0;
|
|
uint64_t max_height = (uint64_t)-1;
|
|
uint64_t wallet_height = m_wallet->blockChainHeight();
|
|
uint32_t account = m_wallet->currentSubaddressAccount();
|
|
|
|
// transactions are stored in wallet2:
|
|
// - confirmed_transfer_details - out transfers
|
|
// - unconfirmed_transfer_details - pending out transfers
|
|
// - payment_details - input transfers
|
|
|
|
// payments are "input transactions";
|
|
// one input transaction contains only one transfer. e.g. <transaction_id> - <100XMR>
|
|
|
|
std::list<std::pair<crypto::hash, tools::wallet2::payment_details>> in_payments;
|
|
m_wallet2->get_payments(in_payments, min_height, max_height);
|
|
for (std::list<std::pair<crypto::hash, tools::wallet2::payment_details>>::const_iterator i = in_payments.begin(); i != in_payments.end(); ++i)
|
|
{
|
|
const tools::wallet2::payment_details &pd = i->second;
|
|
if (pd.m_subaddr_index.major != account) {
|
|
continue;
|
|
}
|
|
|
|
std::string payment_id = epee::string_tools::pod_to_hex(i->first);
|
|
if (payment_id.substr(16).find_first_not_of('0') == std::string::npos)
|
|
payment_id = payment_id.substr(0,16);
|
|
|
|
auto* t = new TransactionRow();
|
|
t->m_paymentId = QString::fromStdString(payment_id);
|
|
t->m_coinbase = pd.m_coinbase;
|
|
t->m_amount = pd.m_amount;
|
|
t->m_balanceDelta = pd.m_amount;
|
|
t->m_fee = pd.m_fee;
|
|
t->m_direction = TransactionRow::Direction_In;
|
|
t->m_hash = QString::fromStdString(epee::string_tools::pod_to_hex(pd.m_tx_hash));
|
|
t->m_blockHeight = pd.m_block_height;
|
|
t->m_description = QString::fromStdString(m_wallet2->get_tx_note(pd.m_tx_hash));
|
|
t->m_subaddrIndex = { pd.m_subaddr_index.minor };
|
|
t->m_subaddrAccount = pd.m_subaddr_index.major;
|
|
t->m_label = QString::fromStdString(m_wallet2->get_subaddress_label(pd.m_subaddr_index));
|
|
t->m_timestamp = QDateTime::fromSecsSinceEpoch(pd.m_timestamp);
|
|
t->m_confirmations = (wallet_height > pd.m_block_height) ? wallet_height - pd.m_block_height : 0;
|
|
t->m_unlockTime = pd.m_unlock_time;
|
|
|
|
m_rows.append(t);
|
|
}
|
|
|
|
// confirmed output transactions
|
|
// one output transaction may contain more than one money transfer, e.g.
|
|
// <transaction_id>:
|
|
// transfer1: 100XMR to <address_1>
|
|
// transfer2: 50XMR to <address_2>
|
|
// fee: fee charged per transaction
|
|
//
|
|
|
|
std::list<std::pair<crypto::hash, tools::wallet2::confirmed_transfer_details>> out_payments;
|
|
m_wallet2->get_payments_out(out_payments, min_height, max_height);
|
|
|
|
for (std::list<std::pair<crypto::hash, tools::wallet2::confirmed_transfer_details>>::const_iterator i = out_payments.begin();
|
|
i != out_payments.end(); ++i) {
|
|
|
|
const crypto::hash &hash = i->first;
|
|
const tools::wallet2::confirmed_transfer_details &pd = i->second;
|
|
if (pd.m_subaddr_account != account) {
|
|
continue;
|
|
}
|
|
|
|
uint64_t change = pd.m_change == (uint64_t)-1 ? 0 : pd.m_change; // change may not be known
|
|
uint64_t fee = pd.m_amount_in - pd.m_amount_out;
|
|
|
|
std::string payment_id = epee::string_tools::pod_to_hex(i->second.m_payment_id);
|
|
if (payment_id.substr(16).find_first_not_of('0') == std::string::npos)
|
|
payment_id = payment_id.substr(0,16);
|
|
|
|
auto* t = new TransactionRow();
|
|
t->m_paymentId = QString::fromStdString(payment_id);
|
|
|
|
t->m_amount = pd.m_amount_out - change;
|
|
t->m_balanceDelta = change - pd.m_amount_in;
|
|
t->m_fee = fee;
|
|
|
|
t->m_direction = TransactionRow::Direction_Out;
|
|
t->m_hash = QString::fromStdString(epee::string_tools::pod_to_hex(hash));
|
|
t->m_blockHeight = pd.m_block_height;
|
|
t->m_description = QString::fromStdString(m_wallet2->get_tx_note(hash));
|
|
t->m_subaddrAccount = pd.m_subaddr_account;
|
|
t->m_label = QString::fromStdString(pd.m_subaddr_indices.size() == 1 ? m_wallet2->get_subaddress_label({pd.m_subaddr_account, *pd.m_subaddr_indices.begin()}) : "");
|
|
t->m_timestamp = QDateTime::fromSecsSinceEpoch(pd.m_timestamp);
|
|
t->m_confirmations = (wallet_height > pd.m_block_height) ? wallet_height - pd.m_block_height : 0;
|
|
|
|
for (uint32_t idx : t->subaddrIndex())
|
|
{
|
|
t->m_subaddrIndex.insert(idx);
|
|
}
|
|
|
|
// single output transaction might contain multiple transfers
|
|
for (auto const &d: pd.m_dests)
|
|
{
|
|
Transfer *transfer = new Transfer(d.amount, QString::fromStdString(d.address(m_wallet2->nettype(), pd.m_payment_id)), this);
|
|
t->m_transfers.append(transfer);
|
|
}
|
|
for (auto const &r: pd.m_rings)
|
|
{
|
|
Ring *ring = new Ring(QString::fromStdString(epee::string_tools::pod_to_hex(r.first)), cryptonote::relative_output_offsets_to_absolute(r.second), this);
|
|
t->m_rings.append(ring);
|
|
}
|
|
|
|
m_rows.append(t);
|
|
}
|
|
|
|
// unconfirmed output transactions
|
|
std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>> upayments_out;
|
|
m_wallet2->get_unconfirmed_payments_out(upayments_out);
|
|
for (std::list<std::pair<crypto::hash, tools::wallet2::unconfirmed_transfer_details>>::const_iterator i = upayments_out.begin(); i != upayments_out.end(); ++i) {
|
|
const tools::wallet2::unconfirmed_transfer_details &pd = i->second;
|
|
if (pd.m_subaddr_account != account) {
|
|
continue;
|
|
}
|
|
|
|
const crypto::hash &hash = i->first;
|
|
uint64_t amount = pd.m_amount_in;
|
|
uint64_t fee = amount - pd.m_amount_out;
|
|
uint64_t change = pd.m_change == (uint64_t)-1 ? 0 : pd.m_change;
|
|
std::string payment_id = epee::string_tools::pod_to_hex(i->second.m_payment_id);
|
|
if (payment_id.substr(16).find_first_not_of('0') == std::string::npos)
|
|
payment_id = payment_id.substr(0,16);
|
|
bool is_failed = pd.m_state == tools::wallet2::unconfirmed_transfer_details::failed;
|
|
|
|
auto *t = new TransactionRow();
|
|
t->m_paymentId = QString::fromStdString(payment_id);
|
|
|
|
t->m_amount = pd.m_amount_out - change;
|
|
t->m_balanceDelta = change - pd.m_amount_in;
|
|
t->m_fee = fee;
|
|
|
|
t->m_direction = TransactionRow::Direction_Out;
|
|
t->m_failed = is_failed;
|
|
t->m_pending = true;
|
|
t->m_hash = QString::fromStdString(epee::string_tools::pod_to_hex(hash));
|
|
t->m_description = QString::fromStdString(m_wallet2->get_tx_note(hash));
|
|
t->m_subaddrAccount = pd.m_subaddr_account;
|
|
t->m_label = QString::fromStdString(pd.m_subaddr_indices.size() == 1 ? m_wallet2->get_subaddress_label({pd.m_subaddr_account, *pd.m_subaddr_indices.begin()}) : "");
|
|
t->m_timestamp = QDateTime::fromSecsSinceEpoch(pd.m_timestamp);
|
|
t->m_confirmations = 0;
|
|
for (uint32_t idx : t->subaddrIndex())
|
|
{
|
|
t->m_subaddrIndex.insert(idx);
|
|
}
|
|
|
|
for (auto const &d: pd.m_dests)
|
|
{
|
|
Transfer *transfer = new Transfer(d.amount, QString::fromStdString(d.address(m_wallet2->nettype(), pd.m_payment_id)), this);
|
|
t->m_transfers.append(transfer);
|
|
}
|
|
for (auto const &r: pd.m_rings)
|
|
{
|
|
Ring *ring = new Ring(QString::fromStdString(epee::string_tools::pod_to_hex(r.first)), cryptonote::relative_output_offsets_to_absolute(r.second), this);
|
|
t->m_rings.append(ring);
|
|
}
|
|
|
|
m_rows.append(t);
|
|
}
|
|
|
|
|
|
// unconfirmed payments (tx pool)
|
|
std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>> upayments;
|
|
m_wallet2->get_unconfirmed_payments(upayments);
|
|
for (std::list<std::pair<crypto::hash, tools::wallet2::pool_payment_details>>::const_iterator i = upayments.begin(); i != upayments.end(); ++i) {
|
|
const tools::wallet2::payment_details &pd = i->second.m_pd;
|
|
if (pd.m_subaddr_index.major != account) {
|
|
continue;
|
|
}
|
|
|
|
std::string payment_id = epee::string_tools::pod_to_hex(i->first);
|
|
if (payment_id.substr(16).find_first_not_of('0') == std::string::npos)
|
|
payment_id = payment_id.substr(0,16);
|
|
auto *t = new TransactionRow();
|
|
t->m_paymentId = QString::fromStdString(payment_id);
|
|
t->m_amount = pd.m_amount;
|
|
t->m_balanceDelta = pd.m_amount;
|
|
t->m_direction = TransactionRow::Direction_In;
|
|
t->m_hash = QString::fromStdString(epee::string_tools::pod_to_hex(pd.m_tx_hash));
|
|
t->m_blockHeight = pd.m_block_height;
|
|
t->m_description = QString::fromStdString(m_wallet2->get_tx_note(pd.m_tx_hash));
|
|
t->m_pending = true;
|
|
t->m_subaddrIndex = { pd.m_subaddr_index.minor };
|
|
t->m_subaddrAccount = pd.m_subaddr_index.major;
|
|
t->m_label = QString::fromStdString(m_wallet2->get_subaddress_label(pd.m_subaddr_index));
|
|
t->m_timestamp = QDateTime::fromSecsSinceEpoch(pd.m_timestamp);
|
|
t->m_confirmations = 0;
|
|
|
|
m_rows.append(t);
|
|
|
|
LOG_PRINT_L1(__FUNCTION__ << ": Unconfirmed payment found " << pd.m_amount);
|
|
}
|
|
}
|
|
|
|
emit refreshFinished();
|
|
}
|
|
|
|
void TransactionHistory::setTxNote(const QString &txid, const QString ¬e)
|
|
{
|
|
cryptonote::blobdata txid_data;
|
|
if(!epee::string_tools::parse_hexstr_to_binbuff(txid.toStdString(), txid_data) || txid_data.size() != sizeof(crypto::hash))
|
|
return;
|
|
const crypto::hash htxid = *reinterpret_cast<const crypto::hash*>(txid_data.data());
|
|
|
|
m_wallet2->set_tx_note(htxid, note.toStdString());
|
|
refresh();
|
|
emit txNoteChanged();
|
|
}
|
|
|
|
quint64 TransactionHistory::count() const
|
|
{
|
|
QReadLocker locker(&m_lock);
|
|
|
|
return m_rows.length();
|
|
}
|
|
|
|
QDateTime TransactionHistory::firstDateTime() const
|
|
{
|
|
return m_firstDateTime;
|
|
}
|
|
|
|
QDateTime TransactionHistory::lastDateTime() const
|
|
{
|
|
return m_lastDateTime;
|
|
}
|
|
|
|
quint64 TransactionHistory::minutesToUnlock() const
|
|
{
|
|
return m_minutesToUnlock;
|
|
}
|
|
|
|
bool TransactionHistory::TransactionHistory::locked() const
|
|
{
|
|
return m_locked;
|
|
}
|
|
|
|
|
|
TransactionHistory::TransactionHistory(Wallet *wallet, tools::wallet2 *wallet2, QObject *parent)
|
|
: QObject(parent)
|
|
, m_wallet(wallet)
|
|
, m_wallet2(wallet2)
|
|
, m_minutesToUnlock(0)
|
|
, m_locked(false)
|
|
{
|
|
#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
|
|
m_firstDateTime = QDate(2014, 4, 18).startOfDay();
|
|
#else
|
|
m_firstDateTime = QDateTime(QDate(2014, 4, 18)); // the genesis block
|
|
#endif
|
|
m_lastDateTime = QDateTime::currentDateTime().addDays(1); // tomorrow (guard against jitter and timezones)
|
|
}
|
|
|
|
void TransactionHistory::clearRows() {
|
|
qDeleteAll(m_rows);
|
|
m_rows.clear();
|
|
}
|
|
|
|
bool TransactionHistory::writeCSV(const QString &path) {
|
|
QString data;
|
|
QReadLocker locker(&m_lock);
|
|
|
|
auto transactions = m_rows;
|
|
|
|
std::sort(m_rows.begin(), m_rows.end(), [](const TransactionRow *info1, const TransactionRow *info2){
|
|
return info1->blockHeight() < info2->blockHeight();
|
|
});
|
|
|
|
for (const auto &info : transactions) {
|
|
// collect column data
|
|
QDateTime timeStamp = info->timestamp();
|
|
double amount = info->amount();
|
|
|
|
// calc historical fiat price
|
|
QString fiatAmount;
|
|
QString preferredFiatSymbol = conf()->get(Config::preferredFiatCurrency).toString();
|
|
const double usd_price = appData()->txFiatHistory->get(timeStamp.toString("yyyyMMdd"));
|
|
double fiat_price = usd_price * amount;
|
|
|
|
if (preferredFiatSymbol != "USD")
|
|
fiat_price = appData()->prices.convert("USD", preferredFiatSymbol, fiat_price);
|
|
double fiat_rounded = ceil(Utils::roundSignificant(fiat_price, 3) * 100.0) / 100.0;
|
|
if (usd_price != 0)
|
|
fiatAmount = QString::number(fiat_rounded);
|
|
else
|
|
fiatAmount = "\"?\"";
|
|
|
|
QString direction = QString("");
|
|
if (info->direction() == TransactionRow::Direction_In)
|
|
direction = QString("in");
|
|
else if (info->direction() == TransactionRow::Direction_Out)
|
|
direction = QString("out");
|
|
else
|
|
continue; // skip TransactionInfo::Direction_Both
|
|
|
|
QString displayAmount = info->displayAmount();
|
|
QString paymentId = info->paymentId();
|
|
if (paymentId == "0000000000000000") {
|
|
paymentId = "";
|
|
}
|
|
|
|
QString date = QString("%1T%2Z").arg(info->date(), info->time()); // ISO 8601
|
|
|
|
QString balanceDelta = WalletManager::displayAmount(info->balanceDelta());
|
|
if (info->direction() == TransactionRow::Direction_Out) {
|
|
balanceDelta = "-" + balanceDelta;
|
|
}
|
|
|
|
// format and write
|
|
QString line = QString("\n%1,%2,\"%3\",%4,\"%5\",%6,%7,%8,\"%9\",\"%10\",\"%11\",%12,\"%13\"")
|
|
.arg(QString::number(info->blockHeight()), QString::number(timeStamp.toSecsSinceEpoch()), date,
|
|
QString::number(info->subaddrAccount()), direction, balanceDelta, info->displayAmount(),
|
|
info->fee(), info->hash(), info->description(), paymentId, fiatAmount, preferredFiatSymbol);
|
|
data += line;
|
|
}
|
|
|
|
if (data.isEmpty())
|
|
return false;
|
|
|
|
data = QString("blockHeight,timestamp,date,accountIndex,direction,balanceDelta,amount,fee,txid,description,paymentId,fiatAmount,fiatCurrency%1").arg(data);
|
|
return Utils::fileWrite(path, data);
|
|
}
|