URI: 
       tAdd memory pool based fee estimates - fee estimates can use ETA or mempool - require protocol version 1.2 - remove fee_unit preference - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit c3f3843cc3f31862f7e3bea1126cb42150c405c4
   DIR parent 2c619ec41d49072e5be05cd45a5019b32b628774
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Wed, 22 Nov 2017 12:09:56 +0100
       
       Add memory pool based fee estimates
        - fee estimates can use ETA or mempool
        - require protocol version 1.2
        - remove fee_unit preference
       
       Diffstat:
         M gui/kivy/uix/dialogs/fee_dialog.py  |      57 ++++++++++++++++++-------------
         M gui/kivy/uix/dialogs/settings.py    |       5 +----
         M gui/qt/amountedit.py                |       7 +------
         M gui/qt/fee_slider.py                |      34 ++++++++++---------------------
         M gui/qt/main_window.py               |      50 +++++++++++++++++--------------
         M gui/qt/transaction_dialog.py        |       4 ++--
         M lib/bitcoin.py                      |       2 +-
         M lib/network.py                      |      11 ++++++++++-
         M lib/simple_config.py                |     118 ++++++++++++++++++++++++++++---
         M lib/version.py                      |       2 +-
         M lib/wallet.py                       |      17 +++++++++--------
       
       11 files changed, 204 insertions(+), 103 deletions(-)
       ---
   DIR diff --git a/gui/kivy/uix/dialogs/fee_dialog.py b/gui/kivy/uix/dialogs/fee_dialog.py
       t@@ -32,7 +32,15 @@ Builder.load_string('''
                        text: _('Dynamic Fees')
                    CheckBox:
                        id: dynfees
       -                on_active: root.on_checkbox(self.active)
       +                on_active: root.on_dynfees(self.active)
       +        BoxLayout:
       +            orientation: 'horizontal'
       +            size_hint: 1, 0.5
       +            Label:
       +                text: _('Use mempool')
       +            CheckBox:
       +                id: mempool
       +                on_active: root.on_mempool(self.active)
                Widget:
                    size_hint: 1, 1
                BoxLayout:
       t@@ -60,7 +68,9 @@ class FeeDialog(Factory.Popup):
                self.config = config
                self.fee_rate = self.config.fee_per_kb()
                self.callback = callback
       +        self.mempool = self.config.get('mempool_fees', False)
                self.dynfees = self.config.get('dynamic_fees', True)
       +        self.ids.mempool.active = self.mempool
                self.ids.dynfees.active = self.dynfees
                self.update_slider()
                self.update_text()
       t@@ -71,34 +81,30 @@ class FeeDialog(Factory.Popup):
        
            def update_slider(self):
                slider = self.ids.slider
       -        if self.dynfees:
       -            slider.range = (0, 4)
       -            slider.step = 1
       -            slider.value = self.config.get('fee_level', 2)
       -        else:
       -            slider.range = (0, 9)
       -            slider.step = 1
       -            slider.value = self.config.static_fee_index(self.fee_rate)
       +        maxp, pos, fee_rate = self.config.get_fee_slider(self.dynfees, self.mempool)
       +        slider.range = (0, maxp)
       +        slider.step = 1
       +        slider.value = pos
        
       -    def get_fee_text(self, value):
       -        if self.ids.dynfees.active:
       -            tooltip = fee_levels[value]
       -            if self.config.has_fee_estimates():
       -                dynfee = self.config.dynfee(value)
       -                tooltip += '\n' + (self.app.format_amount_and_units(dynfee)) + '/kB'
       +    def get_fee_text(self, pos):
       +        dyn = self.dynfees
       +        mempool = self.mempool
       +        if dyn:
       +            fee_rate = self.config.depth_to_fee(pos) if mempool else self.config.eta_to_fee(pos)
                else:
       -            fee_rate = self.config.static_fee(value)
       -            tooltip = self.app.format_amount_and_units(fee_rate) + '/kB'
       -            if self.config.has_fee_estimates():
       -                i = self.config.reverse_dynfee(fee_rate)
       -                tooltip += '\n' + (_('low fee') if i < 0 else 'Within %d blocks'%i)
       -        return tooltip
       +            fee_rate = self.config.static_fee(pos)
       +        target, tooltip = self.config.get_fee_text(pos, dyn, mempool, fee_rate)
       +        return target
        
            def on_ok(self):
                value = int(self.ids.slider.value)
                self.config.set_key('dynamic_fees', self.dynfees, False)
       +        self.config.set_key('mempool_fees', self.mempool, False)
                if self.dynfees:
       -            self.config.set_key('fee_level', value, True)
       +            if self.mempool:
       +                self.config.set_key('depth_level', value, True)
       +            else:
       +                self.config.set_key('fee_level', value, True)
                else:
                    self.config.set_key('fee_per_kb', self.config.static_fee(value), True)
                self.callback()
       t@@ -106,7 +112,12 @@ class FeeDialog(Factory.Popup):
            def on_slider(self, value):
                self.update_text()
        
       -    def on_checkbox(self, b):
       +    def on_dynfees(self, b):
                self.dynfees = b
                self.update_slider()
                self.update_text()
       +
       +    def on_mempool(self, b):
       +        self.mempool = b
       +        self.update_slider()
       +        self.update_text()
   DIR diff --git a/gui/kivy/uix/dialogs/settings.py b/gui/kivy/uix/dialogs/settings.py
       t@@ -204,10 +204,7 @@ class SettingsDialog(Factory.Popup):
                d.open()
        
            def fee_status(self):
       -        if self.config.get('dynamic_fees', True):
       -            return fee_levels[self.config.get('fee_level', 2)]
       -        else:
       -            return self.app.format_amount_and_units(self.config.fee_per_kb()) + '/kB'
       +        return self.config.get_fee_status()
        
            def fee_dialog(self, label, dt):
                if self._fee_dialog is None:
   DIR diff --git a/gui/qt/amountedit.py b/gui/qt/amountedit.py
       t@@ -106,12 +106,7 @@ class BTCAmountEdit(AmountEdit):
        
        class FeerateEdit(BTCAmountEdit):
            def _base_unit(self):
       -        p = self.decimal_point()
       -        if p == 2:
       -            return 'mBTC/kB'
       -        if p == 0:
       -            return 'sat/byte'
       -        raise Exception('Unknown base unit')
       +        return 'sat/byte'
        
            def get_amount(self):
                sat_per_byte_amount = BTCAmountEdit.get_amount(self)
   DIR diff --git a/gui/qt/fee_slider.py b/gui/qt/fee_slider.py
       t@@ -1,6 +1,4 @@
       -
        from electrum.i18n import _
       -
        from PyQt5.QtGui import *
        from PyQt5.QtCore import *
        from PyQt5.QtWidgets import QSlider, QToolTip
       t@@ -22,37 +20,27 @@ class FeeSlider(QSlider):
        
            def moved(self, pos):
                with self.lock:
       -            fee_rate = self.config.dynfee(pos) if self.dyn else self.config.static_fee(pos)
       +            if self.dyn:
       +                fee_rate = self.config.depth_to_fee(pos) if self.config.get('mempool_fees') else self.config.eta_to_fee(pos)
       +            else:
       +                fee_rate = self.config.static_fee(pos)
                    tooltip = self.get_tooltip(pos, fee_rate)
                    QToolTip.showText(QCursor.pos(), tooltip, self)
                    self.setToolTip(tooltip)
                    self.callback(self.dyn, pos, fee_rate)
        
            def get_tooltip(self, pos, fee_rate):
       -        from electrum.util import fee_levels
       -        rate_str = self.window.format_fee_rate(fee_rate) if fee_rate else _('unknown')
       -        if self.dyn:
       -            tooltip = fee_levels[pos] + '\n' + rate_str
       -        else:
       -            tooltip = 'Fixed rate: ' + rate_str
       -            if self.config.has_fee_estimates():
       -                i = self.config.reverse_dynfee(fee_rate)
       -                tooltip += '\n' + (_('Low fee') if i < 0 else 'Within %d blocks'%i)
       -        return tooltip
       +        mempool = self.config.get('mempool_fees')
       +        text, tooltip = self.config.get_fee_text(pos, self.dyn, mempool, fee_rate)
       +        return text + '\n' + tooltip
        
            def update(self):
                with self.lock:
                    self.dyn = self.config.is_dynfee()
       -            if self.dyn:
       -                pos = self.config.get('fee_level', 2)
       -                fee_rate = self.config.dynfee(pos)
       -                self.setRange(0, 4)
       -                self.setValue(pos)
       -            else:
       -                fee_rate = self.config.fee_per_kb()
       -                pos = self.config.static_fee_index(fee_rate)
       -                self.setRange(0, 9)
       -                self.setValue(pos)
       +            mempool = self.config.get('mempool_fees')
       +            maxp, pos, fee_rate = self.config.get_fee_slider(self.dyn, mempool)
       +            self.setRange(0, maxp)
       +            self.setValue(pos)
                    tooltip = self.get_tooltip(pos, fee_rate)
                    self.setToolTip(tooltip)
        
   DIR diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py
       t@@ -131,7 +131,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                self.need_update = threading.Event()
        
                self.decimal_point = config.get('decimal_point', 5)
       -        self.fee_unit = config.get('fee_unit', 0)
                self.num_zeros     = int(config.get('num_zeros',0))
        
                self.completions = QStringListModel()
       t@@ -293,7 +292,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                    self.need_update.set()
                    self.gui_object.network_updated_signal_obj.network_updated_signal \
                        .emit(event, args)
       -
                elif event == 'new_transaction':
                    self.tx_notifications.append(args[0])
                    self.notify_transactions_signal.emit()
       t@@ -315,6 +313,12 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                    if self.config.is_dynfee():
                        self.fee_slider.update()
                        self.do_update_fee()
       +        elif event == 'fee_histogram':
       +            if self.config.is_dynfee():
       +                self.fee_slider.update()
       +                self.do_update_fee()
       +            # todo: update only unconfirmed tx
       +            self.history_list.update()
                else:
                    self.print_error("unexpected network_qt signal:", event, args)
        
       t@@ -636,10 +640,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                return text
        
            def format_fee_rate(self, fee_rate):
       -        if self.fee_unit == 0:
       -            return format_satoshis(fee_rate/1000, False, self.num_zeros, 0, False)  + ' sat/byte'
       -        else:
       -            return self.format_amount(fee_rate) + ' ' + self.base_unit() + '/kB'
       +        return format_satoshis(fee_rate/1000, False, self.num_zeros, 0, False)  + ' sat/byte'
        
            def get_decimal_point(self):
                return self.decimal_point
       t@@ -1076,7 +1077,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
        
                def fee_cb(dyn, pos, fee_rate):
                    if dyn:
       -                self.config.set_key('fee_level', pos, False)
       +                if self.config.get('mempool_fees'):
       +                    self.config.set_key('depth_level', pos, False)
       +                else:
       +                    self.config.set_key('fee_level', pos, False)
                    else:
                        self.config.set_key('fee_per_kb', fee_rate, False)
        
       t@@ -1116,7 +1120,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                self.size_e.setFixedWidth(140)
                self.size_e.setStyleSheet(ColorScheme.DEFAULT.as_stylesheet())
        
       -        self.feerate_e = FeerateEdit(lambda: 2 if self.fee_unit else 0)
       +        self.feerate_e = FeerateEdit(lambda: 0)
                self.feerate_e.setAmount(self.config.fee_per_byte())
                self.feerate_e.textEdited.connect(partial(on_fee_or_feerate, self.feerate_e, False))
                self.feerate_e.editingFinished.connect(partial(on_fee_or_feerate, self.feerate_e, True))
       t@@ -1256,9 +1260,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                '''Recalculate the fee.  If the fee was manually input, retain it, but
                still build the TX to see if there are enough funds.
                '''
       -        if not self.config.get('offline') and self.config.is_dynfee() and not self.config.has_fee_estimates():
       -            self.statusBar().showMessage(_('Waiting for fee estimates...'))
       -            return False
                freeze_fee = self.is_send_fee_frozen()
                freeze_feerate = self.is_send_feerate_frozen()
                amount = '!' if self.is_max else self.amount_e.get_amount()
       t@@ -2670,6 +2671,21 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                nz.valueChanged.connect(on_nz)
                gui_widgets.append((nz_label, nz))
        
       +        msg = '\n'.join([
       +            _('Time based: fee rate is based on average confirmation time estimates'),
       +            _('Mempool based: fee rate is targetting a depth in the memory pool')
       +            ]
       +        )
       +        fee_type_label = HelpLabel(_('Fee estimation') + ':', msg)
       +        fee_type_combo = QComboBox()
       +        fee_type_combo.addItems([_('Time based'), _('Mempool based')])
       +        fee_type_combo.setCurrentIndex(1 if self.config.get('mempool_fees') else 0)
       +        def on_fee_type(x):
       +            self.config.set_key('mempool_fees', x==1)
       +            self.fee_slider.update()
       +        fee_type_combo.currentIndexChanged.connect(on_fee_type)
       +        fee_widgets.append((fee_type_label, fee_type_combo))
       +
                def on_dynfee(x):
                    self.config.set_key('dynamic_fees', x == Qt.Checked)
                    self.fee_slider.update()
       t@@ -2699,18 +2715,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                use_rbf_cb.stateChanged.connect(on_use_rbf)
                fee_widgets.append((use_rbf_cb, None))
        
       -        self.fee_unit = self.config.get('fee_unit', 0)
       -        fee_unit_label = HelpLabel(_('Fee Unit') + ':', '')
       -        fee_unit_combo = QComboBox()
       -        fee_unit_combo.addItems([_('sat/byte'), _('mBTC/kB')])
       -        fee_unit_combo.setCurrentIndex(self.fee_unit)
       -        def on_fee_unit(x):
       -            self.fee_unit = x
       -            self.config.set_key('fee_unit', x)
       -            self.fee_slider.update()
       -        fee_unit_combo.currentIndexChanged.connect(on_fee_unit)
       -        fee_widgets.append((fee_unit_label, fee_unit_combo))
       -
                msg = _('OpenAlias record, used to receive coins and to sign payment requests.') + '\n\n'\
                      + _('The following alias providers are available:') + '\n'\
                      + '\n'.join(['https://cryptoname.co/', 'http://xmr.link']) + '\n\n'\
   DIR diff --git a/gui/qt/transaction_dialog.py b/gui/qt/transaction_dialog.py
       t@@ -221,8 +221,8 @@ class TxDialog(QDialog, MessageBoxMixin):
                    self.date_label.setText(_("Date: {}").format(time_str))
                    self.date_label.show()
                elif exp_n:
       -            text = '%d blocks'%(exp_n) if exp_n > 0 else _('unknown (low fee)')
       -            self.date_label.setText(_('Expected confirmation time') + ': ' + text)
       +            text = '%.2f MB'%(exp_n/1000000)
       +            self.date_label.setText(_('Position in mempool') + ': ' + text + _('from tip'))
                    self.date_label.show()
                else:
                    self.date_label.hide()
   DIR diff --git a/lib/bitcoin.py b/lib/bitcoin.py
       t@@ -102,7 +102,7 @@ NetworkConstants.set_mainnet()
        
        FEE_STEP = 10000
        MAX_FEE_RATE = 300000
       -FEE_TARGETS = [25, 10, 5, 2]
       +
        
        COINBASE_MATURITY = 100
        COIN = 100000000
   DIR diff --git a/lib/network.py b/lib/network.py
       t@@ -321,8 +321,10 @@ class Network(util.DaemonThread):
                    self.queue_request('blockchain.scripthash.subscribe', [h])
        
            def request_fee_estimates(self):
       +        from .simple_config import FEE_ETA_TARGETS
                self.config.requested_fee_estimates()
       -        for i in bitcoin.FEE_TARGETS:
       +        self.queue_request('mempool.get_fee_histogram', [])
       +        for i in FEE_ETA_TARGETS:
                    self.queue_request('blockchain.estimatefee', [i])
        
            def get_status_value(self, key):
       t@@ -332,6 +334,8 @@ class Network(util.DaemonThread):
                    value = self.banner
                elif key == 'fee':
                    value = self.config.fee_estimates
       +        elif key == 'fee_histogram':
       +            value = self.config.mempool_fees
                elif key == 'updated':
                    value = (self.get_local_height(), self.get_server_height())
                elif key == 'servers':
       t@@ -543,6 +547,11 @@ class Network(util.DaemonThread):
                elif method == 'server.donation_address':
                    if error is None:
                        self.donation_address = result
       +        elif method == 'mempool.get_fee_histogram':
       +            if error is None:
       +                self.print_error(result)
       +                self.config.mempool_fees = result
       +                self.notify('fee_histogram')
                elif method == 'blockchain.estimatefee':
                    if error is None and result > 0:
                        i = params[0]
   DIR diff --git a/lib/simple_config.py b/lib/simple_config.py
       t@@ -6,9 +6,12 @@ import stat
        
        from copy import deepcopy
        from .util import (user_dir, print_error, PrintError,
       -                   NoDynamicFeeEstimates)
       +                   NoDynamicFeeEstimates, format_satoshis)
        
       -from .bitcoin import MAX_FEE_RATE, FEE_TARGETS
       +from .bitcoin import MAX_FEE_RATE
       +
       +FEE_ETA_TARGETS = [25, 10, 5, 2]
       +FEE_DEPTH_TARGETS = [10000000, 5000000, 2000000, 1000000, 500000, 200000, 100000]
        
        config = None
        
       t@@ -48,6 +51,7 @@ class SimpleConfig(PrintError):
                # a thread-safe way.
                self.lock = threading.RLock()
        
       +        self.mempool_fees = {}
                self.fee_estimates = {}
                self.fee_estimates_last_updated = {}
                self.last_time_fee_estimates_requested = 0  # zero ensures immediate fees
       t@@ -263,9 +267,9 @@ class SimpleConfig(PrintError):
                    f = MAX_FEE_RATE
                return f
        
       -    def dynfee(self, i):
       +    def eta_to_fee(self, i):
                if i < 4:
       -            j = FEE_TARGETS[i]
       +            j = FEE_ETA_TARGETS[i]
                    fee = self.fee_estimates.get(j)
                else:
                    assert i == 4
       t@@ -276,15 +280,99 @@ class SimpleConfig(PrintError):
                    fee = min(5*MAX_FEE_RATE, fee)
                return fee
        
       -    def reverse_dynfee(self, fee_per_kb):
       +    def fee_to_depth(self, target_fee):
       +        depth = 0
       +        for fee, s in self.mempool_fees:
       +            depth += s
       +            if fee < target_fee:
       +                break
       +        else:
       +            return 0
       +        return depth
       +
       +    def depth_to_fee(self, i):
       +        target = self.depth_target(i)
       +        depth = 0
       +        for fee, s in self.mempool_fees:
       +            depth += s
       +            if depth > target:
       +                break
       +        else:
       +            return 0
       +        return fee * 1000
       +
       +    def depth_target(self, i):
       +        return FEE_DEPTH_TARGETS[i]
       +
       +    def eta_target(self, i):
       +        return FEE_ETA_TARGETS[i]
       +
       +    def fee_to_eta(self, fee_per_kb):
                import operator
       -        l = list(self.fee_estimates.items()) + [(1, self.dynfee(4))]
       +        l = list(self.fee_estimates.items()) + [(1, self.eta_to_fee(4))]
                dist = map(lambda x: (x[0], abs(x[1] - fee_per_kb)), l)
                min_target, min_value = min(dist, key=operator.itemgetter(1))
                if fee_per_kb < self.fee_estimates.get(25)/2:
                    min_target = -1
                return min_target
        
       +    def depth_tooltip(self, depth):
       +        return "%.1f MB from tip"%(depth/1000000)
       +
       +    def eta_tooltip(self, x):
       +        return 'Low fee' if x < 0 else 'Within %d blocks'%x
       +
       +    def get_fee_status(self):
       +        dyn = self.is_dynfee()
       +        mempool = self.get('mempool_fees')
       +        pos = self.get('fee_level', 2) if mempool else self.get('depth_level', 2)
       +        fee_rate = self.fee_per_kb()
       +        target, tooltip = self.get_fee_text(pos, dyn, mempool, fee_rate)
       +        return target
       +
       +    def get_fee_text(self, pos, dyn, mempool, fee_rate):
       +        rate_str = (format_satoshis(fee_rate/1000, False, 0, 0, False)  + ' sat/byte') if fee_rate is not None else 'unknown'
       +        if dyn:
       +            if mempool:
       +                depth = self.depth_target(pos)
       +                text = self.depth_tooltip(depth)
       +            else:
       +                eta = self.eta_target(pos)
       +                text = self.eta_tooltip(eta)
       +            tooltip = rate_str
       +        else:
       +            text = rate_str
       +            if mempool:
       +                if self.has_fee_mempool():
       +                    depth = self.fee_to_depth(fee_rate)
       +                    tooltip = self.depth_tooltip(depth)
       +                else:
       +                    tooltip = ''
       +            else:
       +                if self.has_fee_etas():
       +                    eta = self.fee_to_eta(fee_rate)
       +                    tooltip = self.eta_tooltip(eta)
       +                else:
       +                    tooltip = ''
       +        return text, tooltip
       +
       +    def get_fee_slider(self, dyn, mempool):
       +        if dyn:
       +            if mempool:
       +                maxp = len(FEE_DEPTH_TARGETS) - 1
       +                pos = min(maxp, self.get('depth_level', 2))
       +                fee_rate = self.depth_to_fee(pos)
       +            else:
       +                maxp = len(FEE_ETA_TARGETS) - 1
       +                pos = min(maxp, self.get('fee_level', 2))
       +                fee_rate = self.eta_to_fee(pos)
       +        else:
       +            fee_rate = self.fee_per_kb()
       +            pos = self.static_fee_index(fee_rate)
       +            maxp= 9
       +        return maxp, pos, fee_rate
       +
       +
            def static_fee(self, i):
                return self.fee_rates[i]
        
       t@@ -292,19 +380,27 @@ class SimpleConfig(PrintError):
                dist = list(map(lambda x: abs(x - value), self.fee_rates))
                return min(range(len(dist)), key=dist.__getitem__)
        
       -    def has_fee_estimates(self):
       -        return len(self.fee_estimates)==4
       +    def has_fee_etas(self):
       +        return len(self.fee_estimates) == 4
       +
       +    def has_fee_mempool(self):
       +        return bool(self.mempool_fees)
        
            def is_dynfee(self):
                return self.get('dynamic_fees', True)
        
       +    def use_mempool_fees(self):
       +        return self.get('mempool_fees', False)
       +
            def fee_per_kb(self):
                """Returns sat/kvB fee to pay for a txn.
                Note: might return None.
                """
       -        dyn = self.is_dynfee()
       -        if dyn:
       -            fee_rate = self.dynfee(self.get('fee_level', 2))
       +        if self.is_dynfee():
       +            if self.use_mempool_fees():
       +                fee_rate = self.depth_to_fee(self.get('depth_level', 2))
       +            else:
       +                fee_rate = self.eta_to_fee(self.get('fee_level', 2))
                else:
                    fee_rate = self.get('fee_per_kb', self.max_fee_rate()/2)
                return fee_rate
   DIR diff --git a/lib/version.py b/lib/version.py
       t@@ -1,5 +1,5 @@
        ELECTRUM_VERSION = '3.0.6'   # version of the client package
       -PROTOCOL_VERSION = '1.1'     # protocol version requested
       +PROTOCOL_VERSION = '1.2'     # protocol version requested
        
        # The hash of the mnemonic seed must begin with this
        SEED_PREFIX      = '01'      # Standard wallet
   DIR diff --git a/lib/wallet.py b/lib/wallet.py
       t@@ -538,10 +538,10 @@ class Abstract_Wallet(PrintError):
                            status = _('Unconfirmed')
                            if fee is None:
                                fee = self.tx_fees.get(tx_hash)
       -                    if fee and self.network.config.has_fee_estimates():
       +                    if fee and self.network.config.has_fee_etas():
                                size = tx.estimated_size()
                                fee_per_kb = fee * 1000 / size
       -                        exp_n = self.network.config.reverse_dynfee(fee_per_kb)
       +                        exp_n = self.network.config.fee_to_eta(fee_per_kb)
                            can_bump = is_mine and not tx.is_final()
                        else:
                            status = _('Local')
       t@@ -860,18 +860,17 @@ class Abstract_Wallet(PrintError):
        
            def get_tx_status(self, tx_hash, height, conf, timestamp):
                from .util import format_time
       +        exp_n = False
                if conf == 0:
                    tx = self.transactions.get(tx_hash)
                    if not tx:
                        return 3, 'unknown'
                    is_final = tx and tx.is_final()
                    fee = self.tx_fees.get(tx_hash)
       -            if fee and self.network and self.network.config.has_fee_estimates():
       -                size = len(tx.raw)/2
       -                low_fee = int(self.network.config.dynfee(0)*size/1000)
       -                is_lowfee = fee < low_fee * 0.5
       -            else:
       -                is_lowfee = False
       +            if fee and self.network and self.network.config.has_fee_mempool():
       +                size = tx.estimated_size()
       +                fee_per_kb = fee * 1000 / size
       +                exp_n = self.network.config.fee_to_depth(fee_per_kb//1000)
                    if height == TX_HEIGHT_LOCAL:
                        status = 5
                    elif height == TX_HEIGHT_UNCONF_PARENT:
       t@@ -888,6 +887,8 @@ class Abstract_Wallet(PrintError):
                    status = 5 + min(conf, 6)
                time_str = format_time(timestamp) if timestamp else _("unknown")
                status_str = TX_STATUS[status] if status < 6 else time_str
       +        if exp_n:
       +            status_str += ' [%d sat/b, %.2f MB]'%(fee_per_kb//1000, exp_n/1000000)
                return status, status_str
        
            def relayfee(self):