77 QModelIndex )
88from PyQt6 .QtGui import QColor
99
10+ from electrum .gui .qml .qetxfinalizer import QETxFinalizer
1011from electrum .i18n import _
1112from electrum .bitcoin import DummyAddress
1213from electrum .logging import get_logger
@@ -158,10 +159,12 @@ def __init__(self, parent=None):
158159 super ().__init__ (parent )
159160
160161 self ._wallet = None # type: Optional[QEWallet]
162+ self ._finalizer = None # type: Optional[QETxFinalizer]
161163 self ._sliderPos = 0
162164 self ._rangeMin = - 1
163165 self ._rangeMax = 1
164- self ._tx = None
166+ self ._tx = None # updated on feeslider move and fee histogram updates, used for estimation
167+ self ._finalized_tx = None # updated by finalizer, used for actual forward swap
165168 self ._valid = False
166169 self ._state = QESwapHelper .State .Initialized
167170 self ._userinfo = QESwapHelper .MESSAGE_SWAP_HOWTO
@@ -215,6 +218,11 @@ def wallet(self, wallet: QEWallet):
215218 self .run_swap_manager ()
216219 self .walletChanged .emit ()
217220
221+ finalizerChanged = pyqtSignal ()
222+ @pyqtProperty (QETxFinalizer , notify = finalizerChanged )
223+ def finalizer (self ):
224+ return self ._finalizer
225+
218226 sliderPosChanged = pyqtSignal ()
219227 @pyqtProperty (float , notify = sliderPosChanged )
220228 def sliderPos (self ):
@@ -547,24 +555,26 @@ def initSwapSliderRange(self):
547555 self .swap_slider_moved ()
548556
549557 @profiler
550- def update_tx (self , onchain_amount : Union [int , str ]):
558+ def update_tx (self , onchain_amount : Union [int , str ], fee_policy : Optional [ FeePolicy ] = None ):
551559 """Updates the transaction associated with a forward swap."""
552560 if onchain_amount is None :
553561 self ._tx = None
554562 self .valid = False
555563 return
556- outputs = [PartialTxOutput .from_address_and_value (DummyAddress .SWAP , onchain_amount )]
557- coins = self ._wallet .wallet .get_spendable_coins (None )
558- fee_policy = FeePolicy ('eta:2' )
559564 try :
560- self ._tx = self ._wallet .wallet .make_unsigned_transaction (
561- coins = coins ,
562- outputs = outputs ,
563- fee_policy = fee_policy )
565+ self ._tx = self ._create_swap_tx (onchain_amount , fee_policy )
564566 except (NotEnoughFunds , NoDynamicFeeEstimates ):
565567 self ._tx = None
566568 self .valid = False
567569
570+ def _create_swap_tx (self , onchain_amount : int | str , fee_policy : Optional [FeePolicy ] = None ):
571+ outputs = [PartialTxOutput .from_address_and_value (DummyAddress .SWAP , onchain_amount )]
572+ coins = self ._wallet .wallet .get_spendable_coins (None )
573+ fee_policy = fee_policy if fee_policy else FeePolicy ('eta:2' )
574+ return self ._wallet .wallet .make_unsigned_transaction (
575+ coins = coins , outputs = outputs , fee_policy = fee_policy
576+ )
577+
568578 @qt_event_listener
569579 def on_event_fee_histogram (self , * args ):
570580 self .swap_slider_moved ()
@@ -623,13 +633,15 @@ def fwd_swap_updatetx(self):
623633
624634 def do_normal_swap (self , lightning_amount , onchain_amount ):
625635 assert self ._tx
636+ assert self ._finalized_tx
626637 if lightning_amount is None or onchain_amount is None :
627638 return
628639
640+ assert self ._finalized_tx .get_dummy_output (DummyAddress .SWAP ).value == onchain_amount
641+
629642 async def swap_task ():
630643 assert self .swap_transport is not None , "Swap transport not available"
631644 try :
632- dummy_tx = self ._create_tx (onchain_amount )
633645 self .userinfo = _ ('Performing swap...' )
634646 self .state = QESwapHelper .State .Started
635647 self ._swap , invoice = await self ._wallet .wallet .lnworker .swap_manager .request_normal_swap (
@@ -638,10 +650,11 @@ async def swap_task():
638650 expected_onchain_amount_sat = onchain_amount ,
639651 )
640652
641- tx = self ._wallet .wallet .lnworker .swap_manager .create_funding_tx (self ._swap , dummy_tx , password = self ._wallet .password )
642- coro2 = self ._wallet .wallet .lnworker .swap_manager .wait_for_htlcs_and_broadcast (
653+ tx = self ._wallet .wallet .lnworker .swap_manager .create_funding_tx (
654+ self ._swap , self ._finalized_tx , password = self ._wallet .password )
655+ coro = self ._wallet .wallet .lnworker .swap_manager .wait_for_htlcs_and_broadcast (
643656 transport = self .swap_transport , swap = self ._swap , invoice = invoice , tx = tx )
644- self ._fut_htlc_wait = fut = asyncio .create_task (coro2 )
657+ self ._fut_htlc_wait = fut = asyncio .create_task (coro )
645658
646659 self .canCancel = True
647660 txid = await fut
@@ -675,32 +688,20 @@ async def swap_task():
675688
676689 asyncio .run_coroutine_threadsafe (swap_task (), get_asyncio_loop ())
677690
678- def _create_tx (self , onchain_amount : Union [int , str , None ]) -> PartialTransaction :
679- # TODO: func taken from qt GUI, this should be common code
680- assert not self .isReverse
681- if onchain_amount is None :
682- raise InvalidSwapParameters ("onchain_amount is None" )
683- # coins = self.window.get_coins()
684- coins = self ._wallet .wallet .get_spendable_coins ()
685- if onchain_amount == '!' :
686- max_amount = sum (c .value_sats () for c in coins )
687- max_swap_amount = self ._wallet .wallet .lnworker .swap_manager .client_max_amount_forward_swap ()
688- if max_swap_amount is None :
689- raise InvalidSwapParameters ("swap_manager.client_max_amount_forward_swap() is None" )
690- if max_amount > max_swap_amount :
691- onchain_amount = max_swap_amount
692- outputs = [PartialTxOutput .from_address_and_value (DummyAddress .SWAP , onchain_amount )]
693- fee_policy = FeePolicy ('eta:2' )
694- try :
695- tx = self ._wallet .wallet .make_unsigned_transaction (
696- coins = coins ,
697- outputs = outputs ,
698- send_change_to_lightning = False ,
699- fee_policy = fee_policy
700- )
701- except (NotEnoughFunds , NoDynamicFeeEstimates ) as e :
702- raise InvalidSwapParameters (str (e )) from e
703- return tx
691+ @pyqtSlot ()
692+ def prepNormalSwap (self ):
693+ def mktx (amt , fee_policy : FeePolicy ):
694+ try :
695+ self ._finalized_tx = self ._create_swap_tx (amt , fee_policy )
696+ except (NotEnoughFunds , NoDynamicFeeEstimates ):
697+ self ._finalized_tx = None
698+ return self ._finalized_tx
699+
700+ self ._finalizer = QETxFinalizer (self , make_tx = mktx )
701+ self ._finalizer .canRbf = False
702+ self ._finalizer .amount = QEAmount (amount_sat = self ._send_amount )
703+ self ._finalizer .wallet = self ._wallet
704+ self .finalizerChanged .emit ()
704705
705706 def do_reverse_swap (self , lightning_amount , onchain_amount ):
706707 if lightning_amount is None or onchain_amount is None :
0 commit comments