66from PyQt6 .QtCore import (pyqtProperty , pyqtSignal , pyqtSlot , QObject , QTimer , pyqtEnum , QAbstractListModel , Qt ,
77 QModelIndex )
88
9+ from electrum .gui .qml .qetxfinalizer import QETxFinalizer
910from electrum .i18n import _
1011from electrum .bitcoin import DummyAddress
1112from electrum .logging import get_logger
@@ -156,10 +157,12 @@ def __init__(self, parent=None):
156157 super ().__init__ (parent )
157158
158159 self ._wallet = None # type: Optional[QEWallet]
160+ self ._finalizer = None # type: Optional[QETxFinalizer]
159161 self ._sliderPos = 0
160162 self ._rangeMin = - 1
161163 self ._rangeMax = 1
162- self ._tx = None
164+ self ._tx = None # updated on feeslider move and fee histogram updates, used for estimation
165+ self ._finalized_tx = None # updated by finalizer, used for actual forward swap
163166 self ._valid = False
164167 self ._state = QESwapHelper .State .Initialized
165168 self ._userinfo = QESwapHelper .MESSAGE_SWAP_HOWTO
@@ -213,6 +216,11 @@ def wallet(self, wallet: QEWallet):
213216 self .run_swap_manager ()
214217 self .walletChanged .emit ()
215218
219+ finalizerChanged = pyqtSignal ()
220+ @pyqtProperty (QETxFinalizer , notify = finalizerChanged )
221+ def finalizer (self ):
222+ return self ._finalizer
223+
216224 sliderPosChanged = pyqtSignal ()
217225 @pyqtProperty (float , notify = sliderPosChanged )
218226 def sliderPos (self ):
@@ -545,24 +553,26 @@ def initSwapSliderRange(self):
545553 self .swap_slider_moved ()
546554
547555 @profiler
548- def update_tx (self , onchain_amount : Union [int , str ]):
556+ def update_tx (self , onchain_amount : Union [int , str ], fee_policy : Optional [ FeePolicy ] = None ):
549557 """Updates the transaction associated with a forward swap."""
550558 if onchain_amount is None :
551559 self ._tx = None
552560 self .valid = False
553561 return
554- outputs = [PartialTxOutput .from_address_and_value (DummyAddress .SWAP , onchain_amount )]
555- coins = self ._wallet .wallet .get_spendable_coins (None )
556- fee_policy = FeePolicy ('eta:2' )
557562 try :
558- self ._tx = self ._wallet .wallet .make_unsigned_transaction (
559- coins = coins ,
560- outputs = outputs ,
561- fee_policy = fee_policy )
563+ self ._tx = self ._create_swap_tx (onchain_amount , fee_policy )
562564 except (NotEnoughFunds , NoDynamicFeeEstimates ):
563565 self ._tx = None
564566 self .valid = False
565567
568+ def _create_swap_tx (self , onchain_amount : int | str , fee_policy : Optional [FeePolicy ] = None ):
569+ outputs = [PartialTxOutput .from_address_and_value (DummyAddress .SWAP , onchain_amount )]
570+ coins = self ._wallet .wallet .get_spendable_coins (None )
571+ fee_policy = fee_policy if fee_policy else FeePolicy ('eta:2' )
572+ return self ._wallet .wallet .make_unsigned_transaction (
573+ coins = coins , outputs = outputs , fee_policy = fee_policy
574+ )
575+
566576 @qt_event_listener
567577 def on_event_fee_histogram (self , * args ):
568578 self .swap_slider_moved ()
@@ -621,13 +631,15 @@ def fwd_swap_updatetx(self):
621631
622632 def do_normal_swap (self , lightning_amount , onchain_amount ):
623633 assert self ._tx
634+ assert self ._finalized_tx
624635 if lightning_amount is None or onchain_amount is None :
625636 return
626637
638+ assert self ._finalized_tx .get_dummy_output (DummyAddress .SWAP ).value == onchain_amount
639+
627640 async def swap_task ():
628641 assert self .swap_transport is not None , "Swap transport not available"
629642 try :
630- dummy_tx = self ._create_tx (onchain_amount )
631643 self .userinfo = _ ('Performing swap...' )
632644 self .state = QESwapHelper .State .Started
633645 self ._swap , invoice = await self ._wallet .wallet .lnworker .swap_manager .request_normal_swap (
@@ -636,10 +648,11 @@ async def swap_task():
636648 expected_onchain_amount_sat = onchain_amount ,
637649 )
638650
639- tx = self ._wallet .wallet .lnworker .swap_manager .create_funding_tx (self ._swap , dummy_tx , password = self ._wallet .password )
640- coro2 = self ._wallet .wallet .lnworker .swap_manager .wait_for_htlcs_and_broadcast (
651+ tx = self ._wallet .wallet .lnworker .swap_manager .create_funding_tx (
652+ self ._swap , self ._finalized_tx , password = self ._wallet .password )
653+ coro = self ._wallet .wallet .lnworker .swap_manager .wait_for_htlcs_and_broadcast (
641654 transport = self .swap_transport , swap = self ._swap , invoice = invoice , tx = tx )
642- self ._fut_htlc_wait = fut = asyncio .create_task (coro2 )
655+ self ._fut_htlc_wait = fut = asyncio .create_task (coro )
643656
644657 self .canCancel = True
645658 txid = await fut
@@ -673,32 +686,20 @@ async def swap_task():
673686
674687 asyncio .run_coroutine_threadsafe (swap_task (), get_asyncio_loop ())
675688
676- def _create_tx (self , onchain_amount : Union [int , str , None ]) -> PartialTransaction :
677- # TODO: func taken from qt GUI, this should be common code
678- assert not self .isReverse
679- if onchain_amount is None :
680- raise InvalidSwapParameters ("onchain_amount is None" )
681- # coins = self.window.get_coins()
682- coins = self ._wallet .wallet .get_spendable_coins ()
683- if onchain_amount == '!' :
684- max_amount = sum (c .value_sats () for c in coins )
685- max_swap_amount = self ._wallet .wallet .lnworker .swap_manager .client_max_amount_forward_swap ()
686- if max_swap_amount is None :
687- raise InvalidSwapParameters ("swap_manager.client_max_amount_forward_swap() is None" )
688- if max_amount > max_swap_amount :
689- onchain_amount = max_swap_amount
690- outputs = [PartialTxOutput .from_address_and_value (DummyAddress .SWAP , onchain_amount )]
691- fee_policy = FeePolicy ('eta:2' )
692- try :
693- tx = self ._wallet .wallet .make_unsigned_transaction (
694- coins = coins ,
695- outputs = outputs ,
696- send_change_to_lightning = False ,
697- fee_policy = fee_policy
698- )
699- except (NotEnoughFunds , NoDynamicFeeEstimates ) as e :
700- raise InvalidSwapParameters (str (e )) from e
701- return tx
689+ @pyqtSlot ()
690+ def prepNormalSwap (self ):
691+ def mktx (amt , fee_policy : FeePolicy ):
692+ try :
693+ self ._finalized_tx = self ._create_swap_tx (amt , fee_policy )
694+ except (NotEnoughFunds , NoDynamicFeeEstimates ):
695+ self ._finalized_tx = None
696+ return self ._finalized_tx
697+
698+ self ._finalizer = QETxFinalizer (self , make_tx = mktx )
699+ self ._finalizer .canRbf = False
700+ self ._finalizer .amount = QEAmount (amount_sat = self ._send_amount )
701+ self ._finalizer .wallet = self ._wallet
702+ self .finalizerChanged .emit ()
702703
703704 def do_reverse_swap (self , lightning_amount , onchain_amount ):
704705 if lightning_amount is None or onchain_amount is None :
0 commit comments