Kivy: Show fee dialog before opening a new channel.
Remove fee and rbf from settings, as they are now always proposed
This commit is contained in:
@@ -97,11 +97,12 @@ Builder.load_string('''
|
|||||||
on_release:
|
on_release:
|
||||||
popup.dismiss()
|
popup.dismiss()
|
||||||
Button:
|
Button:
|
||||||
|
id: ok_button
|
||||||
text: _('OK')
|
text: _('OK')
|
||||||
size_hint: 0.5, None
|
size_hint: 0.5, None
|
||||||
height: '48dp'
|
height: '48dp'
|
||||||
on_release:
|
on_release:
|
||||||
root.pay()
|
root.on_pay(root.tx)
|
||||||
popup.dismiss()
|
popup.dismiss()
|
||||||
''')
|
''')
|
||||||
|
|
||||||
@@ -110,33 +111,35 @@ Builder.load_string('''
|
|||||||
|
|
||||||
class ConfirmTxDialog(FeeSliderDialog, Factory.Popup):
|
class ConfirmTxDialog(FeeSliderDialog, Factory.Popup):
|
||||||
|
|
||||||
def __init__(self, app: 'ElectrumWindow', invoice):
|
def __init__(self, app: 'ElectrumWindow', amount, make_tx, on_pay, *, show_final=True):
|
||||||
|
|
||||||
Factory.Popup.__init__(self)
|
Factory.Popup.__init__(self)
|
||||||
FeeSliderDialog.__init__(self, app.electrum_config, self.ids.slider)
|
FeeSliderDialog.__init__(self, app.electrum_config, self.ids.slider)
|
||||||
self.app = app
|
self.app = app
|
||||||
self.show_final = bool(self.config.get('use_rbf'))
|
self.amount = amount
|
||||||
self.invoice = invoice
|
self.make_tx = make_tx
|
||||||
|
self.on_pay = on_pay
|
||||||
|
self.show_final = show_final
|
||||||
self.update_slider()
|
self.update_slider()
|
||||||
self.update_text()
|
self.update_text()
|
||||||
self.update_tx()
|
self.update_tx()
|
||||||
|
|
||||||
def update_tx(self):
|
def update_tx(self):
|
||||||
outputs = self.invoice.outputs
|
rbf = not bool(self.ids.final_cb.active) if self.show_final else False
|
||||||
try:
|
try:
|
||||||
# make unsigned transaction
|
# make unsigned transaction
|
||||||
coins = self.app.wallet.get_spendable_coins(None)
|
tx = self.make_tx(rbf)
|
||||||
tx = self.app.wallet.make_unsigned_transaction(coins=coins, outputs=outputs)
|
|
||||||
except NotEnoughFunds:
|
except NotEnoughFunds:
|
||||||
self.warning = _("Not enough funds")
|
self.warning = _("Not enough funds")
|
||||||
|
self.ids.ok_button.disabled = True
|
||||||
return
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.exception('')
|
self.ids.ok_button.disabled = True
|
||||||
|
self.app.logger.exception('')
|
||||||
self.app.show_error(repr(e))
|
self.app.show_error(repr(e))
|
||||||
return
|
return
|
||||||
rbf = not bool(self.ids.final_cb.active) if self.show_final else False
|
self.ids.ok_button.disabled = False
|
||||||
tx.set_rbf(rbf)
|
amount = self.amount if self.amount != '!' else tx.output_value()
|
||||||
amount = sum(map(lambda x: x.value, outputs)) if '!' not in [x.value for x in outputs] else tx.output_value()
|
|
||||||
tx_size = tx.estimated_size()
|
tx_size = tx.estimated_size()
|
||||||
fee = tx.get_fee()
|
fee = tx.get_fee()
|
||||||
feerate = Decimal(fee) / tx_size # sat/byte
|
feerate = Decimal(fee) / tx_size # sat/byte
|
||||||
@@ -166,9 +169,6 @@ class ConfirmTxDialog(FeeSliderDialog, Factory.Popup):
|
|||||||
target, tooltip, dyn = self.config.get_fee_target()
|
target, tooltip, dyn = self.config.get_fee_target()
|
||||||
self.ids.fee_button.text = target
|
self.ids.fee_button.text = target
|
||||||
|
|
||||||
def pay(self):
|
|
||||||
self.app.protected(_('Send payment?'), self.app.send_screen.send_tx, (self.tx, self.invoice))
|
|
||||||
|
|
||||||
def on_fee_button(self):
|
def on_fee_button(self):
|
||||||
fee_dialog = FeeDialog(self, self.config, self.after_fee_changed)
|
fee_dialog = FeeDialog(self, self.config, self.after_fee_changed)
|
||||||
fee_dialog.open()
|
fee_dialog.open()
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from electrum.logging import Logger
|
|||||||
from electrum.lnutil import ln_dummy_address
|
from electrum.lnutil import ln_dummy_address
|
||||||
|
|
||||||
from .label_dialog import LabelDialog
|
from .label_dialog import LabelDialog
|
||||||
|
from .confirm_tx_dialog import ConfirmTxDialog
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ...main_window import ElectrumWindow
|
from ...main_window import ElectrumWindow
|
||||||
@@ -174,20 +175,26 @@ class LightningOpenChannelDialog(Factory.Popup, Logger):
|
|||||||
else:
|
else:
|
||||||
conn_str = str(self.trampolines[self.pubkey])
|
conn_str = str(self.trampolines[self.pubkey])
|
||||||
amount = '!' if self.is_max else self.app.get_amount(self.amount)
|
amount = '!' if self.is_max else self.app.get_amount(self.amount)
|
||||||
self.app.protected('Create a new channel?', self.do_open_channel, (conn_str, amount))
|
|
||||||
self.dismiss()
|
self.dismiss()
|
||||||
|
|
||||||
def do_open_channel(self, conn_str, amount, password):
|
|
||||||
coins = self.app.wallet.get_spendable_coins(None, nonlocal_only=True)
|
|
||||||
lnworker = self.app.wallet.lnworker
|
lnworker = self.app.wallet.lnworker
|
||||||
try:
|
coins = self.app.wallet.get_spendable_coins(None, nonlocal_only=True)
|
||||||
funding_tx = lnworker.mktx_for_open_channel(coins=coins, funding_sat=amount)
|
make_tx = lambda rbf: lnworker.mktx_for_open_channel(
|
||||||
except Exception as e:
|
coins=coins,
|
||||||
self.logger.exception("Problem opening channel")
|
funding_sat=amount,
|
||||||
self.app.show_error(_('Problem opening channel: ') + '\n' + repr(e))
|
fee_est=None)
|
||||||
return
|
on_pay = lambda tx: self.app.protected('Create a new channel?', self.do_open_channel, (tx, conn_str))
|
||||||
|
d = ConfirmTxDialog(
|
||||||
|
self.app,
|
||||||
|
amount = amount,
|
||||||
|
make_tx=make_tx,
|
||||||
|
on_pay=on_pay,
|
||||||
|
show_final=False)
|
||||||
|
d.open()
|
||||||
|
|
||||||
|
def do_open_channel(self, funding_tx, conn_str, password):
|
||||||
# read funding_sat from tx; converts '!' to int value
|
# read funding_sat from tx; converts '!' to int value
|
||||||
funding_sat = funding_tx.output_value_for_address(ln_dummy_address())
|
funding_sat = funding_tx.output_value_for_address(ln_dummy_address())
|
||||||
|
lnworker = self.app.wallet.lnworker
|
||||||
try:
|
try:
|
||||||
chan, funding_tx = lnworker.open_channel(
|
chan, funding_tx = lnworker.open_channel(
|
||||||
connect_str=conn_str,
|
connect_str=conn_str,
|
||||||
@@ -196,7 +203,7 @@ class LightningOpenChannelDialog(Factory.Popup, Logger):
|
|||||||
push_amt_sat=0,
|
push_amt_sat=0,
|
||||||
password=password)
|
password=password)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.exception("Problem opening channel")
|
self.app.logger.exception("Problem opening channel")
|
||||||
self.app.show_error(_('Problem opening channel: ') + '\n' + repr(e))
|
self.app.show_error(_('Problem opening channel: ') + '\n' + repr(e))
|
||||||
return
|
return
|
||||||
n = chan.constraints.funding_txn_minimum_depth
|
n = chan.constraints.funding_txn_minimum_depth
|
||||||
|
|||||||
@@ -49,11 +49,6 @@ Builder.load_string('''
|
|||||||
description: _("Base unit for Bitcoin amounts.")
|
description: _("Base unit for Bitcoin amounts.")
|
||||||
action: partial(root.unit_dialog, self)
|
action: partial(root.unit_dialog, self)
|
||||||
CardSeparator
|
CardSeparator
|
||||||
SettingsItem:
|
|
||||||
title: _('Onchain fees') + ': ' + app.fee_status
|
|
||||||
description: _('Choose how transaction fees are estimated')
|
|
||||||
action: lambda dt: app.fee_dialog()
|
|
||||||
CardSeparator
|
|
||||||
SettingsItem:
|
SettingsItem:
|
||||||
status: root.fx_status()
|
status: root.fx_status()
|
||||||
title: _('Fiat Currency') + ': ' + self.status
|
title: _('Fiat Currency') + ': ' + self.status
|
||||||
@@ -66,16 +61,6 @@ Builder.load_string('''
|
|||||||
description: _("Save and synchronize your labels.")
|
description: _("Save and synchronize your labels.")
|
||||||
action: partial(root.plugin_dialog, 'labels', self)
|
action: partial(root.plugin_dialog, 'labels', self)
|
||||||
CardSeparator
|
CardSeparator
|
||||||
SettingsItem:
|
|
||||||
status: 'ON' if app.use_rbf else 'OFF'
|
|
||||||
title: _('Replace-by-fee') + ': ' + self.status
|
|
||||||
description: _("Create replaceable transactions.")
|
|
||||||
message:
|
|
||||||
_('If you check this box, your transactions will be marked as non-final,') \
|
|
||||||
+ ' ' + _('and you will have the possibility, while they are unconfirmed, to replace them with transactions that pays higher fees.') \
|
|
||||||
+ ' ' + _('Note that some merchants do not accept non-final transactions until they are confirmed.')
|
|
||||||
action: partial(root.boolean_dialog, 'use_rbf', _('Replace by fee'), self.message)
|
|
||||||
CardSeparator
|
|
||||||
SettingsItem:
|
SettingsItem:
|
||||||
status: _('Yes') if app.use_unconfirmed else _('No')
|
status: _('Yes') if app.use_unconfirmed else _('No')
|
||||||
title: _('Spend unconfirmed') + ': ' + self.status
|
title: _('Spend unconfirmed') + ': ' + self.status
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ from electrum.lnutil import RECEIVED, SENT, PaymentFailure
|
|||||||
from electrum.logging import Logger
|
from electrum.logging import Logger
|
||||||
|
|
||||||
from .dialogs.question import Question
|
from .dialogs.question import Question
|
||||||
from .dialogs.lightning_open_channel import LightningOpenChannelDialog
|
from .dialogs.confirm_tx_dialog import ConfirmTxDialog
|
||||||
|
|
||||||
from electrum.gui.kivy import KIVY_GUI_PATH
|
from electrum.gui.kivy import KIVY_GUI_PATH
|
||||||
from electrum.gui.kivy.i18n import _
|
from electrum.gui.kivy.i18n import _
|
||||||
@@ -372,8 +372,12 @@ class SendScreen(CScreen, Logger):
|
|||||||
threading.Thread(target=pay_thread).start()
|
threading.Thread(target=pay_thread).start()
|
||||||
|
|
||||||
def _do_pay_onchain(self, invoice: OnchainInvoice) -> None:
|
def _do_pay_onchain(self, invoice: OnchainInvoice) -> None:
|
||||||
from .dialogs.confirm_tx_dialog import ConfirmTxDialog
|
outputs = invoice.outputs
|
||||||
d = ConfirmTxDialog(self.app, invoice)
|
amount = sum(map(lambda x: x.value, outputs)) if '!' not in [x.value for x in outputs] else '!'
|
||||||
|
coins = self.app.wallet.get_spendable_coins(None)
|
||||||
|
make_tx = lambda rbf: self.app.wallet.make_unsigned_transaction(coins=coins, outputs=outputs, rbf=rbf)
|
||||||
|
on_pay = lambda tx: self.app.protected(_('Send payment?'), self.send_tx, (tx, invoice))
|
||||||
|
d = ConfirmTxDialog(self.app, amount=amount, make_tx=make_tx, on_pay=on_pay)
|
||||||
d.open()
|
d.open()
|
||||||
|
|
||||||
def send_tx(self, tx, invoice, password):
|
def send_tx(self, tx, invoice, password):
|
||||||
|
|||||||
@@ -1796,9 +1796,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
|||||||
|
|
||||||
def mktx_for_open_channel(self, funding_sat):
|
def mktx_for_open_channel(self, funding_sat):
|
||||||
coins = self.get_coins(nonlocal_only=True)
|
coins = self.get_coins(nonlocal_only=True)
|
||||||
make_tx = lambda fee_est: self.wallet.lnworker.mktx_for_open_channel(coins=coins,
|
make_tx = lambda fee_est: self.wallet.lnworker.mktx_for_open_channel(
|
||||||
funding_sat=funding_sat,
|
coins=coins,
|
||||||
fee_est=fee_est)
|
funding_sat=funding_sat,
|
||||||
|
fee_est=fee_est)
|
||||||
return make_tx
|
return make_tx
|
||||||
|
|
||||||
def open_channel(self, connect_str, funding_sat, push_amt):
|
def open_channel(self, connect_str, funding_sat, push_amt):
|
||||||
@@ -1821,11 +1822,12 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
|||||||
# read funding_sat from tx; converts '!' to int value
|
# read funding_sat from tx; converts '!' to int value
|
||||||
funding_sat = funding_tx.output_value_for_address(ln_dummy_address())
|
funding_sat = funding_tx.output_value_for_address(ln_dummy_address())
|
||||||
def task():
|
def task():
|
||||||
return self.wallet.lnworker.open_channel(connect_str=connect_str,
|
return self.wallet.lnworker.open_channel(
|
||||||
funding_tx=funding_tx,
|
connect_str=connect_str,
|
||||||
funding_sat=funding_sat,
|
funding_tx=funding_tx,
|
||||||
push_amt_sat=push_amt,
|
funding_sat=funding_sat,
|
||||||
password=password)
|
push_amt_sat=push_amt,
|
||||||
|
password=password)
|
||||||
def on_success(args):
|
def on_success(args):
|
||||||
chan, funding_tx = args
|
chan, funding_tx = args
|
||||||
n = chan.constraints.funding_txn_minimum_depth
|
n = chan.constraints.funding_txn_minimum_depth
|
||||||
|
|||||||
@@ -183,8 +183,8 @@ async def sweep(
|
|||||||
fee: int = None,
|
fee: int = None,
|
||||||
imax=100,
|
imax=100,
|
||||||
locktime=None,
|
locktime=None,
|
||||||
tx_version=None
|
tx_version=None) -> PartialTransaction:
|
||||||
) -> PartialTransaction:
|
|
||||||
inputs, keypairs = await sweep_preparations(privkeys, network, imax)
|
inputs, keypairs = await sweep_preparations(privkeys, network, imax)
|
||||||
total = sum(txin.value_sats() for txin in inputs)
|
total = sum(txin.value_sats() for txin in inputs)
|
||||||
if fee is None:
|
if fee is None:
|
||||||
@@ -1236,9 +1236,14 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
|||||||
assert is_address(selected_addr), f"not valid bitcoin address: {selected_addr}"
|
assert is_address(selected_addr), f"not valid bitcoin address: {selected_addr}"
|
||||||
return selected_addr
|
return selected_addr
|
||||||
|
|
||||||
def make_unsigned_transaction(self, *, coins: Sequence[PartialTxInput],
|
def make_unsigned_transaction(
|
||||||
outputs: List[PartialTxOutput], fee=None,
|
self, *,
|
||||||
change_addr: str = None, is_sweep=False) -> PartialTransaction:
|
coins: Sequence[PartialTxInput],
|
||||||
|
outputs: List[PartialTxOutput],
|
||||||
|
fee=None,
|
||||||
|
change_addr: str = None,
|
||||||
|
is_sweep=False,
|
||||||
|
rbf=False) -> PartialTransaction:
|
||||||
|
|
||||||
if any([c.already_has_some_signatures() for c in coins]):
|
if any([c.already_has_some_signatures() for c in coins]):
|
||||||
raise Exception("Some inputs already contain signatures!")
|
raise Exception("Some inputs already contain signatures!")
|
||||||
@@ -1298,12 +1303,13 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
|||||||
old_change_addrs = []
|
old_change_addrs = []
|
||||||
# change address. if empty, coin_chooser will set it
|
# change address. if empty, coin_chooser will set it
|
||||||
change_addrs = self.get_change_addresses_for_new_transaction(change_addr or old_change_addrs)
|
change_addrs = self.get_change_addresses_for_new_transaction(change_addr or old_change_addrs)
|
||||||
tx = coin_chooser.make_tx(coins=coins,
|
tx = coin_chooser.make_tx(
|
||||||
inputs=txi,
|
coins=coins,
|
||||||
outputs=list(outputs) + txo,
|
inputs=txi,
|
||||||
change_addrs=change_addrs,
|
outputs=list(outputs) + txo,
|
||||||
fee_estimator_vb=fee_estimator,
|
change_addrs=change_addrs,
|
||||||
dust_threshold=self.dust_threshold())
|
fee_estimator_vb=fee_estimator,
|
||||||
|
dust_threshold=self.dust_threshold())
|
||||||
else:
|
else:
|
||||||
# "spend max" branch
|
# "spend max" branch
|
||||||
# note: This *will* spend inputs with negative effective value (if there are any).
|
# note: This *will* spend inputs with negative effective value (if there are any).
|
||||||
@@ -1325,7 +1331,7 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
|||||||
# Timelock tx to current height.
|
# Timelock tx to current height.
|
||||||
tx.locktime = get_locktime_for_new_transaction(self.network)
|
tx.locktime = get_locktime_for_new_transaction(self.network)
|
||||||
|
|
||||||
tx.set_rbf(False) # caller can set RBF manually later
|
tx.set_rbf(rbf)
|
||||||
tx.add_info_from_wallet(self)
|
tx.add_info_from_wallet(self)
|
||||||
run_hook('make_unsigned_transaction', self, tx)
|
run_hook('make_unsigned_transaction', self, tx)
|
||||||
return tx
|
return tx
|
||||||
@@ -1340,8 +1346,8 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
|||||||
coins=coins,
|
coins=coins,
|
||||||
outputs=outputs,
|
outputs=outputs,
|
||||||
fee=fee,
|
fee=fee,
|
||||||
change_addr=change_addr)
|
change_addr=change_addr,
|
||||||
tx.set_rbf(rbf)
|
rbf=rbf)
|
||||||
if tx_version is not None:
|
if tx_version is not None:
|
||||||
tx.version = tx_version
|
tx.version = tx_version
|
||||||
if sign:
|
if sign:
|
||||||
|
|||||||
Reference in New Issue
Block a user