URI: 
       tPass make_tx function to ConfirmTxDialog - allow 'spend max' when opening a channel (fixes #5698) - display amount minus fee when 'max' buttons are pressed - estimate fee of channel funding using a template with dummy address - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 78813dcb7de97358f395c3a937f8312223ebc91c
   DIR parent 970bd4e95f4a9a6c27850d35d93af699eed5e06c
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Wed, 13 Nov 2019 09:20:19 +0100
       
       Pass make_tx function to ConfirmTxDialog
        - allow 'spend max' when opening a channel (fixes #5698)
        - display amount minus fee when 'max' buttons are pressed
        - estimate fee of channel funding using a template with dummy address
       
       Diffstat:
         M electrum/commands.py                |      11 ++++++++---
         M electrum/gui/qt/channels_list.py    |      47 +++++++++++++++++++------------
         M electrum/gui/qt/confirm_tx_dialog.… |      30 +++++++++---------------------
         M electrum/gui/qt/main_window.py      |      61 ++++++++++++++++++++++---------
         M electrum/gui/qt/transaction_dialog… |       4 ++--
         M electrum/lnpeer.py                  |      17 +++++++++--------
         M electrum/lnutil.py                  |       5 +++++
         M electrum/lnworker.py                |      23 +++++++++++++++++++----
         M electrum/tests/regtest/regtest.sh   |       4 ++--
         M electrum/transaction.py             |       8 ++++++++
         M electrum/wallet.py                  |       4 ++++
       
       11 files changed, 138 insertions(+), 76 deletions(-)
       ---
   DIR diff --git a/electrum/commands.py b/electrum/commands.py
       t@@ -52,6 +52,7 @@ from .wallet import Abstract_Wallet, create_new_wallet, restore_wallet_from_text
        from .address_synchronizer import TX_HEIGHT_LOCAL
        from .mnemonic import Mnemonic
        from .lnutil import SENT, RECEIVED
       +from .lnutil import ln_dummy_address
        from .lnpeer import channel_id_from_funding_tx
        from .plugin import run_hook
        from .version import ELECTRUM_VERSION
       t@@ -922,8 +923,12 @@ class Commands:
                return True
        
            @command('wpn')
       -    async def open_channel(self, connection_string, amount, channel_push=0, password=None, wallet: Abstract_Wallet = None):
       -        chan = await wallet.lnworker._open_channel_coroutine(connection_string, satoshis(amount), satoshis(channel_push), password)
       +    async def open_channel(self, connection_string, amount, push_amount=0, password=None, wallet: Abstract_Wallet = None):
       +        funding_sat = satoshis(amount)
       +        push_sat = satoshis(push_amount)
       +        dummy_output = PartialTxOutput.from_address_and_value(ln_dummy_address(), funding_sat)
       +        funding_tx = wallet.mktx(outputs = [dummy_output], rbf=False, sign=False, nonlocal_only=True)
       +        chan = await wallet.lnworker._open_channel_coroutine(connection_string, funding_tx, funding_sat, push_sat, password)
                return chan.funding_outpoint.to_str()
        
            @command('wn')
       t@@ -1037,7 +1042,7 @@ command_options = {
            'timeout':     (None, "Timeout in seconds"),
            'force':       (None, "Create new address beyond gap limit, if no more addresses are available."),
            'pending':     (None, "Show only pending requests."),
       -    'channel_push':(None, 'Push initial amount (in BTC)'),
       +    'push_amount': (None, 'Push initial amount (in BTC)'),
            'expired':     (None, "Show only expired requests."),
            'paid':        (None, "Show only paid requests."),
            'show_addresses': (None, "Show input and output addresses"),
   DIR diff --git a/electrum/gui/qt/channels_list.py b/electrum/gui/qt/channels_list.py
       t@@ -5,15 +5,15 @@ from enum import IntEnum
        
        from PyQt5 import QtCore, QtWidgets, QtGui
        from PyQt5.QtCore import Qt
       -from PyQt5.QtWidgets import QMenu, QHBoxLayout, QLabel, QVBoxLayout, QGridLayout, QLineEdit
       +from PyQt5.QtWidgets import QMenu, QHBoxLayout, QLabel, QVBoxLayout, QGridLayout, QLineEdit, QPushButton
        
        from electrum.util import inv_dict, bh2u, bfh
        from electrum.i18n import _
        from electrum.lnchannel import Channel
        from electrum.wallet import Abstract_Wallet
       -from electrum.lnutil import LOCAL, REMOTE, ConnStringFormatError, format_short_channel_id
       +from electrum.lnutil import LOCAL, REMOTE, ConnStringFormatError, format_short_channel_id, LN_MAX_FUNDING_SAT
        
       -from .util import MyTreeView, WindowModalDialog, Buttons, OkButton, CancelButton, EnterButton, WWLabel, WaitingDialog
       +from .util import MyTreeView, WindowModalDialog, Buttons, OkButton, CancelButton, EnterButton, WWLabel, WaitingDialog, HelpLabel
        from .amountedit import BTCAmountEdit
        from .channel_details import ChannelDetailsDialog
        
       t@@ -162,26 +162,38 @@ class ChannelsList(MyTreeView):
            def new_channel_dialog(self):
                lnworker = self.parent.wallet.lnworker
                d = WindowModalDialog(self.parent, _('Open Channel'))
       -        d.setMinimumWidth(700)
                vbox = QVBoxLayout(d)
       -        h = QGridLayout()
       +        vbox.addWidget(QLabel(_('Enter Remote Node ID or connection string or invoice')))
                local_nodeid = QLineEdit()
       +        local_nodeid.setMinimumWidth(700)
                local_nodeid.setText(bh2u(lnworker.node_keypair.pubkey))
                local_nodeid.setReadOnly(True)
                local_nodeid.setCursorPosition(0)
                remote_nodeid = QLineEdit()
       -        local_amt_inp = BTCAmountEdit(self.parent.get_decimal_point)
       -        local_amt_inp.setAmount(200000)
       -        push_amt_inp = BTCAmountEdit(self.parent.get_decimal_point)
       -        push_amt_inp.setAmount(0)
       +        remote_nodeid.setMinimumWidth(700)
       +        amount_e = BTCAmountEdit(self.parent.get_decimal_point)
       +        # max button
       +        def spend_max():
       +            make_tx = self.parent.mktx_for_open_channel('!')
       +            tx = make_tx(None)
       +            amount = tx.output_value()
       +            amount = min(amount, LN_MAX_FUNDING_SAT)
       +            amount_e.setAmount(amount)
       +            amount_e.setFrozen(True)
       +        max_button = EnterButton(_("Max"), spend_max)
       +        max_button.setFixedWidth(100)
       +        max_button.setCheckable(True)
       +        h = QGridLayout()
                h.addWidget(QLabel(_('Your Node ID')), 0, 0)
                h.addWidget(local_nodeid, 0, 1)
       -        h.addWidget(QLabel(_('Remote Node ID or connection string or invoice')), 1, 0)
       +        h.addWidget(QLabel(_('Remote Node ID')), 1, 0)
                h.addWidget(remote_nodeid, 1, 1)
       -        h.addWidget(QLabel('Local amount'), 2, 0)
       -        h.addWidget(local_amt_inp, 2, 1)
       -        h.addWidget(QLabel('Push amount'), 3, 0)
       -        h.addWidget(push_amt_inp, 3, 1)
       +        h.addWidget(QLabel('Amount'), 2, 0)
       +        hbox = QHBoxLayout()
       +        hbox.addWidget(amount_e)
       +        hbox.addWidget(max_button)
       +        hbox.addStretch(1)
       +        h.addLayout(hbox, 2, 1)
                vbox.addLayout(h)
                ok_button = OkButton(d)
                ok_button.setDefault(True)
       t@@ -191,7 +203,6 @@ class ChannelsList(MyTreeView):
                remote_nodeid.setCursorPosition(0)
                if not d.exec_():
                    return
       -        local_amt = local_amt_inp.get_amount()
       -        push_amt = push_amt_inp.get_amount()
       -        connect_contents = str(remote_nodeid.text()).strip()
       -        self.parent.open_channel(connect_contents, local_amt, push_amt)
       +        funding_sat = '!' if max_button.isChecked() else amount_e.get_amount()
       +        connect_str = str(remote_nodeid.text()).strip()
       +        self.parent.open_channel(connect_str, funding_sat, 0)
   DIR diff --git a/electrum/gui/qt/confirm_tx_dialog.py b/electrum/gui/qt/confirm_tx_dialog.py
       t@@ -51,18 +51,17 @@ if TYPE_CHECKING:
        
        class TxEditor:
        
       -    def __init__(self, window: 'ElectrumWindow', inputs, outputs, external_keypairs):
       +    def __init__(self, window: 'ElectrumWindow', make_tx, output_value, is_sweep):
                self.main_window = window
       -        self.outputs = outputs
       -        self.get_coins = inputs
       +        self.make_tx = make_tx
       +        self.output_value = output_value
                self.tx = None  # type: Optional[Transaction]
                self.config = window.config
                self.wallet = window.wallet
       -        self.external_keypairs = external_keypairs
                self.not_enough_funds = False
                self.no_dynfee_estimates = False
                self.needs_update = False
       -        self.password_required = self.wallet.has_keystore_encryption() and not external_keypairs
       +        self.password_required = self.wallet.has_keystore_encryption() and not is_sweep
                self.main_window.gui_object.timer.timeout.connect(self.timer_actions)
        
            def timer_actions(self):
       t@@ -86,17 +85,8 @@ class TxEditor:
        
            def update_tx(self):
                fee_estimator = self.get_fee_estimator()
       -        is_sweep = bool(self.external_keypairs)
       -        coins = self.get_coins()
       -        # deepcopy outputs because '!' is converted to number
       -        outputs = copy.deepcopy(self.outputs)
       -        make_tx = lambda fee_est: self.wallet.make_unsigned_transaction(
       -            coins=coins,
       -            outputs=outputs,
       -            fee=fee_est,
       -            is_sweep=is_sweep)
                try:
       -            self.tx = make_tx(fee_estimator)
       +            self.tx = self.make_tx(fee_estimator)
                    self.not_enough_funds = False
                    self.no_dynfee_estimates = False
                except NotEnoughFunds:
       t@@ -107,7 +97,7 @@ class TxEditor:
                    self.no_dynfee_estimates = True
                    self.tx = None
                    try:
       -                self.tx = make_tx(0)
       +                self.tx = self.make_tx(0)
                    except BaseException:
                        return
                except InternalAddressCorruption as e:
       t@@ -131,9 +121,9 @@ class TxEditor:
        class ConfirmTxDialog(TxEditor, WindowModalDialog):
            # set fee and return password (after pw check)
        
       -    def __init__(self, window: 'ElectrumWindow', inputs, outputs, external_keypairs):
       +    def __init__(self, window: 'ElectrumWindow', make_tx, output_value, is_sweep):
        
       -        TxEditor.__init__(self, window, inputs, outputs, external_keypairs)
       +        TxEditor.__init__(self, window, make_tx, output_value, is_sweep)
                WindowModalDialog.__init__(self, window, _("Confirm Transaction"))
                vbox = QVBoxLayout()
                self.setLayout(vbox)
       t@@ -218,9 +208,7 @@ class ConfirmTxDialog(TxEditor, WindowModalDialog):
        
            def update(self):
                tx = self.tx
       -        output_values = [x.value for x in self.outputs]
       -        is_max = '!' in output_values
       -        amount = tx.output_value() if is_max else sum(output_values)
       +        amount = tx.output_value() if self.output_value == '!' else self.output_value
                self.amount_label.setText(self.main_window.format_amount_and_units(amount))
        
                if self.not_enough_funds:
   DIR diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py
       t@@ -76,6 +76,7 @@ from electrum.simple_config import SimpleConfig
        from electrum.logging import Logger
        from electrum.util import PR_PAID, PR_UNPAID, PR_INFLIGHT, PR_FAILED
        from electrum.util import pr_expiration_values
       +from electrum.lnutil import ln_dummy_address
        
        from .exception_window import Exception_Hook
        from .amountedit import AmountEdit, BTCAmountEdit, MyLineEdit, FeerateEdit
       t@@ -1282,8 +1283,18 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
            def spend_max(self):
                if run_hook('abort_send', self):
                    return
       +        outputs = self.payto_e.get_outputs(True)
       +        if not outputs:
       +            return
                self.max_button.setChecked(True)
       -        amount = sum(x.value_sats() for x in self.get_coins())
       +        make_tx = lambda fee_est: self.wallet.make_unsigned_transaction(
       +            coins=self.get_coins(),
       +            outputs=outputs,
       +            fee=fee_est,
       +            is_sweep=False)
       +
       +        tx = make_tx(None)
       +        amount = tx.output_value()#sum(x.value_sats() for x in self.get_coins())
                self.amount_e.setAmount(amount)
                ## substract extra fee
                #__, x_fee_amount = run_hook('get_tx_extra_fee', self.wallet, tx) or (None, 0)
       t@@ -1448,20 +1459,20 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
                outputs = []
                for invoice in invoices:
                    outputs += invoice['outputs']
       -        self.pay_onchain_dialog(self.get_coins, outputs)
       +        self.pay_onchain_dialog(self.get_coins(), outputs)
        
            def do_pay_invoice(self, invoice):
                if invoice['type'] == PR_TYPE_LN:
                    self.pay_lightning_invoice(invoice['invoice'])
                elif invoice['type'] == PR_TYPE_ONCHAIN:
                    outputs = invoice['outputs']
       -            self.pay_onchain_dialog(self.get_coins, outputs, invoice=invoice)
       +            self.pay_onchain_dialog(self.get_coins(), outputs, invoice=invoice)
                else:
                    raise Exception('unknown invoice type')
        
       -    def get_coins(self):
       +    def get_coins(self, nonlocal_only=False):
                coins = self.get_manually_selected_coins()
       -        return coins or self.wallet.get_spendable_coins(None)
       +        return coins or self.wallet.get_spendable_coins(None, nonlocal_only=nonlocal_only)
        
            def get_manually_selected_coins(self) -> Sequence[PartialTxInput]:
                return self.utxo_list.get_spend_list()
       t@@ -1470,10 +1481,19 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
                # trustedcoin requires this
                if run_hook('abort_send', self):
                    return
       +        is_sweep = bool(external_keypairs)
       +        make_tx = lambda fee_est: self.wallet.make_unsigned_transaction(
       +            coins=inputs,
       +            outputs=outputs,
       +            fee=fee_est,
       +            is_sweep=is_sweep)
                if self.config.get('advanced_preview'):
       -            self.preview_tx_dialog(inputs, outputs, invoice=invoice)
       +            self.preview_tx_dialog(make_tx, outputs, is_sweep=is_sweep, invoice=invoice)
                    return
       -        d = ConfirmTxDialog(self, inputs, outputs, external_keypairs)
       +
       +        output_values = [x.value for x in outputs]
       +        output_value = '!' if '!' in output_values else sum(output_values)
       +        d = ConfirmTxDialog(self, make_tx, output_value, is_sweep)
                d.update_tx()
                if d.not_enough_funds:
                    self.show_message(_('Not Enough Funds'))
       t@@ -1487,10 +1507,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
                            self.broadcast_or_show(tx, invoice=invoice)
                    self.sign_tx_with_password(tx, sign_done, password, external_keypairs)
                else:
       -            self.preview_tx_dialog(inputs, outputs, external_keypairs=external_keypairs, invoice=invoice)
       +            self.preview_tx_dialog(make_tx, outputs, is_sweep=is_sweep, invoice=invoice)
        
       -    def preview_tx_dialog(self, inputs, outputs, external_keypairs=None, invoice=None):
       -        d = PreviewTxDialog(inputs, outputs, external_keypairs, window=self, invoice=invoice)
       +    def preview_tx_dialog(self, make_tx, outputs, is_sweep=False, invoice=None):
       +        d = PreviewTxDialog(make_tx, outputs, is_sweep, window=self, invoice=invoice)
                d.show()
        
            def broadcast_or_show(self, tx, invoice=None):
       t@@ -1572,21 +1592,26 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
                WaitingDialog(self, _('Broadcasting transaction...'),
                              broadcast_thread, broadcast_done, self.on_error)
        
       -    def open_channel(self, connect_str, local_amt, push_amt):
       +    def mktx_for_open_channel(self, funding_sat):
       +        coins = self.get_coins(nonlocal_only=True)
       +        make_tx = partial(self.wallet.lnworker.mktx_for_open_channel, coins, funding_sat)
       +        return make_tx
       +
       +    def open_channel(self, connect_str, funding_sat, push_amt):
                # use ConfirmTxDialog
                # we need to know the fee before we broadcast, because the txid is required
                # however, the user must not be allowed to broadcast early
       -        funding_sat = local_amt + push_amt
       -        inputs = self.get_coins
       -        outputs = [PartialTxOutput.from_address_and_value(self.wallet.dummy_address(), funding_sat)]
       -        d = ConfirmTxDialog(self, inputs, outputs, None)
       -        cancelled, is_send, password, tx = d.run()
       +        make_tx = self.mktx_for_open_channel(funding_sat)
       +        d = ConfirmTxDialog(self, make_tx, funding_sat, False)
       +        cancelled, is_send, password, funding_tx = d.run()
                if not is_send:
                    return
                if cancelled:
                    return
       +        # read funding_sat from tx; converts '!' to int value
       +        funding_sat = funding_tx.output_value_for_address(ln_dummy_address())
                def task():
       -            return self.wallet.lnworker.open_channel(connect_str, local_amt, push_amt, password)
       +            return self.wallet.lnworker.open_channel(connect_str, funding_tx, funding_sat, push_amt, password)
                def on_success(chan):
                    n = chan.constraints.funding_txn_minimum_depth
                    message = '\n'.join([
       t@@ -2647,7 +2672,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
                scriptpubkey = bfh(bitcoin.address_to_script(addr))
                outputs = [PartialTxOutput(scriptpubkey=scriptpubkey, value='!')]
                self.warn_if_watching_only()
       -        self.pay_onchain_dialog(lambda: coins, outputs, invoice=None, external_keypairs=keypairs)
       +        self.pay_onchain_dialog(coins, outputs, invoice=None, external_keypairs=keypairs)
        
            def _do_import(self, title, header_layout, func):
                text = text_dialog(self, title, header_layout, _('Import'), allow_multi=True)
   DIR diff --git a/electrum/gui/qt/transaction_dialog.py b/electrum/gui/qt/transaction_dialog.py
       t@@ -602,8 +602,8 @@ class TxDialog(BaseTxDialog):
        
        class PreviewTxDialog(BaseTxDialog, TxEditor):
        
       -    def __init__(self, inputs, outputs, external_keypairs, *, window: 'ElectrumWindow', invoice):
       -        TxEditor.__init__(self, window, inputs, outputs, external_keypairs)
       +    def __init__(self, make_tx, outputs, is_sweep, *, window: 'ElectrumWindow', invoice):
       +        TxEditor.__init__(self, window, make_tx, outputs, is_sweep)
                BaseTxDialog.__init__(self, parent=window, invoice=invoice, desc='', prompt_if_unsaved=False, finalized=False)
                self.update_tx()
                self.update()
   DIR diff --git a/electrum/lnpeer.py b/electrum/lnpeer.py
       t@@ -46,10 +46,12 @@ from .lntransport import LNTransport, LNTransportBase
        from .lnmsg import encode_msg, decode_msg
        from .interface import GracefulDisconnect, NetworkException
        from .lnrouter import fee_for_edge_msat
       +from .lnutil import ln_dummy_address
        
        if TYPE_CHECKING:
            from .lnworker import LNWorker, LNGossip, LNWallet
            from .lnrouter import RouteEdge
       +    from .transaction import PartialTransaction
        
        
        LN_P2P_NETWORK_TIMEOUT = 20
       t@@ -479,12 +481,8 @@ class Peer(Logger):
                return local_config
        
            @log_exceptions
       -    async def channel_establishment_flow(self, password: Optional[str], funding_sat: int,
       +    async def channel_establishment_flow(self, password: Optional[str], funding_tx: 'PartialTransaction', funding_sat: int, 
                                                 push_msat: int, temp_channel_id: bytes) -> Channel:
       -        wallet = self.lnworker.wallet
       -        # dry run creating funding tx to see if we even have enough funds
       -        funding_tx_test = wallet.mktx(outputs=[PartialTxOutput.from_address_and_value(wallet.dummy_address(), funding_sat)],
       -                                      password=password, nonlocal_only=True)
                await asyncio.wait_for(self.initialized.wait(), LN_P2P_NETWORK_TIMEOUT)
                feerate = self.lnworker.current_feerate_per_kw()
                local_config = self.make_local_config(funding_sat, push_msat, LOCAL)
       t@@ -555,16 +553,19 @@ class Peer(Logger):
                    initial_msat=push_msat,
                    reserve_sat = remote_reserve_sat,
                    htlc_minimum_msat = htlc_min,
       -
                    next_per_commitment_point=remote_per_commitment_point,
                    current_per_commitment_point=None,
                    revocation_store=their_revocation_store,
                )
       -        # create funding tx
       +        # replace dummy output in funding tx
                redeem_script = funding_output_script(local_config, remote_config)
                funding_address = bitcoin.redeem_script_to_address('p2wsh', redeem_script)
                funding_output = PartialTxOutput.from_address_and_value(funding_address, funding_sat)
       -        funding_tx = wallet.mktx(outputs=[funding_output], password=password, nonlocal_only=True)
       +        dummy_output = PartialTxOutput.from_address_and_value(ln_dummy_address(), funding_sat)
       +        funding_tx.outputs().remove(dummy_output)
       +        funding_tx.add_outputs([funding_output])
       +        funding_tx.set_rbf(False)
       +        self.lnworker.wallet.sign_transaction(funding_tx, password)
                funding_txid = funding_tx.txid()
                funding_index = funding_tx.outputs().index(funding_output)
                # remote commitment transaction
   DIR diff --git a/electrum/lnutil.py b/electrum/lnutil.py
       t@@ -27,6 +27,11 @@ if TYPE_CHECKING:
        HTLC_TIMEOUT_WEIGHT = 663
        HTLC_SUCCESS_WEIGHT = 703
        
       +LN_MAX_FUNDING_SAT = pow(2, 24)
       +
       +# dummy address for fee estimation of funding tx
       +def ln_dummy_address():
       +    return redeem_script_to_address('p2wsh', '')
        
        class Keypair(NamedTuple):
            pubkey: bytes
   DIR diff --git a/electrum/lnworker.py b/electrum/lnworker.py
       t@@ -24,6 +24,7 @@ from . import keystore
        from .util import profiler
        from .util import PR_UNPAID, PR_EXPIRED, PR_PAID, PR_INFLIGHT, PR_FAILED
        from .util import PR_TYPE_LN
       +from .lnutil import LN_MAX_FUNDING_SAT
        from .keystore import BIP32_KeyStore
        from .bitcoin import COIN
        from .transaction import Transaction
       t@@ -48,6 +49,8 @@ from .lnutil import (Outpoint, LNPeerAddr,
                             NUM_MAX_EDGES_IN_PAYMENT_PATH, SENT, RECEIVED, HTLCOwner,
                             UpdateAddHtlc, Direction, LnLocalFeatures, format_short_channel_id,
                             ShortChannelID)
       +from .lnutil import ln_dummy_address
       +from .transaction import PartialTxOutput
        from .lnonion import OnionFailureCode
        from .lnmsg import decode_msg
        from .i18n import _
       t@@ -768,13 +771,14 @@ class LNWallet(LNWorker):
                            await self.force_close_channel(chan.channel_id)
        
            @log_exceptions
       -    async def _open_channel_coroutine(self, connect_str, local_amount_sat, push_sat, password):
       +    async def _open_channel_coroutine(self, connect_str, funding_tx, funding_sat, push_sat, password):
                peer = await self.add_peer(connect_str)
                # peer might just have been connected to
                await asyncio.wait_for(peer.initialized.wait(), LN_P2P_NETWORK_TIMEOUT)
                chan = await peer.channel_establishment_flow(
                    password,
       -            funding_sat=local_amount_sat + push_sat,
       +            funding_tx=funding_tx,
       +            funding_sat=funding_sat,
                    push_msat=push_sat * 1000,
                    temp_channel_id=os.urandom(32))
                self.save_channel(chan)
       t@@ -805,8 +809,19 @@ class LNWallet(LNWorker):
                    peer = await self._add_peer(host, port, node_id)
                return peer
        
       -    def open_channel(self, connect_str, local_amt_sat, push_amt_sat, password=None, timeout=20):
       -        coro = self._open_channel_coroutine(connect_str, local_amt_sat, push_amt_sat, password)
       +    def mktx_for_open_channel(self, coins, funding_sat, fee_est):
       +        dummy_address = ln_dummy_address()
       +        outputs = [PartialTxOutput.from_address_and_value(dummy_address, funding_sat)]
       +        tx = self.wallet.make_unsigned_transaction(
       +            coins=coins,
       +            outputs=outputs,
       +            fee=fee_est)
       +        tx.set_rbf(False)
       +        return tx
       +
       +    def open_channel(self, connect_str, funding_tx, funding_sat, push_amt_sat, password=None, timeout=20):
       +        assert funding_sat <= LN_MAX_FUNDING_SAT
       +        coro = self._open_channel_coroutine(connect_str, funding_tx, funding_sat, push_amt_sat, password)
                fut = asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop)
                try:
                    chan = fut.result(timeout=timeout)
   DIR diff --git a/electrum/tests/regtest/regtest.sh b/electrum/tests/regtest/regtest.sh
       t@@ -109,8 +109,8 @@ fi
        
        if [[ $1 == "open" ]]; then
            bob_node=$($bob nodeid)
       -    channel_id1=$($alice open_channel $bob_node 0.001 --channel_push 0.001)
       -    channel_id2=$($carol open_channel $bob_node 0.001 --channel_push 0.001)
       +    channel_id1=$($alice open_channel $bob_node 0.002 --push_amount 0.001)
       +    channel_id2=$($carol open_channel $bob_node 0.002 --push_amount 0.001)
            echo "mining 3 blocks"
            new_blocks 3
            sleep 10 # time for channelDB
   DIR diff --git a/electrum/transaction.py b/electrum/transaction.py
       t@@ -879,6 +879,14 @@ class Transaction:
                script = bitcoin.address_to_script(addr)
                return self.get_output_idxs_from_scriptpubkey(script)
        
       +    def output_value_for_address(self, addr):
       +        # assumes exactly one output has that address
       +        for o in self.outputs():
       +            if o.address == addr:
       +                return o.value
       +        else:
       +            raise Exception('output not found', addr)
       +
        
        def convert_raw_tx_to_hex(raw: Union[str, bytes]) -> str:
            """Sanitizes tx-describing input (hex/base43/base64) into
   DIR diff --git a/electrum/wallet.py b/electrum/wallet.py
       t@@ -917,6 +917,10 @@ class Abstract_Wallet(AddressSynchronizer):
            def make_unsigned_transaction(self, *, coins: Sequence[PartialTxInput],
                                          outputs: List[PartialTxOutput], fee=None,
                                          change_addr: str = None, is_sweep=False) -> PartialTransaction:
       +
       +        # prevent side-effect with '!'
       +        outputs = copy.deepcopy(outputs)
       +
                # check outputs
                i_max = None
                for i, o in enumerate(outputs):