#include "utils/Icons.h"
#include "WebsocketNotifier.h"
+#include <QClipboard>
+#include <QJsonObject>
+#include <QJsonDocument>
+#include <QJsonArray>
+
HistoryWidget::HistoryWidget(Wallet *wallet, QWidget *parent)
: QWidget(parent)
, ui(new Ui::HistoryWidget)
m_copyMenu->addAction("Date", this, [this]{copy(copyField::Date);});
m_copyMenu->addAction("Description", this, [this]{copy(copyField::Description);});
m_copyMenu->addAction("Amount", this, [this]{copy(copyField::Amount);});
+ m_copyMenu->addAction("Row as JSON", this, [this]{copy(copyField::JSON);});
ui->history->setContextMenuPolicy(Qt::CustomContextMenu);
connect(ui->history, &QTreeView::customContextMenuRequested, this, &HistoryWidget::showContextMenu);
conf()->get(Config::timeFormat).toString()));
case copyField::Amount:
return WalletManager::displayAmount(abs(tx.balanceDelta));
+ case copyField::JSON: {
+ QJsonObject obj;
+ obj.insert("txid", tx.hash);
+ obj.insert("amount", static_cast<double>(tx.amount));
+ obj.insert("fee", static_cast<double>(tx.fee));
+ obj.insert("height", static_cast<double>(tx.blockHeight));
+ obj.insert("timestamp", tx.timestamp.toSecsSinceEpoch());
+ obj.insert("direction", tx.direction == TransactionRow::Direction_In ? "in" : "out");
+ obj.insert("payment_id", tx.paymentId);
+ obj.insert("description", tx.description);
+ obj.insert("confirmations", static_cast<double>(tx.confirmations));
+ obj.insert("failed", tx.failed);
+ obj.insert("pending", tx.pending);
+ obj.insert("coinbase", tx.coinbase);
+ obj.insert("label", tx.label);
+
+ QJsonArray subaddrIndices;
+ for (const auto &idx : tx.subaddrIndex) subaddrIndices.append(static_cast<int>(idx));
+ obj.insert("subaddr_index", subaddrIndices);
+ obj.insert("subaddr_account", static_cast<int>(tx.subaddrAccount));
+
+ QJsonDocument doc(obj);
+ return QString::fromUtf8(doc.toJson(QJsonDocument::Indented));
+ }
default:
return QString("");
}
TxID = 0,
Description,
Date,
- Amount
+ Amount,
+ JSON
};
void copy(copyField field);
m_actionPauseSync->setChecked(conf()->get(Config::syncPaused).toBool());
m_statusLabelStatus->addAction(m_actionPauseSync);
+ m_actionScanMempoolWhenPaused = new QAction(tr("Scan mempool when paused"), this);
+ m_actionScanMempoolWhenPaused->setCheckable(true);
+ m_actionScanMempoolWhenPaused->setChecked(conf()->get(Config::scanMempoolWhenPaused).toBool());
+ m_statusLabelStatus->addAction(m_actionScanMempoolWhenPaused);
+
+ connect(m_actionScanMempoolWhenPaused, &QAction::toggled, this, [](bool checked) {
+ conf()->set(Config::scanMempoolWhenPaused, checked);
+ });
+
m_actionEnableWebsocket = new QAction(tr("Enable Websocket"), this);
m_actionEnableWebsocket->setCheckable(true);
m_actionEnableWebsocket->setChecked(!conf()->get(Config::disableWebsocket).toBool());
QPointer<QAction> m_updateNetworkInfoAction;
QPointer<QAction> m_actionEnableWebsocket;
QPointer<QAction> m_actionPauseSync;
+ QPointer<QAction> m_actionScanMempoolWhenPaused;
QDateTime m_lastSyncStatusUpdate;
QDateTime m_lastNetInfoUpdate;
#include <chrono>
#include <thread>
+#include <tuple>
#include "AddressBook.h"
#include "Coins.h"
// Don't call refresh function if we don't have the daemon and target height
// We do this to prevent to UI from getting confused about the amount of blocks that are still remaining
if (haveHeights) {
- // Prevent background network usage when sync is paused
- if (m_syncPaused)
- continue;
QMutexLocker locker(&m_asyncMutex);
quint64 walletHeight = m_walletImpl->blockChainHeight();
m_walletImpl->refresh();
}
+
+ // Scan mempool if paused
+ // Low-bandwidth query if user has enabled it
+ if (m_syncPaused) {
+ if (m_refreshNow || conf()->get(Config::scanMempoolWhenPaused).toBool()) {
+ scanMempool();
+ m_refreshNow = false;
+ } else {
+ std::this_thread::sleep_for(std::chrono::milliseconds(250));
+ }
+ last = std::chrono::steady_clock::now();
+ continue;
+ }
}
}
});
}
+void Wallet::scanMempool() {
+ QMutexLocker locker(&m_asyncMutex);
+ try {
+ std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> process_txs;
+ m_wallet2->update_pool_state(process_txs, false, false);
+ if (!process_txs.empty()) {
+ m_wallet2->process_pool_state(process_txs);
+ }
+ } catch (const std::exception &e) {
+ qWarning() << "Failed to scan mempool:" << e.what();
+ }
+}
+
Wallet::~Wallet()
{
qDebug() << "~Wallet: Closing wallet" << QThread::currentThreadId();
void onTransactionCreated(Monero::PendingTransaction *mtx, const QVector<QString> &address);
private:
+ void scanMempool();
friend class WalletManager;
friend class WalletListenerImpl;
{Config::torManagedPort, {QS("torManagedPort"), "19450"}},
{Config::useLocalTor, {QS("useLocalTor"), false}},
{Config::initSyncThreshold, {QS("initSyncThreshold"), 360}},
+ {Config::scanMempoolWhenPaused, {QS("scanMempoolWhenPaused"), false}},
{Config::enabledPlugins, {QS("enabledPlugins"), QStringList{"tickers", "crowdfunding", "revuo", "calc"}}},
{Config::restartRequired, {QS("restartRequired"), false}},
lastNetInfoUpdate,
lastSyncTimestamp,
lastPriceUpdateTimestamp,
+ scanMempoolWhenPaused,
};
enum PrivacyLevel {