]> Nutra Git (v1) - gamesguru/feather.git/commitdiff
manual input selection, subtract fee from amount
authortobtoht <tob@featherwallet.org>
Sun, 28 Apr 2024 19:26:54 +0000 (21:26 +0200)
committertobtoht <tob@featherwallet.org>
Sun, 28 Apr 2024 19:28:09 +0000 (21:28 +0200)
31 files changed:
contrib/guix/guix-build
contrib/guix/libexec/build.sh
monero
src/CoinsWidget.cpp
src/MainWindow.cpp
src/MainWindow.h
src/MainWindow.ui
src/SendWidget.cpp
src/SendWidget.h
src/SendWidget.ui
src/SettingsDialog.cpp
src/SettingsDialog.h
src/SettingsDialog.ui
src/WindowManager.cpp
src/WindowManager.h
src/dialog/OutputSweepDialog.cpp
src/dialog/OutputSweepDialog.h
src/dialog/OutputSweepDialog.ui
src/dialog/TxPoolViewerDialog.cpp [new file with mode: 0644]
src/dialog/TxPoolViewerDialog.h [new file with mode: 0644]
src/dialog/TxPoolViewerDialog.ui [new file with mode: 0644]
src/libwalletqt/PendingTransaction.cpp
src/libwalletqt/PendingTransaction.h
src/libwalletqt/Transfer.h
src/libwalletqt/Wallet.cpp
src/libwalletqt/Wallet.h
src/libwalletqt/rows/TxBacklogEntry.h [new file with mode: 0644]
src/utils/Utils.cpp
src/utils/config.cpp
src/utils/config.h
src/utils/nodes.cpp

index 26926974d2b29fcd9e451673a0b7a7152759247b..d6ec15350c21df75676b78d32b3f4790a95b2aa0 100755 (executable)
@@ -350,6 +350,7 @@ EOF
                                  --container \
                                  --pure \
                                  --no-cwd \
+                                 --cores="$JOBS" \
                                  ${SUBSTITUTE_URLS:+--substitute-urls="$SUBSTITUTE_URLS"} \
                                  -- echo "$HOST"
 
index b3a474740e565d7f888c69d6f06a981a7013b3a4..b0473ec74be41abffe549a071b98222c21502815 100755 (executable)
@@ -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} \
diff --git a/monero b/monero
index 85ea9458c8a27814729b24c3b932f60ff331903e..376fb747ea262cf6cd773cc169bbd3e84670d733 160000 (submodule)
--- a/monero
+++ b/monero
@@ -1 +1 @@
-Subproject commit 85ea9458c8a27814729b24c3b932f60ff331903e
+Subproject commit 376fb747ea262cf6cd773cc169bbd3e84670d733
index 84715d27d814f651ae690d6e3442bee515e25ba1..f592ef1281612b267503b1251d7d1ea21f7046ac 100644 (file)
@@ -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) {
index 14b7db8a39aa620ff6115c48c4c75427e03e2206..6c1877c446f1d78461ec33c27ea1c924b8ac6954 100644 (file)
@@ -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())
index 4b2ceb170257ea4b8effd6bf834f137566c950d1..f5786b0b8e303a26c8b67f14957b7f73fec7e89e 100644 (file)
@@ -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;
index e9b390e5e2533fcc2f9c3d184a3158df543100ce..7cde5195a88c8c6e18130df2e87027f86a06f68e 100644 (file)
      <x>0</x>
      <y>0</y>
      <width>977</width>
-     <height>24</height>
+     <height>27</height>
     </rect>
    </property>
    <widget class="QMenu" name="menuFile">
     <addaction name="actionPay_to_many"/>
     <addaction name="actionAddress_checker"/>
     <addaction name="actionCreateDesktopEntry"/>
+    <addaction name="actionTxPoolViewer"/>
    </widget>
    <widget class="QMenu" name="menuHelp">
     <property name="title">
     <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>
index 89866007cecafa8a282804f500b97f05a075fa49..6e4c687519c8646ca4e099c3f353c5a5d10362bb 100644 (file)
@@ -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);
index d4aace7bb9d13d46cc65ec404ff3442051685096..fa7c78d191dbaeb56e893765f968365b0122d5a7 100644 (file)
@@ -40,6 +40,9 @@ public slots:
     void onPreferredFiatCurrencyChanged();
     void setWebsocketEnabled(bool enabled);
 
+    void setManualFeeSelectionEnabled(bool enabled);
+    void setSubtractFeeFromAmountEnabled(bool enabled);
+
     void disableSendButton();
     void enableSendButton();
 
index 8ad083d53d28067ee98beb7890bb5260568bcd3a..59f1f69585dabe6e1d0811d5b113601a480113db 100644 (file)
@@ -7,7 +7,7 @@
     <x>0</x>
     <y>0</y>
     <width>647</width>
-    <height>231</height>
+    <height>254</height>
    </rect>
   </property>
   <property name="sizePolicy">
        </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">
      </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>
      </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>
index 28cee3ed76213cf4eca7c654445fa2dd26c21235..37e69e41b7602b0218343f88ea8d81e13cd5f5d7 100644 (file)
@@ -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() {
index 10703e59fd13871bb557823b0a2240b9a42070ba..604dd9c5917487fa13a4b9b7d6a62018426cf547 100644 (file)
@@ -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();
index 897e7c6874d324280c46da3632c83102d0922318..f27703c5fd610b5ccbf9acc33d37b4e56a5b783a 100644 (file)
@@ -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">
               </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">
index 90b3e1f518d339efd8691073206c41330b2243b4..616cd6dda19fba28867fadc4ae255fcaeaab6ded 100644 (file)
@@ -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);
index eb4a8b42332f11a49a69b163ff40eb8e4ae45ada..92c9225d677fbbb97feb21d01a07c6093a9d9243 100644 (file)
@@ -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();
index c0490e71fff72447dded37d076c0feaa8e823cb3..b9e4c00f1860cffae3b6890c9e3a0f6b1d9cfdfe 100644 (file)
@@ -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;
\ No newline at end of file
index e11a37b7492a3dea05f5aeb4e6b90ba31cd90f27..a274482432c44c2534cce00c0832217aef3c1844 100644 (file)
@@ -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;
 };
 
 
index fc417c4de6256f6284990dc3023f6ed6298a784b..5225aa14ccf377d96d5aee37854a8642ce3d85e3 100644 (file)
@@ -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>
        </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>
diff --git a/src/dialog/TxPoolViewerDialog.cpp b/src/dialog/TxPoolViewerDialog.cpp
new file mode 100644 (file)
index 0000000..30f4e0d
--- /dev/null
@@ -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;
diff --git a/src/dialog/TxPoolViewerDialog.h b/src/dialog/TxPoolViewerDialog.h
new file mode 100644 (file)
index 0000000..bec5db3
--- /dev/null
@@ -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
diff --git a/src/dialog/TxPoolViewerDialog.ui b/src/dialog/TxPoolViewerDialog.ui
new file mode 100644 (file)
index 0000000..9d3c35f
--- /dev/null
@@ -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>
index 68767f51e818b1579524350fa011ff6b533e974f..85e751daa3bbe72931939fe98b76305ef88eeb2f 100644 (file)
@@ -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];
 }
index 9e71f71a7c763fabe3581b8ef70a69baa10572f3..754cfcd16658ad4048e7bd1ae2052dd98b5c1138 100644 (file)
@@ -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;
index d502db21b739c0c46697878f2f19f28759830cba..d2ae300161d2c9445a2a2305ae7f0df82223312a 100644 (file)
@@ -4,9 +4,7 @@
 #ifndef TRANSFER_H
 #define TRANSFER_H
 
-#include <wallet/api/wallet2_api.h>
 #include <QObject>
-#include <utility>
 
 class Transfer : public QObject
 {
index cb1332955fbe6231bbaee401be8ac7a45228dad9..c5eb5a8bffc2eccb1633547be31d0868fa43f481 100644 (file)
@@ -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");
index 7dbc0380b86f5f922b42be692f14c79fb8658e11..bf5636b0bbde7733a3e65caa3764264913d1dc17 100644 (file)
@@ -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;
diff --git a/src/libwalletqt/rows/TxBacklogEntry.h b/src/libwalletqt/rows/TxBacklogEntry.h
new file mode 100644 (file)
index 0000000..1ff18d7
--- /dev/null
@@ -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
index e625aea0b8adf206bae6a3202a30e5491bb94344..e85f615d47601307bbe3539f650b87047558c3ba 100644 (file)
@@ -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;
 
index 8617109e8b3f62d5b9f93e342c1a3af7fe34e983..c04799ac7978328c58fc51881e937871280b8bb8 100644 (file)
@@ -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}},
index 59c18102936feb02f5eb6c25d3a3062673ab4d54..9f163e0e24bd21847b86759a096d6c5444786a5c 100644 (file)
@@ -121,6 +121,8 @@ public:
         multiBroadcast,
         offlineTxSigningMethod,
         offlineTxSigningForceKISync,
+        manualFeeTierSelection,
+        subtractFeeFromAmount,
 
         // Misc
         blockExplorers,
index f475301c7c5e495bb0c83bc1122b1a76576e5c43..a31acc2f00f4016a52668b3eb18c3513cf61239c 100644 (file)
@@ -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;