qt: perform 'fully spend' action with coin selection, keep separate from coin control when doing action.
Also stop timer when dialog is finished, to avoid re-generating txs with the same input coin set, which results in an exception as these coins have signatures when the swap has started.
This commit is contained in:
@@ -1264,9 +1264,11 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
|
||||
|
||||
def run_swap_dialog(
|
||||
self,
|
||||
*,
|
||||
is_reverse: Optional[bool] = None,
|
||||
recv_amount_sat_or_max: Optional[Union[int, str]] = None,
|
||||
channels: Optional[Sequence['Channel']] = None,
|
||||
get_coins: Optional[Callable[..., Sequence[PartialTxInput]]] = None,
|
||||
) -> bool:
|
||||
if not self.network:
|
||||
self.show_error(_("You are offline."))
|
||||
@@ -1290,7 +1292,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
|
||||
transport,
|
||||
is_reverse=is_reverse,
|
||||
recv_amount_sat_or_max=recv_amount_sat_or_max,
|
||||
channels=channels
|
||||
channels=channels,
|
||||
get_coins=get_coins
|
||||
)
|
||||
try:
|
||||
return d.run(transport)
|
||||
@@ -1484,17 +1487,18 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
|
||||
msg = _('Signing transaction...')
|
||||
WaitingDialog(self, msg, task, on_success, on_failure)
|
||||
|
||||
def mktx_for_open_channel(self, *, funding_sat, node_id):
|
||||
def mktx_for_open_channel(self, *, funding_sat, node_id, get_coins=None):
|
||||
def make_tx(fee_policy, *, confirmed_only=False, base_tx=None):
|
||||
assert base_tx is None
|
||||
coins = get_coins() if get_coins else self.get_coins(nonlocal_only=True, confirmed_only=confirmed_only)
|
||||
return self.wallet.lnworker.mktx_for_open_channel(
|
||||
coins=self.get_coins(nonlocal_only=True, confirmed_only=confirmed_only),
|
||||
coins=coins,
|
||||
funding_sat=funding_sat,
|
||||
node_id=node_id,
|
||||
fee_policy=fee_policy)
|
||||
return make_tx
|
||||
|
||||
def open_channel(self, connect_str, funding_sat, push_amt):
|
||||
def open_channel(self, connect_str, funding_sat, *, push_amt=0, get_coins=None):
|
||||
try:
|
||||
node_id, rest = extract_nodeid(connect_str)
|
||||
except ConnStringFormatError as e:
|
||||
@@ -1505,7 +1509,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
|
||||
if not self.question(msg):
|
||||
return
|
||||
# we need to know the fee before we broadcast, because the txid is required
|
||||
make_tx = self.mktx_for_open_channel(funding_sat=funding_sat, node_id=node_id)
|
||||
make_tx = self.mktx_for_open_channel(funding_sat=funding_sat, node_id=node_id, get_coins=get_coins)
|
||||
funding_tx, _, _ = self.confirm_tx_dialog(make_tx, funding_sat, context=TxEditorContext.CHANNEL_FUNDING)
|
||||
if not funding_tx:
|
||||
return
|
||||
@@ -2027,7 +2031,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
|
||||
msg = _('Do you want to create your first channel?') + '\n\n' + messages.MSG_LIGHTNING_WARNING
|
||||
if not self.question(msg):
|
||||
return
|
||||
d = NewChannelDialog(self, amount_sat, min_amount_sat)
|
||||
d = NewChannelDialog(self, amount_sat=amount_sat, min_amount_sat=min_amount_sat)
|
||||
return d.run()
|
||||
|
||||
def new_contact_dialog(self):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
from typing import TYPE_CHECKING, Optional, Callable, Sequence
|
||||
from PyQt6.QtWidgets import QLabel, QVBoxLayout, QGridLayout, QPushButton, QComboBox, QLineEdit, QHBoxLayout
|
||||
|
||||
import electrum_ecc as ecc
|
||||
@@ -16,12 +16,20 @@ from .amountedit import BTCAmountEdit
|
||||
from .my_treeview import create_toolbar_with_menu
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from electrum.transaction import PartialTxInput
|
||||
from .main_window import ElectrumWindow
|
||||
|
||||
|
||||
class NewChannelDialog(WindowModalDialog):
|
||||
|
||||
def __init__(self, window: 'ElectrumWindow', amount_sat: Optional[int] = None, min_amount_sat: Optional[int] = None):
|
||||
def __init__(
|
||||
self,
|
||||
window: 'ElectrumWindow',
|
||||
*,
|
||||
amount_sat: Optional[int] = None,
|
||||
min_amount_sat: Optional[int] = None,
|
||||
get_coins: Optional[Callable[..., Sequence['PartialTxInput']]] = None,
|
||||
):
|
||||
WindowModalDialog.__init__(self, window, _('Open Channel'))
|
||||
self.window = window
|
||||
self.network = window.network
|
||||
@@ -30,6 +38,7 @@ class NewChannelDialog(WindowModalDialog):
|
||||
self.trampolines = hardcoded_trampoline_nodes()
|
||||
self.trampoline_names = list(self.trampolines.keys())
|
||||
self.min_amount_sat = min_amount_sat or MIN_FUNDING_SAT
|
||||
self.get_coins = get_coins
|
||||
vbox = QVBoxLayout(self)
|
||||
toolbar, menu = create_toolbar_with_menu(self.config, '')
|
||||
menu.addConfig(
|
||||
@@ -141,7 +150,7 @@ class NewChannelDialog(WindowModalDialog):
|
||||
if not self.max_button.isChecked():
|
||||
return
|
||||
dummy_nodeid = ecc.GENERATOR.get_public_key_bytes(compressed=True)
|
||||
make_tx = self.window.mktx_for_open_channel(funding_sat='!', node_id=dummy_nodeid)
|
||||
make_tx = self.window.mktx_for_open_channel(funding_sat='!', node_id=dummy_nodeid, get_coins=self.get_coins)
|
||||
try:
|
||||
tx = make_tx(FeePolicy(self.config.FEE_POLICY))
|
||||
except (NotEnoughFunds, NoDynamicFeeEstimates) as e:
|
||||
@@ -176,5 +185,5 @@ class NewChannelDialog(WindowModalDialog):
|
||||
connect_str = str(self.trampolines[name])
|
||||
if not connect_str:
|
||||
return
|
||||
self.window.open_channel(connect_str, funding_sat, 0)
|
||||
self.window.open_channel(connect_str, funding_sat, get_coins=self.get_coins)
|
||||
return True
|
||||
|
||||
@@ -11,7 +11,7 @@ from electrum_aionostr.util import from_nip19
|
||||
from electrum.i18n import _
|
||||
from electrum.util import NotEnoughFunds, NoDynamicFeeEstimates, UserCancelled, trigger_callback
|
||||
from electrum.bitcoin import DummyAddress
|
||||
from electrum.transaction import PartialTxOutput, PartialTransaction
|
||||
from electrum.transaction import PartialTxOutput, PartialTransaction, PartialTxInput
|
||||
from electrum.fee_policy import FeePolicy
|
||||
from electrum.submarine_swaps import NostrTransport
|
||||
|
||||
@@ -92,16 +92,16 @@ class SwapProvidersButton(QPushButton):
|
||||
trigger_callback('swap_provider_changed')
|
||||
|
||||
|
||||
|
||||
class SwapDialog(WindowModalDialog, QtEventListener):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
window: 'ElectrumWindow',
|
||||
transport: 'SwapServerTransport',
|
||||
*,
|
||||
is_reverse: Optional[bool] = None,
|
||||
recv_amount_sat_or_max: Optional[Union[int, str]] = None, # sat or '!'
|
||||
channels: Optional[Sequence['Channel']] = None,
|
||||
get_coins: Optional[Callable[..., Sequence['PartialTxInput']]] = None,
|
||||
):
|
||||
WindowModalDialog.__init__(self, window, _('Submarine Swap'))
|
||||
self.window = window
|
||||
@@ -111,6 +111,8 @@ class SwapDialog(WindowModalDialog, QtEventListener):
|
||||
self.network = window.network
|
||||
self.channels = channels
|
||||
self.is_reverse = is_reverse if is_reverse is not None else True
|
||||
self.get_coins = get_coins
|
||||
|
||||
vbox = QVBoxLayout(self)
|
||||
|
||||
self.transport = transport
|
||||
@@ -187,6 +189,8 @@ class SwapDialog(WindowModalDialog, QtEventListener):
|
||||
self.timer.timeout.connect(self.timer_actions)
|
||||
self.timer.start()
|
||||
|
||||
self.finished.connect(self.on_finished)
|
||||
|
||||
self.fee_slider.update()
|
||||
self.register_callbacks()
|
||||
|
||||
@@ -194,6 +198,9 @@ class SwapDialog(WindowModalDialog, QtEventListener):
|
||||
self.unregister_callbacks()
|
||||
event.accept()
|
||||
|
||||
def on_finished(self, *args):
|
||||
self.timer.stop()
|
||||
|
||||
@qt_event_listener
|
||||
def on_event_fee_histogram(self, *args):
|
||||
self.update_send_receive()
|
||||
@@ -425,7 +432,7 @@ class SwapDialog(WindowModalDialog, QtEventListener):
|
||||
assert not self.is_reverse
|
||||
if onchain_amount is None:
|
||||
raise InvalidSwapParameters("onchain_amount is None")
|
||||
coins = self.window.get_coins()
|
||||
coins = self.get_coins() if self.get_coins else self.window.get_coins()
|
||||
if onchain_amount == '!':
|
||||
max_amount = sum(c.value_sats() for c in coins)
|
||||
max_swap_amount = self.swap_manager.client_max_amount_forward_swap()
|
||||
|
||||
@@ -260,10 +260,7 @@ class UTXOList(MyTreeView):
|
||||
|
||||
def swap_coins(self, coins: list[PartialTxInput]) -> None:
|
||||
assert coins, "no coins selected?"
|
||||
#self.clear_coincontrol()
|
||||
self.add_to_coincontrol(coins)
|
||||
self.main_window.run_swap_dialog(is_reverse=False, recv_amount_sat_or_max='!')
|
||||
self.clear_coincontrol()
|
||||
self.main_window.run_swap_dialog(is_reverse=False, recv_amount_sat_or_max='!', get_coins=lambda *args, **kwargs: coins)
|
||||
|
||||
def can_open_channel(self, coins):
|
||||
if self.wallet.lnworker is None:
|
||||
@@ -274,9 +271,7 @@ class UTXOList(MyTreeView):
|
||||
def open_channel_with_coins(self, coins: list[PartialTxInput]) -> None:
|
||||
assert coins, "no coins selected?"
|
||||
# todo : use a single dialog in new flow
|
||||
#self.clear_coincontrol()
|
||||
self.add_to_coincontrol(coins)
|
||||
d = NewChannelDialog(self.main_window)
|
||||
d = NewChannelDialog(self.main_window, get_coins=lambda *args, **kwargs: coins)
|
||||
d.max_button.setChecked(True)
|
||||
d.max_button.setEnabled(False)
|
||||
d.min_button.setEnabled(False)
|
||||
@@ -284,7 +279,6 @@ class UTXOList(MyTreeView):
|
||||
d.amount_e.setFrozen(True)
|
||||
d.spend_max()
|
||||
d.run()
|
||||
self.clear_coincontrol()
|
||||
|
||||
def clipboard_contains_address(self) -> bool:
|
||||
text = self.main_window.app.clipboard().text()
|
||||
@@ -297,10 +291,8 @@ class UTXOList(MyTreeView):
|
||||
return
|
||||
addr = self.main_window.app.clipboard().text()
|
||||
outputs = [PartialTxOutput.from_address_and_value(addr, '!')]
|
||||
#self.clear_coincontrol()
|
||||
self.add_to_coincontrol(coins)
|
||||
self.main_window.send_tab.pay_onchain_dialog(outputs)
|
||||
self.clear_coincontrol()
|
||||
|
||||
self.main_window.send_tab.pay_onchain_dialog(outputs, get_coins=lambda *args, **kwargs: coins)
|
||||
|
||||
def on_double_click(self, idx):
|
||||
outpoint = idx.sibling(idx.row(), self.Columns.OUTPOINT).data(self.ROLE_PREVOUT_STR)
|
||||
|
||||
Reference in New Issue
Block a user