mirror of
https://github.com/feather-wallet/feather.git
synced 2024-11-16 09:17:36 +00:00
manual input selection, subtract fee from amount
This commit is contained in:
parent
0da98d2003
commit
deb9d7ff63
31 changed files with 904 additions and 42 deletions
|
@ -350,6 +350,7 @@ EOF
|
|||
--container \
|
||||
--pure \
|
||||
--no-cwd \
|
||||
--cores="$JOBS" \
|
||||
${SUBSTITUTE_URLS:+--substitute-urls="$SUBSTITUTE_URLS"} \
|
||||
-- echo "$HOST"
|
||||
|
||||
|
|
|
@ -222,6 +222,8 @@ mkdir -p "$OUTDIR"
|
|||
# Log the depends build ids
|
||||
make -C contrib/depends --no-print-directory HOST="$HOST" print-final_build_id_long | tr ':' '\n' > ${LOGDIR}/depends-hashes.txt
|
||||
|
||||
export CMAKE_BUILD_PARALLEL_LEVEL=$JOBS
|
||||
|
||||
# Build the depends tree, overriding variables that assume multilib gcc
|
||||
make -C contrib/depends --jobs="$JOBS" HOST="$HOST" \
|
||||
${V:+V=1} \
|
||||
|
|
2
monero
2
monero
|
@ -1 +1 @@
|
|||
Subproject commit 85ea9458c8a27814729b24c3b932f60ff331903e
|
||||
Subproject commit 376fb747ea262cf6cd773cc169bbd3e84670d733
|
|
@ -245,7 +245,16 @@ void CoinsWidget::onSweepOutputs() {
|
|||
#endif
|
||||
}
|
||||
|
||||
m_wallet->sweepOutputs(keyImages, dialog.address(), dialog.churn(), dialog.outputs());
|
||||
QString address = dialog.address();
|
||||
bool churn = dialog.churn();
|
||||
int outputs = dialog.outputs();
|
||||
|
||||
QtFuture::connect(m_wallet, &Wallet::preTransactionChecksComplete)
|
||||
.then([this, keyImages, address, churn, outputs](int feeLevel){
|
||||
m_wallet->sweepOutputs(keyImages, address, churn, outputs, feeLevel);
|
||||
});
|
||||
|
||||
m_wallet->preTransactionChecks(dialog.feeLevel());
|
||||
}
|
||||
|
||||
void CoinsWidget::copy(copyField field) {
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "dialog/TxConfDialog.h"
|
||||
#include "dialog/TxImportDialog.h"
|
||||
#include "dialog/TxInfoDialog.h"
|
||||
#include "dialog/TxPoolViewerDialog.h"
|
||||
#include "dialog/ViewOnlyDialog.h"
|
||||
#include "dialog/WalletInfoDialog.h"
|
||||
#include "dialog/WalletCacheDebugDialog.h"
|
||||
|
@ -88,9 +89,13 @@ MainWindow::MainWindow(WindowManager *windowManager, Wallet *wallet, QWidget *pa
|
|||
connect(m_windowManager, &WindowManager::websocketStatusChanged, this, &MainWindow::onWebsocketStatusChanged);
|
||||
this->onWebsocketStatusChanged(!conf()->get(Config::disableWebsocket).toBool());
|
||||
|
||||
connect(m_windowManager, &WindowManager::proxySettingsChanged, this, &MainWindow::onProxySettingsChanged);
|
||||
connect(m_windowManager, &WindowManager::proxySettingsChanged, [this]{
|
||||
this->onProxySettingsChanged();
|
||||
});
|
||||
connect(m_windowManager, &WindowManager::updateBalance, m_wallet, &Wallet::updateBalance);
|
||||
connect(m_windowManager, &WindowManager::offlineMode, this, &MainWindow::onOfflineMode);
|
||||
connect(m_windowManager, &WindowManager::manualFeeSelectionEnabled, this, &MainWindow::onManualFeeSelectionEnabled);
|
||||
connect(m_windowManager, &WindowManager::subtractFeeFromAmountEnabled, this, &MainWindow::onSubtractFeeFromAmountEnabled);
|
||||
|
||||
connect(torManager(), &TorManager::connectionStateChanged, this, &MainWindow::onTorConnectionStateChanged);
|
||||
this->onTorConnectionStateChanged(torManager()->torConnected);
|
||||
|
@ -178,7 +183,7 @@ void MainWindow::initStatusBar() {
|
|||
m_statusBtnProxySettings = new StatusBarButton(icons()->icon("tor_logo_disabled.png"), "Proxy settings", this);
|
||||
connect(m_statusBtnProxySettings, &StatusBarButton::clicked, this, &MainWindow::menuProxySettingsClicked);
|
||||
this->statusBar()->addPermanentWidget(m_statusBtnProxySettings);
|
||||
this->onProxySettingsChanged();
|
||||
this->onProxySettingsChanged(false);
|
||||
|
||||
m_statusBtnHwDevice = new StatusBarButton(this->hardwareDevicePairedIcon(), this->getHardwareDevice(), this);
|
||||
connect(m_statusBtnHwDevice, &StatusBarButton::clicked, this, &MainWindow::menuHwDeviceClicked);
|
||||
|
@ -302,6 +307,7 @@ void MainWindow::initMenu() {
|
|||
connect(ui->actionRefresh_tabs, &QAction::triggered, [this]{m_wallet->refreshModels();});
|
||||
connect(ui->actionRescan_spent, &QAction::triggered, this, &MainWindow::rescanSpent);
|
||||
connect(ui->actionWallet_cache_debug, &QAction::triggered, this, &MainWindow::showWalletCacheDebugDialog);
|
||||
connect(ui->actionTxPoolViewer, &QAction::triggered, this, &MainWindow::showTxPoolViewerDialog);
|
||||
|
||||
// [Wallet] -> [History]
|
||||
connect(ui->actionExport_CSV, &QAction::triggered, this, &MainWindow::onExportHistoryCSV);
|
||||
|
@ -458,6 +464,7 @@ void MainWindow::initWalletContext() {
|
|||
connect(m_wallet, &Wallet::initiateTransaction, this, &MainWindow::onInitiateTransaction);
|
||||
connect(m_wallet, &Wallet::keysCorrupted, this, &MainWindow::onKeysCorrupted);
|
||||
connect(m_wallet, &Wallet::selectedInputsChanged, this, &MainWindow::onSelectedInputsChanged);
|
||||
connect(m_wallet, &Wallet::txPoolBacklog, this, &MainWindow::onTxPoolBacklog);
|
||||
|
||||
// Wallet
|
||||
connect(m_wallet, &Wallet::connectionStatusChanged, [this](int status){
|
||||
|
@ -644,8 +651,10 @@ void MainWindow::onWebsocketStatusChanged(bool enabled) {
|
|||
m_sendWidget->setWebsocketEnabled(enabled);
|
||||
}
|
||||
|
||||
void MainWindow::onProxySettingsChanged() {
|
||||
m_nodes->connectToNode();
|
||||
void MainWindow::onProxySettingsChanged(bool connect) {
|
||||
if (connect) {
|
||||
m_nodes->connectToNode();
|
||||
}
|
||||
|
||||
int proxy = conf()->get(Config::proxy).toInt();
|
||||
|
||||
|
@ -682,6 +691,14 @@ void MainWindow::onOfflineMode(bool offline) {
|
|||
m_statusBtnProxySettings->setVisible(!offline);
|
||||
}
|
||||
|
||||
void MainWindow::onManualFeeSelectionEnabled(bool enabled) {
|
||||
m_sendWidget->setManualFeeSelectionEnabled(enabled);
|
||||
}
|
||||
|
||||
void MainWindow::onSubtractFeeFromAmountEnabled(bool enabled) {
|
||||
m_sendWidget->setSubtractFeeFromAmountEnabled(enabled);
|
||||
}
|
||||
|
||||
void MainWindow::onMultiBroadcast(const QMap<QString, QString> &txHexMap) {
|
||||
QMapIterator<QString, QString> i(txHexMap);
|
||||
while (i.hasNext()) {
|
||||
|
@ -1309,6 +1326,14 @@ void MainWindow::showWalletCacheDebugDialog() {
|
|||
dialog.exec();
|
||||
}
|
||||
|
||||
void MainWindow::showTxPoolViewerDialog() {
|
||||
if (!m_txPoolViewerDialog) {
|
||||
m_txPoolViewerDialog = new TxPoolViewerDialog{this, m_wallet};
|
||||
}
|
||||
|
||||
m_txPoolViewerDialog->show();
|
||||
}
|
||||
|
||||
void MainWindow::showAccountSwitcherDialog() {
|
||||
m_accountSwitcherDialog->show();
|
||||
m_accountSwitcherDialog->update();
|
||||
|
@ -1624,6 +1649,36 @@ void MainWindow::onSelectedInputsChanged(const QStringList &selectedInputs) {
|
|||
}
|
||||
}
|
||||
|
||||
void MainWindow::onTxPoolBacklog(const QVector<quint64> &backlog, quint64 originalFeeLevel, quint64 automaticFeeLevel) {
|
||||
bool automatic = (originalFeeLevel == 0);
|
||||
|
||||
if (automaticFeeLevel == 0) {
|
||||
qWarning() << "Automatic fee level wasn't adjusted";
|
||||
automaticFeeLevel = 2;
|
||||
}
|
||||
|
||||
quint64 feeLevel = automatic ? automaticFeeLevel : originalFeeLevel;
|
||||
|
||||
for (int i = 0; i < backlog.size(); i++) {
|
||||
qDebug() << QString("Fee level: %1, backlog: %2").arg(QString::number(i), QString::number(backlog[i]));
|
||||
}
|
||||
|
||||
if (automatic) {
|
||||
if (backlog.size() >= 1 && backlog[1] >= 2) {
|
||||
auto button = QMessageBox::question(this, "Transaction Pool Backlog",
|
||||
QString("There is a backlog of %1 blocks (≈ %2 minutes) in the transaction pool "
|
||||
"at the maximum automatic fee level.\n\n"
|
||||
"Do you want to increase the fee for this transaction?")
|
||||
.arg(QString::number(backlog[1]), QString::number(backlog[1] * 2)));
|
||||
if (button == QMessageBox::Yes) {
|
||||
feeLevel = 3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_wallet->confirmPreTransactionChecks(feeLevel);
|
||||
}
|
||||
|
||||
void MainWindow::onExportHistoryCSV() {
|
||||
QString fn = QFileDialog::getSaveFileName(this, "Save CSV file", QDir::homePath(), "CSV (*.csv)");
|
||||
if (fn.isEmpty())
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "dialog/KeysDialog.h"
|
||||
#include "dialog/AboutDialog.h"
|
||||
#include "dialog/SplashDialog.h"
|
||||
#include "dialog/TxPoolViewerDialog.h"
|
||||
#include "libwalletqt/Wallet.h"
|
||||
#include "model/SubaddressModel.h"
|
||||
#include "model/SubaddressProxyModel.h"
|
||||
|
@ -124,6 +125,7 @@ private slots:
|
|||
void onInitiateTransaction();
|
||||
void onKeysCorrupted();
|
||||
void onSelectedInputsChanged(const QStringList &selectedInputs);
|
||||
void onTxPoolBacklog(const QVector<quint64> &backlog, quint64 originalFeeLevel, quint64 automaticFeeLevel);
|
||||
|
||||
// libwalletqt
|
||||
void onBalanceUpdated(quint64 balance, quint64 spendable);
|
||||
|
@ -141,6 +143,7 @@ private slots:
|
|||
void showViewOnlyDialog();
|
||||
void showKeyImageSyncWizard();
|
||||
void showWalletCacheDebugDialog();
|
||||
void showTxPoolViewerDialog();
|
||||
void showAccountSwitcherDialog();
|
||||
void showAddressChecker();
|
||||
void showURDialog();
|
||||
|
@ -162,8 +165,10 @@ private slots:
|
|||
void tryStoreWallet();
|
||||
void onWebsocketStatusChanged(bool enabled);
|
||||
void showUpdateNotification();
|
||||
void onProxySettingsChanged();
|
||||
void onProxySettingsChanged(bool connect = true);
|
||||
void onOfflineMode(bool offline);
|
||||
void onManualFeeSelectionEnabled(bool enabled);
|
||||
void onSubtractFeeFromAmountEnabled(bool enabled);
|
||||
void onMultiBroadcast(const QMap<QString, QString> &txHexMap);
|
||||
|
||||
private:
|
||||
|
@ -213,6 +218,7 @@ private:
|
|||
|
||||
SplashDialog *m_splashDialog = nullptr;
|
||||
AccountSwitcherDialog *m_accountSwitcherDialog = nullptr;
|
||||
TxPoolViewerDialog *m_txPoolViewerDialog = nullptr;
|
||||
|
||||
WalletUnlockWidget *m_walletUnlockWidget = nullptr;
|
||||
ContactsWidget *m_contactsWidget = nullptr;
|
||||
|
|
|
@ -469,7 +469,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>977</width>
|
||||
<height>24</height>
|
||||
<height>27</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuFile">
|
||||
|
@ -552,6 +552,7 @@
|
|||
<addaction name="actionPay_to_many"/>
|
||||
<addaction name="actionAddress_checker"/>
|
||||
<addaction name="actionCreateDesktopEntry"/>
|
||||
<addaction name="actionTxPoolViewer"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuHelp">
|
||||
<property name="title">
|
||||
|
@ -933,6 +934,11 @@
|
|||
<string>PlaceholderBegin</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionTxPoolViewer">
|
||||
<property name="text">
|
||||
<string>Tx pool viewer</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<layoutdefault spacing="6" margin="11"/>
|
||||
<customwidgets>
|
||||
|
|
|
@ -66,6 +66,9 @@ SendWidget::SendWidget(Wallet *wallet, QWidget *parent)
|
|||
|
||||
ui->lineAddress->setNetType(constants::networkType);
|
||||
this->setupComboBox();
|
||||
|
||||
this->setManualFeeSelectionEnabled(conf()->get(Config::manualFeeTierSelection).toBool());
|
||||
this->setSubtractFeeFromAmountEnabled(conf()->get(Config::subtractFeeFromAmount).toBool());
|
||||
}
|
||||
|
||||
void SendWidget::currencyComboChanged(int index) {
|
||||
|
@ -175,6 +178,8 @@ void SendWidget::sendClicked() {
|
|||
return;
|
||||
}
|
||||
|
||||
bool subtractFeeFromAmount = conf()->get(Config::subtractFeeFromAmount).toBool() && ui->check_subtractFeeFromAmount->isChecked();
|
||||
|
||||
QString description = ui->lineDescription->text();
|
||||
|
||||
if (!outputs.empty()) { // multi destination transaction
|
||||
|
@ -190,7 +195,13 @@ void SendWidget::sendClicked() {
|
|||
amounts.push_back(output.amount);
|
||||
}
|
||||
|
||||
m_wallet->createTransactionMultiDest(addresses, amounts, description);
|
||||
QtFuture::connect(m_wallet, &Wallet::preTransactionChecksComplete)
|
||||
.then([this, addresses, amounts, description, subtractFeeFromAmount](int feeLevel){
|
||||
m_wallet->createTransactionMultiDest(addresses, amounts, description, feeLevel, subtractFeeFromAmount);
|
||||
});
|
||||
|
||||
m_wallet->preTransactionChecks(ui->combo_feePriority->currentIndex());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -243,7 +254,12 @@ void SendWidget::sendClicked() {
|
|||
#endif
|
||||
}
|
||||
|
||||
m_wallet->createTransaction(recipient, amount, description, sendAll);
|
||||
QtFuture::connect(m_wallet, &Wallet::preTransactionChecksComplete)
|
||||
.then([this, recipient, amount, description, sendAll, subtractFeeFromAmount](int feeLevel){
|
||||
m_wallet->createTransaction(recipient, amount, description, sendAll, feeLevel, subtractFeeFromAmount);
|
||||
});
|
||||
|
||||
m_wallet->preTransactionChecks(ui->combo_feePriority->currentIndex());
|
||||
}
|
||||
|
||||
void SendWidget::aliasClicked() {
|
||||
|
@ -377,6 +393,15 @@ void SendWidget::setWebsocketEnabled(bool enabled) {
|
|||
}
|
||||
}
|
||||
|
||||
void SendWidget::setManualFeeSelectionEnabled(bool enabled) {
|
||||
ui->label_feeTarget->setVisible(enabled);
|
||||
ui->combo_feePriority->setVisible(enabled);
|
||||
}
|
||||
|
||||
void SendWidget::setSubtractFeeFromAmountEnabled(bool enabled) {
|
||||
ui->check_subtractFeeFromAmount->setVisible(enabled);
|
||||
}
|
||||
|
||||
void SendWidget::onDataPasted(const QString &data) {
|
||||
if (!data.isEmpty()) {
|
||||
QVariantMap uriData = m_wallet->parse_uri_to_object(data);
|
||||
|
|
|
@ -40,6 +40,9 @@ public slots:
|
|||
void onPreferredFiatCurrencyChanged();
|
||||
void setWebsocketEnabled(bool enabled);
|
||||
|
||||
void setManualFeeSelectionEnabled(bool enabled);
|
||||
void setSubtractFeeFromAmountEnabled(bool enabled);
|
||||
|
||||
void disableSendButton();
|
||||
void enableSendButton();
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>647</width>
|
||||
<height>231</height>
|
||||
<height>254</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
|
@ -172,6 +172,13 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="check_subtractFeeFromAmount">
|
||||
<property name="text">
|
||||
<string>Subtract fee from amount</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
|
@ -187,7 +194,14 @@
|
|||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_feeTarget">
|
||||
<property name="text">
|
||||
<string>Fee</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<property name="spacing">
|
||||
<number>6</number>
|
||||
|
@ -228,6 +242,35 @@
|
|||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QComboBox" name="combo_feePriority">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Automatic</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Low</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Normal</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>High</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Highest</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
|
|
|
@ -319,6 +319,28 @@ void Settings::setupTransactionsTab() {
|
|||
// Hide unimplemented settings
|
||||
ui->checkBox_alwaysOpenAdvancedTxDialog->hide();
|
||||
ui->checkBox_requirePasswordToSpend->hide();
|
||||
|
||||
// [Manual fee-tier selection]
|
||||
ui->checkBox_manualFeeTierSelection->setChecked(conf()->get(Config::manualFeeTierSelection).toBool());
|
||||
connect(ui->checkBox_manualFeeTierSelection, &QCheckBox::toggled, [this](bool toggled){
|
||||
if (toggled) {
|
||||
auto result = QMessageBox::question(this, "Privacy warning", "Using a non-automatic fee makes your transactions stick out and harms your privacy.\n\nAre you sure you want to enable manual fee-tier selection?");
|
||||
if (result == QMessageBox::No) {
|
||||
ui->checkBox_manualFeeTierSelection->setChecked(false);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
conf()->set(Config::manualFeeTierSelection, toggled);
|
||||
emit manualFeeSelectionEnabled(toggled);
|
||||
});
|
||||
|
||||
ui->checkBox_subtractFeeFromAmount->setChecked(conf()->get(Config::subtractFeeFromAmount).toBool());
|
||||
connect(ui->checkBox_subtractFeeFromAmount, &QCheckBox::toggled, [this](bool toggled){
|
||||
conf()->set(Config::subtractFeeFromAmount, toggled);
|
||||
emit subtractFeeFromAmountEnabled(toggled);
|
||||
});
|
||||
}
|
||||
|
||||
void Settings::setupPluginsTab() {
|
||||
|
|
|
@ -45,6 +45,8 @@ signals:
|
|||
void updateBalance();
|
||||
void offlineMode(bool offline);
|
||||
void pluginConfigured(const QString &id);
|
||||
void manualFeeSelectionEnabled(bool enabled);
|
||||
void subtractFeeFromAmountEnabled(bool enabled);
|
||||
|
||||
public slots:
|
||||
// void checkboxExternalLinkWarn();
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
<item>
|
||||
<widget class="QStackedWidget" name="stackedWidget">
|
||||
<property name="currentIndex">
|
||||
<number>7</number>
|
||||
<number>5</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="page_appearance">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_6">
|
||||
|
@ -955,6 +955,20 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_manualFeeTierSelection">
|
||||
<property name="text">
|
||||
<string>Manual fee-tier selection</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkBox_subtractFeeFromAmount">
|
||||
<property name="text">
|
||||
<string>Subtract fee from outputs</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
|
|
|
@ -176,6 +176,8 @@ void WindowManager::showSettings(Nodes *nodes, QWidget *parent, bool showProxyTa
|
|||
connect(&settings, &Settings::proxySettingsChanged, this, &WindowManager::onProxySettingsChanged);
|
||||
connect(&settings, &Settings::websocketStatusChanged, this, &WindowManager::onWebsocketStatusChanged);
|
||||
connect(&settings, &Settings::offlineMode, this, &WindowManager::offlineMode);
|
||||
connect(&settings, &Settings::manualFeeSelectionEnabled, this, &WindowManager::manualFeeSelectionEnabled);
|
||||
connect(&settings, &Settings::subtractFeeFromAmountEnabled, this, &WindowManager::subtractFeeFromAmountEnabled);
|
||||
connect(&settings, &Settings::hideUpdateNotifications, [this](bool hidden){
|
||||
for (const auto &window : m_windows) {
|
||||
window->onHideUpdateNotifications(hidden);
|
||||
|
|
|
@ -50,6 +50,8 @@ signals:
|
|||
void preferredFiatCurrencyChanged();
|
||||
void offlineMode(bool offline);
|
||||
void pluginConfigured(const QString &id);
|
||||
void manualFeeSelectionEnabled(bool enabled);
|
||||
void subtractFeeFromAmountEnabled(bool enabled);
|
||||
|
||||
public slots:
|
||||
void onProxySettingsChanged();
|
||||
|
|
|
@ -22,6 +22,7 @@ OutputSweepDialog::OutputSweepDialog(QWidget *parent, quint64 amount)
|
|||
m_address = ui->lineEdit_address->text();
|
||||
m_churn = ui->checkBox_churn->isChecked();
|
||||
m_outputs = ui->spinBox_numOutputs->value();
|
||||
m_feeLevel = ui->combo_feePriority->currentIndex();
|
||||
});
|
||||
|
||||
connect(ui->spinBox_numOutputs, QOverload<int>::of(&QSpinBox::valueChanged), [this](int value){
|
||||
|
@ -52,4 +53,8 @@ int OutputSweepDialog::outputs() const {
|
|||
return m_outputs;
|
||||
}
|
||||
|
||||
int OutputSweepDialog::feeLevel() const {
|
||||
return m_feeLevel;
|
||||
}
|
||||
|
||||
OutputSweepDialog::~OutputSweepDialog() = default;
|
|
@ -24,6 +24,7 @@ public:
|
|||
QString address();
|
||||
bool churn() const;
|
||||
int outputs() const;
|
||||
int feeLevel() const;
|
||||
|
||||
private:
|
||||
QScopedPointer<Ui::OutputSweepDialog> ui;
|
||||
|
@ -31,8 +32,9 @@ private:
|
|||
uint64_t m_amount;
|
||||
|
||||
QString m_address;
|
||||
bool m_churn;
|
||||
int m_outputs;
|
||||
bool m_churn = false;
|
||||
int m_outputs = 1;
|
||||
int m_feeLevel = 0;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>720</width>
|
||||
<height>193</height>
|
||||
<height>225</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
|
@ -19,6 +19,9 @@
|
|||
</property>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::ExpandingFieldsGrow</enum>
|
||||
</property>
|
||||
<property name="verticalSpacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
|
@ -49,6 +52,42 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Fee:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="combo_feePriority">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Automatic</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Low</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Normal</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>High</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Highest</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
|
|
160
src/dialog/TxPoolViewerDialog.cpp
Normal file
160
src/dialog/TxPoolViewerDialog.cpp
Normal file
|
@ -0,0 +1,160 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
// SPDX-FileCopyrightText: 2020-2024 The Monero Project
|
||||
|
||||
#include "TxPoolViewerDialog.h"
|
||||
#include "ui_TxPoolViewerDialog.h"
|
||||
|
||||
#include <QTreeWidgetItem>
|
||||
|
||||
#include "utils/Utils.h"
|
||||
#include "utils/ColorScheme.h"
|
||||
#include "libwalletqt/WalletManager.h"
|
||||
|
||||
TxPoolViewerDialog::TxPoolViewerDialog(QWidget *parent, Wallet *wallet)
|
||||
: QDialog(parent)
|
||||
, ui(new Ui::TxPoolViewerDialog)
|
||||
, m_wallet(wallet)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
connect(ui->btn_refresh, &QPushButton::clicked, this, &TxPoolViewerDialog::refresh);
|
||||
connect(m_wallet, &Wallet::poolStats, this, &TxPoolViewerDialog::onTxPoolBacklog);
|
||||
|
||||
ui->tree_pool->sortByColumn(2, Qt::DescendingOrder);
|
||||
|
||||
this->refresh();
|
||||
}
|
||||
|
||||
void TxPoolViewerDialog::refresh() {
|
||||
ui->btn_refresh->setEnabled(false);
|
||||
m_wallet->getTxPoolStatsAsync();
|
||||
}
|
||||
|
||||
class TxPoolSortItem : public QTreeWidgetItem {
|
||||
public:
|
||||
using QTreeWidgetItem::QTreeWidgetItem;
|
||||
|
||||
bool operator<(const QTreeWidgetItem &other) const override {
|
||||
int column = treeWidget()->sortColumn();
|
||||
|
||||
if (column == 2) {
|
||||
return this->text(column).toInt() < other.text(column).toInt();
|
||||
}
|
||||
|
||||
return this->text(column) < other.text(column);
|
||||
}
|
||||
};
|
||||
|
||||
void TxPoolViewerDialog::onTxPoolBacklog(const QVector<TxBacklogEntry> &txPool, const QVector<quint64> &baseFees, quint64 blockWeightLimit) {
|
||||
ui->btn_refresh->setEnabled(true);
|
||||
|
||||
if (baseFees.size() != 4) {
|
||||
return;
|
||||
}
|
||||
|
||||
ui->tree_pool->clear();
|
||||
ui->tree_feeTiers->clear();
|
||||
|
||||
m_feeTierStats.clear();
|
||||
for (int i = 0; i < 4; i++) {
|
||||
m_feeTierStats.push_back(FeeTierStats{});
|
||||
}
|
||||
|
||||
ui->label_transactions->setText(QString::number(txPool.size()));
|
||||
|
||||
uint64_t totalWeight = 0;
|
||||
uint64_t totalFees = 0;
|
||||
for (const auto &entry : txPool) {
|
||||
totalWeight += entry.weight;
|
||||
totalFees += entry.fee;
|
||||
|
||||
auto* item = new TxPoolSortItem();
|
||||
item->setText(0, QString("%1 B").arg(QString::number(entry.weight)));
|
||||
item->setTextAlignment(0, Qt::AlignRight);
|
||||
|
||||
item->setText(1, QString("%1 XMR").arg(WalletManager::displayAmount(entry.fee)));
|
||||
item->setTextAlignment(1, Qt::AlignRight);
|
||||
|
||||
quint64 fee_per_byte = entry.fee / entry.weight;
|
||||
item->setText(2, QString::number(entry.fee / entry.weight));
|
||||
item->setTextAlignment(2, Qt::AlignRight);
|
||||
|
||||
if (fee_per_byte == baseFees[0]) {
|
||||
item->setBackground(2, QBrush(ColorScheme::BLUE.asColor(true)));
|
||||
}
|
||||
if (fee_per_byte == baseFees[1]) {
|
||||
item->setBackground(2, QBrush(ColorScheme::GREEN.asColor(true)));
|
||||
}
|
||||
if (fee_per_byte == baseFees[2]) {
|
||||
item->setBackground(2, QBrush(ColorScheme::YELLOW.asColor(true)));
|
||||
}
|
||||
if (fee_per_byte == baseFees[3]) {
|
||||
item->setBackground(2, QBrush(ColorScheme::RED.asColor(true)));
|
||||
}
|
||||
|
||||
if (fee_per_byte >= baseFees[3]) {
|
||||
m_feeTierStats[3].weightFromTip += entry.weight;
|
||||
}
|
||||
if (fee_per_byte >= baseFees[2]) {
|
||||
m_feeTierStats[2].weightFromTip += entry.weight;
|
||||
}
|
||||
if (fee_per_byte >= baseFees[1]) {
|
||||
m_feeTierStats[1].weightFromTip += entry.weight;
|
||||
}
|
||||
if (fee_per_byte >= baseFees[0]) {
|
||||
m_feeTierStats[0].weightFromTip += entry.weight;
|
||||
}
|
||||
|
||||
ui->tree_pool->addTopLevelItem(item);
|
||||
}
|
||||
|
||||
ui->tree_pool->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
|
||||
ui->tree_pool->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
|
||||
|
||||
ui->label_totalWeight->setText(Utils::formatBytes(totalWeight));
|
||||
ui->label_totalFees->setText(QString("%1 XMR").arg(WalletManager::displayAmount(totalFees)));
|
||||
|
||||
quint64 fullRewardZone = blockWeightLimit >> 1;
|
||||
ui->label_blockWeightLimit->setText(Utils::formatBytes(fullRewardZone));
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
QString tierName;
|
||||
switch (i) {
|
||||
case 0:
|
||||
tierName = "Low";
|
||||
break;
|
||||
case 1:
|
||||
tierName = "Normal";
|
||||
break;
|
||||
case 2:
|
||||
tierName = "High";
|
||||
break;
|
||||
case 3:
|
||||
default:
|
||||
tierName = "Highest ";
|
||||
break;
|
||||
}
|
||||
|
||||
auto* item = new QTreeWidgetItem();
|
||||
item->setText(0, tierName);
|
||||
|
||||
item->setText(1, QString::number(baseFees[i]));
|
||||
item->setTextAlignment(1, Qt::AlignRight);
|
||||
|
||||
item->setText(2, QString(" %1 blocks").arg(QString::number(m_feeTierStats[i].weightFromTip / fullRewardZone))); // approximation
|
||||
item->setTextAlignment(2, Qt::AlignRight);
|
||||
|
||||
item->setText(3, QString("%1 kB").arg(QString::number(m_feeTierStats[i].weightFromTip / 1000)));
|
||||
item->setTextAlignment(3, Qt::AlignRight);
|
||||
|
||||
ui->tree_feeTiers->addTopLevelItem(item);
|
||||
}
|
||||
|
||||
ui->tree_feeTiers->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
|
||||
ui->tree_feeTiers->header()->setSectionResizeMode(1, QHeaderView::ResizeToContents);
|
||||
ui->tree_feeTiers->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
|
||||
ui->tree_feeTiers->headerItem()->setTextAlignment(2, Qt::AlignRight);
|
||||
ui->tree_feeTiers->headerItem()->setTextAlignment(3, Qt::AlignRight);
|
||||
}
|
||||
|
||||
TxPoolViewerDialog::~TxPoolViewerDialog() = default;
|
40
src/dialog/TxPoolViewerDialog.h
Normal file
40
src/dialog/TxPoolViewerDialog.h
Normal file
|
@ -0,0 +1,40 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
// SPDX-FileCopyrightText: 2020-2024 The Monero Project
|
||||
|
||||
#ifndef FEATHER_TXPOOLVIEWERDIALOG_H
|
||||
#define FEATHER_TXPOOLVIEWERDIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
#include "components.h"
|
||||
#include "libwalletqt/Wallet.h"
|
||||
|
||||
namespace Ui {
|
||||
class TxPoolViewerDialog;
|
||||
}
|
||||
|
||||
struct FeeTierStats {
|
||||
quint64 transactions = 0;
|
||||
quint64 totalWeight = 0;
|
||||
quint64 weightFromTip = 0;
|
||||
};
|
||||
|
||||
class TxPoolViewerDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TxPoolViewerDialog(QWidget *parent, Wallet *wallet);
|
||||
~TxPoolViewerDialog() override;
|
||||
|
||||
private:
|
||||
void refresh();
|
||||
void onTxPoolBacklog(const QVector<TxBacklogEntry> &txPool, const QVector<quint64> &baseFees, quint64 blockWeightLimit);
|
||||
|
||||
QVector<FeeTierStats> m_feeTierStats;
|
||||
QScopedPointer<Ui::TxPoolViewerDialog> ui;
|
||||
Wallet *m_wallet;
|
||||
};
|
||||
|
||||
|
||||
#endif //FEATHER_TXPOOLVIEWERDIALOG_H
|
280
src/dialog/TxPoolViewerDialog.ui
Normal file
280
src/dialog/TxPoolViewerDialog.ui
Normal file
|
@ -0,0 +1,280 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>TxPoolViewerDialog</class>
|
||||
<widget class="QDialog" name="TxPoolViewerDialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>564</width>
|
||||
<height>779</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Tx Pool Viewer</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_2">
|
||||
<property name="title">
|
||||
<string>Stats</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<property name="labelAlignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Transactions:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="label_transactions">
|
||||
<property name="text">
|
||||
<string>Loading..</string>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Total weight:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="label_totalWeight">
|
||||
<property name="text">
|
||||
<string>Loading..</string>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Total fees:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLabel" name="label_totalFees">
|
||||
<property name="text">
|
||||
<string>Loading..</string>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Full reward zone:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLabel" name="label_blockWeightLimit">
|
||||
<property name="text">
|
||||
<string>Loading..</string>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>10</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_3">
|
||||
<property name="title">
|
||||
<string>Transactions</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||
<item>
|
||||
<widget class="QTreeWidget" name="tree_pool">
|
||||
<property name="rootIsDecorated">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="sortingEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Weight</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Fee</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Fee / B</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>10</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Fee tiers</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QTreeWidget" name="tree_feeTiers">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="rootIsDecorated">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Tier</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Fee / B</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Backlog</string>
|
||||
</property>
|
||||
</column>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string>Weight from tip</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="btn_refresh">
|
||||
<property name="text">
|
||||
<string>Refresh</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<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="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Close</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>TxPoolViewerDialog</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>TxPoolViewerDialog</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>
|
|
@ -83,6 +83,11 @@ QString PendingTransaction::signedTxToHex(int index) const
|
|||
return QString::fromStdString(m_pimpl->signedTxToHex(index));
|
||||
}
|
||||
|
||||
quint64 PendingTransaction::weight(int index) const
|
||||
{
|
||||
return m_pimpl->weight(index);
|
||||
}
|
||||
|
||||
PendingTransactionInfo * PendingTransaction::transaction(int index) const {
|
||||
return m_pending_tx_info[index];
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ public:
|
|||
std::string unsignedTxToBin() const;
|
||||
QString unsignedTxToBase64() const;
|
||||
QString signedTxToHex(int index) const;
|
||||
quint64 weight(int index) const;
|
||||
void refresh();
|
||||
|
||||
PendingTransactionInfo * transaction(int index) const;
|
||||
|
|
|
@ -4,9 +4,7 @@
|
|||
#ifndef TRANSFER_H
|
||||
#define TRANSFER_H
|
||||
|
||||
#include <wallet/api/wallet2_api.h>
|
||||
#include <QObject>
|
||||
#include <utility>
|
||||
|
||||
class Transfer : public QObject
|
||||
{
|
||||
|
|
|
@ -824,37 +824,62 @@ void Wallet::setSelectedInputs(const QStringList &selectedInputs) {
|
|||
emit selectedInputsChanged(selectedInputs);
|
||||
}
|
||||
|
||||
void Wallet::preTransactionChecks(int feeLevel) {
|
||||
pauseRefresh();
|
||||
emit initiateTransaction();
|
||||
this->automaticFeeAdjustment(feeLevel);
|
||||
}
|
||||
|
||||
void Wallet::automaticFeeAdjustment(int feeLevel) {
|
||||
m_scheduler.run([this, feeLevel]{
|
||||
QVector<quint64> results;
|
||||
|
||||
std::vector<std::pair<uint64_t, uint64_t>> blocks;
|
||||
uint64_t priority = 0;
|
||||
try {
|
||||
priority = m_wallet2->adjust_priority(0, blocks);
|
||||
}
|
||||
catch (const std::exception &e) { }
|
||||
|
||||
for (const auto &block : blocks) {
|
||||
results.append(block.first);
|
||||
}
|
||||
|
||||
emit txPoolBacklog(results, feeLevel, priority);
|
||||
});
|
||||
}
|
||||
|
||||
void Wallet::confirmPreTransactionChecks(int feeLevel) {
|
||||
emit preTransactionChecksComplete(feeLevel);
|
||||
}
|
||||
|
||||
// Phase 1: Transaction creation
|
||||
// Pick one:
|
||||
// - createTransaction
|
||||
// - createTransactionMultiDest
|
||||
// - sweepOutputs
|
||||
|
||||
void Wallet::createTransaction(const QString &address, quint64 amount, const QString &description, bool all) {
|
||||
void Wallet::createTransaction(const QString &address, quint64 amount, const QString &description, bool all, int feeLevel, bool subtractFeeFromAmount) {
|
||||
this->tmpTxDescription = description;
|
||||
pauseRefresh();
|
||||
|
||||
qInfo() << "Creating transaction";
|
||||
m_scheduler.run([this, all, address, amount] {
|
||||
m_scheduler.run([this, all, address, amount, feeLevel, subtractFeeFromAmount] {
|
||||
std::set<uint32_t> subaddr_indices;
|
||||
|
||||
Monero::PendingTransaction *ptImpl = m_walletImpl->createTransaction(address.toStdString(), "", all ? Monero::optional<uint64_t>() : Monero::optional<uint64_t>(amount), constants::mixin,
|
||||
Monero::PendingTransaction::Priority_Default,
|
||||
currentSubaddressAccount(), subaddr_indices, m_selectedInputs);
|
||||
static_cast<Monero::PendingTransaction::Priority>(feeLevel),
|
||||
currentSubaddressAccount(), subaddr_indices, m_selectedInputs, subtractFeeFromAmount);
|
||||
|
||||
QVector<QString> addresses{address};
|
||||
this->onTransactionCreated(ptImpl, addresses);
|
||||
});
|
||||
|
||||
emit initiateTransaction();
|
||||
}
|
||||
|
||||
void Wallet::createTransactionMultiDest(const QVector<QString> &addresses, const QVector<quint64> &amounts, const QString &description) {
|
||||
void Wallet::createTransactionMultiDest(const QVector<QString> &addresses, const QVector<quint64> &amounts, const QString &description, int feeLevel, bool subtractFeeFromAmount) {
|
||||
this->tmpTxDescription = description;
|
||||
pauseRefresh();
|
||||
|
||||
qInfo() << "Creating transaction";
|
||||
m_scheduler.run([this, addresses, amounts] {
|
||||
m_scheduler.run([this, addresses, amounts, feeLevel, subtractFeeFromAmount] {
|
||||
std::vector<std::string> dests;
|
||||
for (auto &addr : addresses) {
|
||||
dests.push_back(addr.toStdString());
|
||||
|
@ -867,23 +892,20 @@ void Wallet::createTransactionMultiDest(const QVector<QString> &addresses, const
|
|||
|
||||
std::set<uint32_t> subaddr_indices;
|
||||
Monero::PendingTransaction *ptImpl = m_walletImpl->createTransactionMultDest(dests, "", amount, constants::mixin,
|
||||
Monero::PendingTransaction::Priority_Default,
|
||||
currentSubaddressAccount(), subaddr_indices, m_selectedInputs);
|
||||
static_cast<Monero::PendingTransaction::Priority>(feeLevel),
|
||||
currentSubaddressAccount(), subaddr_indices, m_selectedInputs, subtractFeeFromAmount);
|
||||
|
||||
this->onTransactionCreated(ptImpl, addresses);
|
||||
});
|
||||
|
||||
emit initiateTransaction();
|
||||
}
|
||||
|
||||
void Wallet::sweepOutputs(const QVector<QString> &keyImages, QString address, bool churn, int outputs) {
|
||||
pauseRefresh();
|
||||
void Wallet::sweepOutputs(const QVector<QString> &keyImages, QString address, bool churn, int outputs, int feeLevel) {
|
||||
if (churn) {
|
||||
address = this->address(0, 0);
|
||||
}
|
||||
|
||||
qInfo() << "Creating transaction";
|
||||
m_scheduler.run([this, keyImages, address, outputs] {
|
||||
m_scheduler.run([this, keyImages, address, outputs, feeLevel] {
|
||||
std::vector<std::string> kis;
|
||||
for (const auto &key_image : keyImages) {
|
||||
kis.push_back(key_image.toStdString());
|
||||
|
@ -891,13 +913,11 @@ void Wallet::sweepOutputs(const QVector<QString> &keyImages, QString address, bo
|
|||
Monero::PendingTransaction *ptImpl = m_walletImpl->createTransactionSelected(kis,
|
||||
address.toStdString(),
|
||||
outputs,
|
||||
Monero::PendingTransaction::Priority_Default);
|
||||
static_cast<Monero::PendingTransaction::Priority>(feeLevel));
|
||||
|
||||
QVector<QString> addresses {address};
|
||||
this->onTransactionCreated(ptImpl, addresses);
|
||||
});
|
||||
|
||||
emit initiateTransaction();
|
||||
}
|
||||
|
||||
// Phase 2: Transaction construction completed
|
||||
|
@ -1335,6 +1355,80 @@ void Wallet::setNewWallet() {
|
|||
m_newWallet = true;
|
||||
}
|
||||
|
||||
bool Wallet::getBaseFees(QVector<quint64> &baseFees) {
|
||||
std::vector<uint64_t> base_fees;
|
||||
|
||||
try {
|
||||
base_fees = m_wallet2->get_base_fees();
|
||||
}
|
||||
catch (const std::exception &e) {
|
||||
qWarning() << "Failed to get base fees: " << QString::fromStdString(e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto fee : base_fees) {
|
||||
baseFees.append(fee);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Wallet::estimateBacklog(const QVector<quint64> &baseFees, QVector<quint64> &backlog) {
|
||||
std::vector<std::pair<double, double>> fee_levels;
|
||||
|
||||
for (const auto fee : baseFees) {
|
||||
fee_levels.push_back(std::make_pair<double, double>(fee, fee));
|
||||
}
|
||||
|
||||
std::vector<std::pair<uint64_t, uint64_t>> backlog_;
|
||||
try {
|
||||
backlog_ = m_wallet2->estimate_backlog(fee_levels);
|
||||
}
|
||||
catch (const std::exception &e) {
|
||||
qWarning() << "Failed to estimate backlog: " << QString::fromStdString(e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const auto b : backlog_) {
|
||||
backlog.append(b.first);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Wallet::getBlockWeightLimit(quint64 &blockWeightLimit) {
|
||||
try {
|
||||
blockWeightLimit = m_wallet2->get_block_weight_limit();
|
||||
}
|
||||
catch (const std::exception &e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Wallet::getTxPoolStatsAsync() {
|
||||
m_scheduler.run([this] {
|
||||
QVector<TxBacklogEntry> txPoolBacklog;
|
||||
|
||||
quint64 blockWeightLimit = m_wallet2->get_block_weight_limit();
|
||||
std::vector<uint64_t> base_fees = m_wallet2->get_base_fees();
|
||||
|
||||
QVector<quint64> baseFees;
|
||||
for (const auto &fee : base_fees) {
|
||||
baseFees.push_back(fee);
|
||||
}
|
||||
|
||||
auto entries = m_wallet2->get_txpool_backlog();
|
||||
for (const auto &entry : entries) {
|
||||
TxBacklogEntry result{entry.weight, entry.fee, entry.time_in_pool};
|
||||
txPoolBacklog.push_back(result);
|
||||
}
|
||||
|
||||
emit poolStats(txPoolBacklog, baseFees, blockWeightLimit);
|
||||
});
|
||||
}
|
||||
|
||||
Wallet::~Wallet()
|
||||
{
|
||||
qDebug("~Wallet: Closing wallet");
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "utils/networktype.h"
|
||||
#include "PassphraseHelper.h"
|
||||
#include "WalletListenerImpl.h"
|
||||
#include "rows/TxBacklogEntry.h"
|
||||
|
||||
namespace Monero {
|
||||
struct Wallet; // forward declaration
|
||||
|
@ -313,10 +314,13 @@ public:
|
|||
|
||||
// ##### Transactions #####
|
||||
void setSelectedInputs(const QStringList &selected);
|
||||
void preTransactionChecks(int feeLevel);
|
||||
void automaticFeeAdjustment(int feeLevel);
|
||||
void confirmPreTransactionChecks(int feeLevel);
|
||||
|
||||
void createTransaction(const QString &address, quint64 amount, const QString &description, bool all);
|
||||
void createTransactionMultiDest(const QVector<QString> &addresses, const QVector<quint64> &amounts, const QString &description);
|
||||
void sweepOutputs(const QVector<QString> &keyImages, QString address, bool churn, int outputs);
|
||||
void createTransaction(const QString &address, quint64 amount, const QString &description, bool all, int feeLevel = 0, bool subtractFeeFromAmount = false);
|
||||
void createTransactionMultiDest(const QVector<QString> &addresses, const QVector<quint64> &amounts, const QString &description, int feeLevel = 0, bool subtractFeeFromAmount = false);
|
||||
void sweepOutputs(const QVector<QString> &keyImages, QString address, bool churn, int outputs, int feeLevel = 0);
|
||||
|
||||
void commitTransaction(PendingTransaction *tx, const QString &description="");
|
||||
void onTransactionCommitted(bool success, PendingTransaction *tx, const QStringList& txid, const QMap<QString, QString> &txHexMap);
|
||||
|
@ -412,6 +416,11 @@ public:
|
|||
|
||||
void onHeightsRefreshed(bool success, quint64 daemonHeight, quint64 targetHeight);
|
||||
|
||||
void getTxPoolStatsAsync();
|
||||
bool getBaseFees(QVector<quint64> &baseFees);
|
||||
bool estimateBacklog(const QVector<quint64> &baseFees, QVector<quint64> &backlog);
|
||||
bool getBlockWeightLimit(quint64 &blockWeightLimit);
|
||||
|
||||
signals:
|
||||
// emitted on every event happened with wallet
|
||||
// (money sent/received, new block)
|
||||
|
@ -435,6 +444,9 @@ signals:
|
|||
void deviceShowAddressShowed();
|
||||
void transactionProofVerified(TxProofResult result);
|
||||
void spendProofVerified(QPair<bool, bool> result);
|
||||
void poolStats(const QVector<TxBacklogEntry> &txPool, const QVector<quint64> &baseFees, quint64 blockWeightLimit);
|
||||
void txPoolBacklog(const QVector<quint64> &backlog, quint64 originalFeeLevel, quint64 adjustedFeeLevel);
|
||||
void preTransactionChecksComplete(int feeLevel);
|
||||
|
||||
void connectionStatusChanged(int status) const;
|
||||
void currentSubaddressAccountChanged() const;
|
||||
|
|
13
src/libwalletqt/rows/TxBacklogEntry.h
Normal file
13
src/libwalletqt/rows/TxBacklogEntry.h
Normal file
|
@ -0,0 +1,13 @@
|
|||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
// SPDX-FileCopyrightText: 2020-2024 The Monero Project
|
||||
|
||||
#ifndef FEATHER_TXBACKLOGENTRY_H
|
||||
#define FEATHER_TXBACKLOGENTRY_H
|
||||
|
||||
struct TxBacklogEntry {
|
||||
quint64 weight;
|
||||
quint64 fee;
|
||||
quint64 timeInPool;
|
||||
};
|
||||
|
||||
#endif //FEATHER_TXBACKLOGENTRY_H
|
|
@ -407,7 +407,7 @@ QString formatBytes(quint64 bytes)
|
|||
QVector<QString> sizes = { "B", "KB", "MB", "GB", "TB" };
|
||||
|
||||
int i;
|
||||
double _data;
|
||||
double _data = bytes;
|
||||
for (i = 0; i < sizes.count() && bytes >= 10000; i++, bytes /= 1000)
|
||||
_data = bytes / 1000.0;
|
||||
|
||||
|
|
|
@ -82,9 +82,13 @@ static const QHash<Config::ConfigKey, ConfigDirective> configStrings = {
|
|||
{Config::disableWebsocket, {QS("disableWebsocket"), false}},
|
||||
{Config::offlineMode, {QS("offlineMode"), false}},
|
||||
|
||||
// Transactions
|
||||
{Config::multiBroadcast, {QS("multiBroadcast"), true}},
|
||||
{Config::offlineTxSigningMethod, {QS("offlineTxSigningMethod"), Config::OTSMethod::UnifiedResources}},
|
||||
{Config::offlineTxSigningForceKISync, {QS("offlineTxSigningForceKISync"), false}},
|
||||
{Config::manualFeeTierSelection, {QS("manualFeeTierSelection"), false}},
|
||||
{Config::subtractFeeFromAmount, {QS("subtractFeeFromAmount"), false}},
|
||||
|
||||
{Config::warnOnExternalLink,{QS("warnOnExternalLink"), true}},
|
||||
{Config::hideBalance, {QS("hideBalance"), false}},
|
||||
{Config::hideNotifications, {QS("hideNotifications"), false}},
|
||||
|
|
|
@ -121,6 +121,8 @@ public:
|
|||
multiBroadcast,
|
||||
offlineTxSigningMethod,
|
||||
offlineTxSigningForceKISync,
|
||||
manualFeeTierSelection,
|
||||
subtractFeeFromAmount,
|
||||
|
||||
// Misc
|
||||
blockExplorers,
|
||||
|
|
|
@ -253,6 +253,14 @@ void Nodes::autoConnect(bool forceReconnect) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!m_allowConnection) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (conf()->get(Config::offlineMode).toBool()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// this function is responsible for automatically connecting to a daemon.
|
||||
if (m_wallet == nullptr || !m_enableAutoconnect) {
|
||||
return;
|
||||
|
@ -334,6 +342,13 @@ FeatherNode Nodes::pickEligibleNode() {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (conf()->get(Config::proxy).toInt() == Config::Proxy::Tor && conf()->get(Config::torOnlyAllowOnion).toBool()) {
|
||||
if (!node.isOnion() && !node.isLocal()) {
|
||||
// We only want to connect to .onion nodes, but local nodes get an exception.
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't connect to nodes that failed to connect recently
|
||||
if (m_recentFailures.contains(node.toAddress())) {
|
||||
continue;
|
||||
|
|
Loading…
Reference in a new issue