send tx change to lightning
This commit is contained in:
@@ -753,3 +753,7 @@ def is_minikey(text: str) -> bool:
|
|||||||
|
|
||||||
def minikey_to_private_key(text: str) -> bytes:
|
def minikey_to_private_key(text: str) -> bytes:
|
||||||
return sha256(text)
|
return sha256(text)
|
||||||
|
|
||||||
|
# dummy address for fee estimation of funding tx
|
||||||
|
def get_dummy_address(purpose):
|
||||||
|
return redeem_script_to_address('p2wsh', sha256(bytes(purpose, "utf8")).hex())
|
||||||
|
|||||||
@@ -221,6 +221,8 @@ class CoinChooserBase(Logger):
|
|||||||
amounts = [amount for amount in amounts if amount >= dust_threshold]
|
amounts = [amount for amount in amounts if amount >= dust_threshold]
|
||||||
change = [PartialTxOutput.from_address_and_value(addr, amount)
|
change = [PartialTxOutput.from_address_and_value(addr, amount)
|
||||||
for addr, amount in zip(change_addrs, amounts)]
|
for addr, amount in zip(change_addrs, amounts)]
|
||||||
|
for c in change:
|
||||||
|
c.is_change = True
|
||||||
return change
|
return change
|
||||||
|
|
||||||
def _construct_tx_from_selected_buckets(self, *, buckets: Sequence[Bucket],
|
def _construct_tx_from_selected_buckets(self, *, buckets: Sequence[Bucket],
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
|
|||||||
from electrum.i18n import _
|
from electrum.i18n import _
|
||||||
from electrum.gui import messages
|
from electrum.gui import messages
|
||||||
from electrum.util import bfh
|
from electrum.util import bfh
|
||||||
from electrum.lnutil import extract_nodeid, ln_dummy_address, ConnStringFormatError
|
from electrum.lnutil import extract_nodeid, ConnStringFormatError
|
||||||
|
from electrum.bitcoin import get_dummy_address
|
||||||
from electrum.lnworker import hardcoded_trampoline_nodes
|
from electrum.lnworker import hardcoded_trampoline_nodes
|
||||||
from electrum.logging import get_logger
|
from electrum.logging import get_logger
|
||||||
|
|
||||||
@@ -181,7 +182,7 @@ class QEChannelOpener(QObject, AuthMixin):
|
|||||||
"""
|
"""
|
||||||
self._logger.debug('opening channel')
|
self._logger.debug('opening channel')
|
||||||
# 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(get_dummy_address('channel'))
|
||||||
lnworker = self._wallet.wallet.lnworker
|
lnworker = self._wallet.wallet.lnworker
|
||||||
|
|
||||||
def open_thread():
|
def open_thread():
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from typing import Union
|
|||||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QTimer, Q_ENUMS
|
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QTimer, Q_ENUMS
|
||||||
|
|
||||||
from electrum.i18n import _
|
from electrum.i18n import _
|
||||||
from electrum.lnutil import ln_dummy_address
|
from electrum.bitcoin import get_dummy_address
|
||||||
from electrum.logging import get_logger
|
from electrum.logging import get_logger
|
||||||
from electrum.transaction import PartialTxOutput
|
from electrum.transaction import PartialTxOutput
|
||||||
from electrum.util import NotEnoughFunds, NoDynamicFeeEstimates, profiler, get_asyncio_loop
|
from electrum.util import NotEnoughFunds, NoDynamicFeeEstimates, profiler, get_asyncio_loop
|
||||||
@@ -245,7 +245,7 @@ class QESwapHelper(AuthMixin, QObject, QtEventListener):
|
|||||||
# this is just to estimate the maximal spendable onchain amount for HTLC
|
# this is just to estimate the maximal spendable onchain amount for HTLC
|
||||||
self.update_tx('!')
|
self.update_tx('!')
|
||||||
try:
|
try:
|
||||||
max_onchain_spend = self._tx.output_value_for_address(ln_dummy_address())
|
max_onchain_spend = self._tx.output_value_for_address(get_dummy_address('swap'))
|
||||||
except AttributeError: # happens if there are no utxos
|
except AttributeError: # happens if there are no utxos
|
||||||
max_onchain_spend = 0
|
max_onchain_spend = 0
|
||||||
reverse = int(min(lnworker.num_sats_can_send(),
|
reverse = int(min(lnworker.num_sats_can_send(),
|
||||||
@@ -283,7 +283,7 @@ class QESwapHelper(AuthMixin, QObject, QtEventListener):
|
|||||||
self._tx = None
|
self._tx = None
|
||||||
self.valid = False
|
self.valid = False
|
||||||
return
|
return
|
||||||
outputs = [PartialTxOutput.from_address_and_value(ln_dummy_address(), onchain_amount)]
|
outputs = [PartialTxOutput.from_address_and_value(get_dummy_address('swap'), onchain_amount)]
|
||||||
coins = self._wallet.wallet.get_spendable_coins(None)
|
coins = self._wallet.wallet.get_spendable_coins(None)
|
||||||
try:
|
try:
|
||||||
self._tx = self._wallet.wallet.make_unsigned_transaction(
|
self._tx = self._wallet.wallet.make_unsigned_transaction(
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ class TxEditor(WindowModalDialog):
|
|||||||
vbox.addLayout(grid)
|
vbox.addLayout(grid)
|
||||||
vbox.addWidget(self.io_widget)
|
vbox.addWidget(self.io_widget)
|
||||||
self.message_label = WWLabel('')
|
self.message_label = WWLabel('')
|
||||||
|
self.message_label.setMinimumHeight(70)
|
||||||
vbox.addWidget(self.message_label)
|
vbox.addWidget(self.message_label)
|
||||||
|
|
||||||
buttons = self.create_buttons_bar()
|
buttons = self.create_buttons_bar()
|
||||||
@@ -395,6 +396,11 @@ class TxEditor(WindowModalDialog):
|
|||||||
self.toggle_locktime,
|
self.toggle_locktime,
|
||||||
_('Edit Locktime'), '')
|
_('Edit Locktime'), '')
|
||||||
self.pref_menu.addSeparator()
|
self.pref_menu.addSeparator()
|
||||||
|
add_pref_action(
|
||||||
|
self.config.WALLET_SEND_CHANGE_TO_LIGHTNING,
|
||||||
|
self.toggle_send_change_to_lightning,
|
||||||
|
_('Send change to Lightning'),
|
||||||
|
_('If possible, send the change of this transaction to your channels, with a submarine swap'))
|
||||||
add_pref_action(
|
add_pref_action(
|
||||||
self.wallet.use_change,
|
self.wallet.use_change,
|
||||||
self.toggle_use_change,
|
self.toggle_use_change,
|
||||||
@@ -465,6 +471,11 @@ class TxEditor(WindowModalDialog):
|
|||||||
self.config.WALLET_BATCH_RBF = b
|
self.config.WALLET_BATCH_RBF = b
|
||||||
self.trigger_update()
|
self.trigger_update()
|
||||||
|
|
||||||
|
def toggle_send_change_to_lightning(self):
|
||||||
|
b = not self.config.WALLET_SEND_CHANGE_TO_LIGHTNING
|
||||||
|
self.config.WALLET_SEND_CHANGE_TO_LIGHTNING = b
|
||||||
|
self.trigger_update()
|
||||||
|
|
||||||
def toggle_confirmed_only(self):
|
def toggle_confirmed_only(self):
|
||||||
b = not self.config.WALLET_SPEND_CONFIRMED_ONLY
|
b = not self.config.WALLET_SPEND_CONFIRMED_ONLY
|
||||||
self.config.WALLET_SPEND_CONFIRMED_ONLY = b
|
self.config.WALLET_SPEND_CONFIRMED_ONLY = b
|
||||||
@@ -563,6 +574,8 @@ class TxEditor(WindowModalDialog):
|
|||||||
self.error = long_warning
|
self.error = long_warning
|
||||||
else:
|
else:
|
||||||
messages.append(long_warning)
|
messages.append(long_warning)
|
||||||
|
if self.tx.has_dummy_output('swap'):
|
||||||
|
messages.append(_('This transaction will send funds to a submarine swap.'))
|
||||||
# warn if spending unconf
|
# warn if spending unconf
|
||||||
if any((txin.block_height is not None and txin.block_height<=0) for txin in self.tx.inputs()):
|
if any((txin.block_height is not None and txin.block_height<=0) for txin in self.tx.inputs()):
|
||||||
messages.append(_('This transaction will spend unconfirmed coins.'))
|
messages.append(_('This transaction will spend unconfirmed coins.'))
|
||||||
@@ -573,6 +586,9 @@ class TxEditor(WindowModalDialog):
|
|||||||
num_change = sum(int(o.is_change) for o in self.tx.outputs())
|
num_change = sum(int(o.is_change) for o in self.tx.outputs())
|
||||||
if num_change > 1:
|
if num_change > 1:
|
||||||
messages.append(_('This transaction has {} change outputs.'.format(num_change)))
|
messages.append(_('This transaction has {} change outputs.'.format(num_change)))
|
||||||
|
if num_change == 0:
|
||||||
|
messages.append(_('Make sure you pay enough mining fees; you will not be able to bump the fee later.'))
|
||||||
|
|
||||||
# TODO: warn if we send change back to input address
|
# TODO: warn if we send change back to input address
|
||||||
return messages
|
return messages
|
||||||
|
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ import electrum
|
|||||||
from electrum.gui import messages
|
from electrum.gui import messages
|
||||||
from electrum import (keystore, ecc, constants, util, bitcoin, commands,
|
from electrum import (keystore, ecc, constants, util, bitcoin, commands,
|
||||||
paymentrequest, lnutil)
|
paymentrequest, lnutil)
|
||||||
from electrum.bitcoin import COIN, is_address
|
from electrum.bitcoin import COIN, is_address, get_dummy_address
|
||||||
from electrum.plugin import run_hook, BasePlugin
|
from electrum.plugin import run_hook, BasePlugin
|
||||||
from electrum.i18n import _
|
from electrum.i18n import _
|
||||||
from electrum.util import (format_time, UserCancelled, profiler, bfh, InvalidPassword,
|
from electrum.util import (format_time, UserCancelled, profiler, bfh, InvalidPassword,
|
||||||
@@ -70,7 +70,7 @@ from electrum.network import Network, UntrustedServerReturnedError, NetworkExcep
|
|||||||
from electrum.exchange_rate import FxThread
|
from electrum.exchange_rate import FxThread
|
||||||
from electrum.simple_config import SimpleConfig
|
from electrum.simple_config import SimpleConfig
|
||||||
from electrum.logging import Logger
|
from electrum.logging import Logger
|
||||||
from electrum.lnutil import ln_dummy_address, extract_nodeid, ConnStringFormatError
|
from electrum.lnutil import extract_nodeid, ConnStringFormatError
|
||||||
from electrum.lnaddr import lndecode
|
from electrum.lnaddr import lndecode
|
||||||
from electrum.submarine_swaps import SwapServerError
|
from electrum.submarine_swaps import SwapServerError
|
||||||
|
|
||||||
@@ -163,6 +163,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
|
|||||||
computing_privkeys_signal = pyqtSignal()
|
computing_privkeys_signal = pyqtSignal()
|
||||||
show_privkeys_signal = pyqtSignal()
|
show_privkeys_signal = pyqtSignal()
|
||||||
show_error_signal = pyqtSignal(str)
|
show_error_signal = pyqtSignal(str)
|
||||||
|
show_message_signal = pyqtSignal(str)
|
||||||
labels_changed_signal = pyqtSignal()
|
labels_changed_signal = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, gui_object: 'ElectrumGui', wallet: Abstract_Wallet):
|
def __init__(self, gui_object: 'ElectrumGui', wallet: Abstract_Wallet):
|
||||||
@@ -263,6 +264,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
|
|||||||
self.app.update_fiat_signal.connect(self.update_fiat)
|
self.app.update_fiat_signal.connect(self.update_fiat)
|
||||||
|
|
||||||
self.show_error_signal.connect(self.show_error)
|
self.show_error_signal.connect(self.show_error)
|
||||||
|
self.show_message_signal.connect(self.show_message)
|
||||||
self.history_list.setFocus()
|
self.history_list.setFocus()
|
||||||
|
|
||||||
# network callbacks
|
# network callbacks
|
||||||
@@ -1236,6 +1238,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
|
|||||||
'''Sign the transaction in a separate thread. When done, calls
|
'''Sign the transaction in a separate thread. When done, calls
|
||||||
the callback with a success code of True or False.
|
the callback with a success code of True or False.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
def on_success(result):
|
def on_success(result):
|
||||||
callback(True)
|
callback(True)
|
||||||
def on_failure(exc_info):
|
def on_failure(exc_info):
|
||||||
@@ -1279,7 +1282,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
|
|||||||
@protected
|
@protected
|
||||||
def _open_channel(self, connect_str, funding_sat, push_amt, funding_tx, password):
|
def _open_channel(self, connect_str, funding_sat, push_amt, funding_tx, 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(get_dummy_address('channel'))
|
||||||
def task():
|
def task():
|
||||||
return self.wallet.lnworker.open_channel(
|
return self.wallet.lnworker.open_channel(
|
||||||
connect_str=connect_str,
|
connect_str=connect_str,
|
||||||
@@ -2822,3 +2825,14 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
|
|||||||
return
|
return
|
||||||
d = RebalanceDialog(self, chan1, chan2, amount_sat)
|
d = RebalanceDialog(self, chan1, chan2, amount_sat)
|
||||||
d.run()
|
d.run()
|
||||||
|
|
||||||
|
def on_swap_result(self, txid):
|
||||||
|
msg = _("Submarine swap") + ': ' + (_("Success") if txid else _("Expired")) + '\n\n'
|
||||||
|
if txid:
|
||||||
|
msg += _("Funding transaction") + ': ' + txid + '\n'
|
||||||
|
msg += _("Please remain online until the funding transaction is confirmed.")
|
||||||
|
self.show_message_signal.emit(msg)
|
||||||
|
else:
|
||||||
|
msg += _("Lightning funds were not received.")
|
||||||
|
self.show_error_signal.emit(msg)
|
||||||
|
|
||||||
|
|||||||
@@ -325,6 +325,15 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
|
|||||||
# user cancelled
|
# user cancelled
|
||||||
return
|
return
|
||||||
is_preview = conf_dlg.is_preview
|
is_preview = conf_dlg.is_preview
|
||||||
|
|
||||||
|
if tx.has_dummy_output('swap'):
|
||||||
|
sm = self.wallet.lnworker.swap_manager
|
||||||
|
coro = sm.request_swap_for_tx(tx)
|
||||||
|
swap, invoice, tx = self.network.run_from_another_thread(coro)
|
||||||
|
assert not tx.has_dummy_output('swap')
|
||||||
|
tx.swap_invoice = invoice
|
||||||
|
tx.swap_payment_hash = swap.payment_hash
|
||||||
|
|
||||||
if is_preview:
|
if is_preview:
|
||||||
self.window.show_transaction(tx, external_keypairs=external_keypairs, payment_identifier=payment_identifier)
|
self.window.show_transaction(tx, external_keypairs=external_keypairs, payment_identifier=payment_identifier)
|
||||||
return
|
return
|
||||||
@@ -711,6 +720,12 @@ class SendTab(QWidget, MessageBoxMixin, Logger):
|
|||||||
def broadcast_transaction(self, tx: Transaction, *, payment_identifier: PaymentIdentifier = None):
|
def broadcast_transaction(self, tx: Transaction, *, payment_identifier: PaymentIdentifier = None):
|
||||||
# note: payment_identifier is explicitly passed as self.payto_e.payment_identifier might
|
# note: payment_identifier is explicitly passed as self.payto_e.payment_identifier might
|
||||||
# already be cleared or otherwise have changed.
|
# already be cleared or otherwise have changed.
|
||||||
|
if hasattr(tx, 'swap_payment_hash'):
|
||||||
|
sm = self.wallet.lnworker.swap_manager
|
||||||
|
swap = sm.get_swap(tx.swap_payment_hash)
|
||||||
|
coro = sm.wait_for_htlcs_and_broadcast(swap, tx.swap_invoice, tx)
|
||||||
|
self.window.run_coroutine_from_thread(coro, _('Awaiting lightning payment..'), on_result=self.window.on_swap_result)
|
||||||
|
return
|
||||||
|
|
||||||
def broadcast_thread():
|
def broadcast_thread():
|
||||||
# non-GUI thread
|
# non-GUI thread
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from PyQt5.QtWidgets import QLabel, QVBoxLayout, QGridLayout, QPushButton
|
|||||||
|
|
||||||
from electrum.i18n import _
|
from electrum.i18n import _
|
||||||
from electrum.util import NotEnoughFunds, NoDynamicFeeEstimates
|
from electrum.util import NotEnoughFunds, NoDynamicFeeEstimates
|
||||||
from electrum.lnutil import ln_dummy_address
|
from electrum.bitcoin import get_dummy_address
|
||||||
from electrum.transaction import PartialTxOutput, PartialTransaction
|
from electrum.transaction import PartialTxOutput, PartialTransaction
|
||||||
|
|
||||||
from electrum.gui import messages
|
from electrum.gui import messages
|
||||||
@@ -173,7 +173,7 @@ class SwapDialog(WindowModalDialog, QtEventListener):
|
|||||||
|
|
||||||
def _spend_max_forward_swap(self, tx: Optional[PartialTransaction]) -> None:
|
def _spend_max_forward_swap(self, tx: Optional[PartialTransaction]) -> None:
|
||||||
if tx:
|
if tx:
|
||||||
amount = tx.output_value_for_address(ln_dummy_address())
|
amount = tx.output_value_for_address(get_dummy_address('swap'))
|
||||||
self.send_amount_e.setAmount(amount)
|
self.send_amount_e.setAmount(amount)
|
||||||
else:
|
else:
|
||||||
self.send_amount_e.setAmount(None)
|
self.send_amount_e.setAmount(None)
|
||||||
@@ -256,7 +256,7 @@ class SwapDialog(WindowModalDialog, QtEventListener):
|
|||||||
lightning_amount_sat=lightning_amount,
|
lightning_amount_sat=lightning_amount,
|
||||||
expected_onchain_amount_sat=onchain_amount + self.swap_manager.get_claim_fee(),
|
expected_onchain_amount_sat=onchain_amount + self.swap_manager.get_claim_fee(),
|
||||||
)
|
)
|
||||||
self.window.run_coroutine_from_thread(coro, _('Swapping funds'), on_result=self.on_result)
|
self.window.run_coroutine_from_thread(coro, _('Swapping funds'), on_result=self.window.on_swap_result)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
lightning_amount = self.recv_amount_e.get_amount()
|
lightning_amount = self.recv_amount_e.get_amount()
|
||||||
@@ -294,7 +294,7 @@ class SwapDialog(WindowModalDialog, QtEventListener):
|
|||||||
raise InvalidSwapParameters("swap_manager.max_amount_forward_swap() is None")
|
raise InvalidSwapParameters("swap_manager.max_amount_forward_swap() is None")
|
||||||
if max_amount > max_swap_amount:
|
if max_amount > max_swap_amount:
|
||||||
onchain_amount = max_swap_amount
|
onchain_amount = max_swap_amount
|
||||||
outputs = [PartialTxOutput.from_address_and_value(ln_dummy_address(), onchain_amount)]
|
outputs = [PartialTxOutput.from_address_and_value(get_dummy_address('swap'), onchain_amount)]
|
||||||
try:
|
try:
|
||||||
tx = self.window.wallet.make_unsigned_transaction(
|
tx = self.window.wallet.make_unsigned_transaction(
|
||||||
coins=coins,
|
coins=coins,
|
||||||
@@ -325,16 +325,7 @@ class SwapDialog(WindowModalDialog, QtEventListener):
|
|||||||
tx=tx,
|
tx=tx,
|
||||||
channels=self.channels,
|
channels=self.channels,
|
||||||
)
|
)
|
||||||
self.window.run_coroutine_from_thread(coro, _('Swapping funds'), on_result=self.on_result)
|
self.window.run_coroutine_from_thread(coro, _('Swapping funds'), on_result=self.window.on_swap_result)
|
||||||
|
|
||||||
def on_result(self, txid):
|
|
||||||
msg = _("Submarine swap") + ': ' + (_("Success") if txid else _("Expired")) + '\n\n'
|
|
||||||
if txid:
|
|
||||||
msg += _("Funding transaction") + ': ' + txid + '\n'
|
|
||||||
msg += _("Please remain online until the funding transaction is confirmed.")
|
|
||||||
else:
|
|
||||||
msg += _("Lightning funds were not received.")
|
|
||||||
self.window.show_error_signal.emit(msg)
|
|
||||||
|
|
||||||
def get_description(self):
|
def get_description(self):
|
||||||
onchain_funds = "onchain funds"
|
onchain_funds = "onchain funds"
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ from electrum.simple_config import SimpleConfig
|
|||||||
from electrum.util import quantize_feerate
|
from electrum.util import quantize_feerate
|
||||||
from electrum import bitcoin
|
from electrum import bitcoin
|
||||||
|
|
||||||
from electrum.bitcoin import base_encode, NLOCKTIME_BLOCKHEIGHT_MAX
|
from electrum.bitcoin import base_encode, NLOCKTIME_BLOCKHEIGHT_MAX, get_dummy_address
|
||||||
from electrum.i18n import _
|
from electrum.i18n import _
|
||||||
from electrum.plugin import run_hook
|
from electrum.plugin import run_hook
|
||||||
from electrum import simple_config
|
from electrum import simple_config
|
||||||
@@ -123,6 +123,8 @@ class TxInOutWidget(QWidget):
|
|||||||
legend=_("Change Address"), color=ColorScheme.YELLOW, tooltip=_("Wallet change address"))
|
legend=_("Change Address"), color=ColorScheme.YELLOW, tooltip=_("Wallet change address"))
|
||||||
self.txo_color_2fa = TxOutputColoring(
|
self.txo_color_2fa = TxOutputColoring(
|
||||||
legend=_("TrustedCoin (2FA) batch fee"), color=ColorScheme.BLUE, tooltip=_("TrustedCoin (2FA) fee for the next batch of transactions"))
|
legend=_("TrustedCoin (2FA) batch fee"), color=ColorScheme.BLUE, tooltip=_("TrustedCoin (2FA) fee for the next batch of transactions"))
|
||||||
|
self.txo_color_swap = TxOutputColoring(
|
||||||
|
legend=_("Submarine swap address"), color=ColorScheme.BLUE, tooltip=_("Submarine swap address"))
|
||||||
self.outputs_header = QLabel()
|
self.outputs_header = QLabel()
|
||||||
self.outputs_textedit = QTextBrowserWithDefaultSize(750, 100)
|
self.outputs_textedit = QTextBrowserWithDefaultSize(750, 100)
|
||||||
self.outputs_textedit.setOpenLinks(False) # disable automatic link opening
|
self.outputs_textedit.setOpenLinks(False) # disable automatic link opening
|
||||||
@@ -139,6 +141,7 @@ class TxInOutWidget(QWidget):
|
|||||||
outheader_hbox.addWidget(self.txo_color_recv.legend_label)
|
outheader_hbox.addWidget(self.txo_color_recv.legend_label)
|
||||||
outheader_hbox.addWidget(self.txo_color_change.legend_label)
|
outheader_hbox.addWidget(self.txo_color_change.legend_label)
|
||||||
outheader_hbox.addWidget(self.txo_color_2fa.legend_label)
|
outheader_hbox.addWidget(self.txo_color_2fa.legend_label)
|
||||||
|
outheader_hbox.addWidget(self.txo_color_swap.legend_label)
|
||||||
|
|
||||||
vbox = QVBoxLayout()
|
vbox = QVBoxLayout()
|
||||||
vbox.addLayout(self.inheader_hbox)
|
vbox.addLayout(self.inheader_hbox)
|
||||||
@@ -164,9 +167,10 @@ class TxInOutWidget(QWidget):
|
|||||||
lnk.setToolTip(_('Click to open, right-click for menu'))
|
lnk.setToolTip(_('Click to open, right-click for menu'))
|
||||||
lnk.setAnchor(True)
|
lnk.setAnchor(True)
|
||||||
lnk.setUnderlineStyle(QTextCharFormat.SingleUnderline)
|
lnk.setUnderlineStyle(QTextCharFormat.SingleUnderline)
|
||||||
tf_used_recv, tf_used_change, tf_used_2fa = False, False, False
|
tf_used_recv, tf_used_change, tf_used_2fa, tf_used_swap = False, False, False, False
|
||||||
def addr_text_format(addr: str) -> QTextCharFormat:
|
def addr_text_format(addr: str) -> QTextCharFormat:
|
||||||
nonlocal tf_used_recv, tf_used_change, tf_used_2fa
|
nonlocal tf_used_recv, tf_used_change, tf_used_2fa, tf_used_swap
|
||||||
|
sm = self.wallet.lnworker.swap_manager
|
||||||
if self.wallet.is_mine(addr):
|
if self.wallet.is_mine(addr):
|
||||||
if self.wallet.is_change(addr):
|
if self.wallet.is_change(addr):
|
||||||
tf_used_change = True
|
tf_used_change = True
|
||||||
@@ -179,6 +183,9 @@ class TxInOutWidget(QWidget):
|
|||||||
fmt.setAnchor(True)
|
fmt.setAnchor(True)
|
||||||
fmt.setUnderlineStyle(QTextCharFormat.SingleUnderline)
|
fmt.setUnderlineStyle(QTextCharFormat.SingleUnderline)
|
||||||
return fmt
|
return fmt
|
||||||
|
elif sm and sm.is_lockup_address_for_a_swap(addr) or addr==get_dummy_address('swap'):
|
||||||
|
tf_used_swap = True
|
||||||
|
return self.txo_color_swap.text_char_format
|
||||||
elif self.wallet.is_billing_address(addr):
|
elif self.wallet.is_billing_address(addr):
|
||||||
tf_used_2fa = True
|
tf_used_2fa = True
|
||||||
return self.txo_color_2fa.text_char_format
|
return self.txo_color_2fa.text_char_format
|
||||||
@@ -267,6 +274,7 @@ class TxInOutWidget(QWidget):
|
|||||||
self.txo_color_recv.legend_label.setVisible(tf_used_recv)
|
self.txo_color_recv.legend_label.setVisible(tf_used_recv)
|
||||||
self.txo_color_change.legend_label.setVisible(tf_used_change)
|
self.txo_color_change.legend_label.setVisible(tf_used_change)
|
||||||
self.txo_color_2fa.legend_label.setVisible(tf_used_2fa)
|
self.txo_color_2fa.legend_label.setVisible(tf_used_2fa)
|
||||||
|
self.txo_color_swap.legend_label.setVisible(tf_used_swap)
|
||||||
|
|
||||||
def _open_internal_link(self, target):
|
def _open_internal_link(self, target):
|
||||||
"""Accepts either a str txid, str address, or a QUrl which should be
|
"""Accepts either a str txid, str address, or a QUrl which should be
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ from . import constants
|
|||||||
from .util import (bfh, log_exceptions, ignore_exceptions, chunks, OldTaskGroup,
|
from .util import (bfh, log_exceptions, ignore_exceptions, chunks, OldTaskGroup,
|
||||||
UnrelatedTransactionException, error_text_bytes_to_safe_str)
|
UnrelatedTransactionException, error_text_bytes_to_safe_str)
|
||||||
from . import transaction
|
from . import transaction
|
||||||
from .bitcoin import make_op_return
|
from .bitcoin import make_op_return, get_dummy_address
|
||||||
from .transaction import PartialTxOutput, match_script_against_template, Sighash
|
from .transaction import PartialTxOutput, match_script_against_template, Sighash
|
||||||
from .logging import Logger
|
from .logging import Logger
|
||||||
from .lnonion import (new_onion_packet, OnionFailureCode, calc_hops_data_for_payment,
|
from .lnonion import (new_onion_packet, OnionFailureCode, calc_hops_data_for_payment,
|
||||||
@@ -48,7 +48,6 @@ from .lntransport import LNTransport, LNTransportBase
|
|||||||
from .lnmsg import encode_msg, decode_msg, UnknownOptionalMsgType, FailedToParseMsg
|
from .lnmsg import encode_msg, decode_msg, UnknownOptionalMsgType, FailedToParseMsg
|
||||||
from .interface import GracefulDisconnect
|
from .interface import GracefulDisconnect
|
||||||
from .lnrouter import fee_for_edge_msat
|
from .lnrouter import fee_for_edge_msat
|
||||||
from .lnutil import ln_dummy_address
|
|
||||||
from .json_db import StoredDict
|
from .json_db import StoredDict
|
||||||
from .invoices import PR_PAID
|
from .invoices import PR_PAID
|
||||||
from .simple_config import FEE_LN_ETA_TARGET
|
from .simple_config import FEE_LN_ETA_TARGET
|
||||||
@@ -813,11 +812,7 @@ class Peer(Logger):
|
|||||||
redeem_script = funding_output_script(local_config, remote_config)
|
redeem_script = funding_output_script(local_config, remote_config)
|
||||||
funding_address = bitcoin.redeem_script_to_address('p2wsh', redeem_script)
|
funding_address = bitcoin.redeem_script_to_address('p2wsh', redeem_script)
|
||||||
funding_output = PartialTxOutput.from_address_and_value(funding_address, funding_sat)
|
funding_output = PartialTxOutput.from_address_and_value(funding_address, funding_sat)
|
||||||
dummy_output = PartialTxOutput.from_address_and_value(ln_dummy_address(), funding_sat)
|
funding_tx.replace_dummy_output('channel', funding_address)
|
||||||
if dummy_output not in funding_tx.outputs(): raise Exception("LN dummy output (err 1)")
|
|
||||||
funding_tx._outputs.remove(dummy_output)
|
|
||||||
if dummy_output in funding_tx.outputs(): raise Exception("LN dummy output (err 2)")
|
|
||||||
funding_tx.add_outputs([funding_output])
|
|
||||||
# find and encrypt op_return data associated to funding_address
|
# find and encrypt op_return data associated to funding_address
|
||||||
has_onchain_backup = self.lnworker and self.lnworker.has_recoverable_channels()
|
has_onchain_backup = self.lnworker and self.lnworker.has_recoverable_channels()
|
||||||
if has_onchain_backup:
|
if has_onchain_backup:
|
||||||
|
|||||||
@@ -53,9 +53,6 @@ HTLC_OUTPUT_WEIGHT = 172
|
|||||||
LN_MAX_FUNDING_SAT_LEGACY = pow(2, 24) - 1
|
LN_MAX_FUNDING_SAT_LEGACY = pow(2, 24) - 1
|
||||||
DUST_LIMIT_MAX = 1000
|
DUST_LIMIT_MAX = 1000
|
||||||
|
|
||||||
# dummy address for fee estimation of funding tx
|
|
||||||
def ln_dummy_address():
|
|
||||||
return redeem_script_to_address('p2wsh', '')
|
|
||||||
|
|
||||||
from .json_db import StoredObject, stored_in, stored_as
|
from .json_db import StoredObject, stored_in, stored_as
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ from .lnchannel import ChannelState, PeerState, HTLCWithStatus
|
|||||||
from .lnrater import LNRater
|
from .lnrater import LNRater
|
||||||
from . import lnutil
|
from . import lnutil
|
||||||
from .lnutil import funding_output_script
|
from .lnutil import funding_output_script
|
||||||
from .bitcoin import redeem_script_to_address
|
from .bitcoin import redeem_script_to_address, get_dummy_address
|
||||||
from .lnutil import (Outpoint, LNPeerAddr,
|
from .lnutil import (Outpoint, LNPeerAddr,
|
||||||
get_compressed_pubkey_from_bech32, extract_nodeid,
|
get_compressed_pubkey_from_bech32, extract_nodeid,
|
||||||
PaymentFailure, split_host_port, ConnStringFormatError,
|
PaymentFailure, split_host_port, ConnStringFormatError,
|
||||||
@@ -66,7 +66,7 @@ from .lnutil import (Outpoint, LNPeerAddr,
|
|||||||
UpdateAddHtlc, Direction, LnFeatures, ShortChannelID,
|
UpdateAddHtlc, Direction, LnFeatures, ShortChannelID,
|
||||||
HtlcLog, derive_payment_secret_from_payment_preimage,
|
HtlcLog, derive_payment_secret_from_payment_preimage,
|
||||||
NoPathFound, InvalidGossipMsg)
|
NoPathFound, InvalidGossipMsg)
|
||||||
from .lnutil import ln_dummy_address, ln_compare_features, IncompatibleLightningFeatures
|
from .lnutil import ln_compare_features, IncompatibleLightningFeatures
|
||||||
from .transaction import PartialTxOutput, PartialTransaction, PartialTxInput
|
from .transaction import PartialTxOutput, PartialTransaction, PartialTxInput
|
||||||
from .lnonion import OnionFailureCode, OnionRoutingFailure, OnionPacket
|
from .lnonion import OnionFailureCode, OnionRoutingFailure, OnionPacket
|
||||||
from .lnmsg import decode_msg
|
from .lnmsg import decode_msg
|
||||||
@@ -1274,7 +1274,7 @@ class LNWallet(LNWorker):
|
|||||||
funding_sat: int,
|
funding_sat: int,
|
||||||
node_id: bytes,
|
node_id: bytes,
|
||||||
fee_est=None) -> PartialTransaction:
|
fee_est=None) -> PartialTransaction:
|
||||||
outputs = [PartialTxOutput.from_address_and_value(ln_dummy_address(), funding_sat)]
|
outputs = [PartialTxOutput.from_address_and_value(get_dummy_address('channel'), funding_sat)]
|
||||||
if self.has_recoverable_channels():
|
if self.has_recoverable_channels():
|
||||||
dummy_scriptpubkey = make_op_return(self.cb_data(node_id))
|
dummy_scriptpubkey = make_op_return(self.cb_data(node_id))
|
||||||
outputs.append(PartialTxOutput(scriptpubkey=dummy_scriptpubkey, value=0))
|
outputs.append(PartialTxOutput(scriptpubkey=dummy_scriptpubkey, value=0))
|
||||||
@@ -2549,7 +2549,7 @@ class LNWallet(LNWorker):
|
|||||||
# check that we can send onchain
|
# check that we can send onchain
|
||||||
swap_server_mining_fee = 10000 # guessing, because we have not called get_pairs yet
|
swap_server_mining_fee = 10000 # guessing, because we have not called get_pairs yet
|
||||||
swap_funding_sat = swap_recv_amount + swap_server_mining_fee
|
swap_funding_sat = swap_recv_amount + swap_server_mining_fee
|
||||||
swap_output = PartialTxOutput.from_address_and_value(ln_dummy_address(), int(swap_funding_sat))
|
swap_output = PartialTxOutput.from_address_and_value(get_dummy_address('swap'), int(swap_funding_sat))
|
||||||
if not self.wallet.can_pay_onchain([swap_output], coins=coins):
|
if not self.wallet.can_pay_onchain([swap_output], coins=coins):
|
||||||
continue
|
continue
|
||||||
return (chan, swap_recv_amount)
|
return (chan, swap_recv_amount)
|
||||||
|
|||||||
@@ -877,6 +877,7 @@ class SimpleConfig(Logger):
|
|||||||
WALLET_PAYREQ_EXPIRY_SECONDS = ConfigVar('request_expiry', default=invoices.PR_DEFAULT_EXPIRATION_WHEN_CREATING, type_=int)
|
WALLET_PAYREQ_EXPIRY_SECONDS = ConfigVar('request_expiry', default=invoices.PR_DEFAULT_EXPIRATION_WHEN_CREATING, type_=int)
|
||||||
WALLET_USE_SINGLE_PASSWORD = ConfigVar('single_password', default=False, type_=bool)
|
WALLET_USE_SINGLE_PASSWORD = ConfigVar('single_password', default=False, type_=bool)
|
||||||
# note: 'use_change' and 'multiple_change' are per-wallet settings
|
# note: 'use_change' and 'multiple_change' are per-wallet settings
|
||||||
|
WALLET_SEND_CHANGE_TO_LIGHTNING = ConfigVar('send_change_to_lightning', default=False, type_=bool)
|
||||||
|
|
||||||
FX_USE_EXCHANGE_RATE = ConfigVar('use_exchange_rate', default=False, type_=bool)
|
FX_USE_EXCHANGE_RATE = ConfigVar('use_exchange_rate', default=False, type_=bool)
|
||||||
FX_CURRENCY = ConfigVar('currency', default='EUR', type_=str)
|
FX_CURRENCY = ConfigVar('currency', default='EUR', type_=str)
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ from .bitcoin import (script_to_p2wsh, opcodes, p2wsh_nested_script, push_script
|
|||||||
from .transaction import PartialTxInput, PartialTxOutput, PartialTransaction, Transaction, TxInput, TxOutpoint
|
from .transaction import PartialTxInput, PartialTxOutput, PartialTransaction, Transaction, TxInput, TxOutpoint
|
||||||
from .transaction import script_GetOp, match_script_against_template, OPPushDataGeneric, OPPushDataPubkey
|
from .transaction import script_GetOp, match_script_against_template, OPPushDataGeneric, OPPushDataPubkey
|
||||||
from .util import log_exceptions, BelowDustLimit, OldTaskGroup
|
from .util import log_exceptions, BelowDustLimit, OldTaskGroup
|
||||||
from .lnutil import REDEEM_AFTER_DOUBLE_SPENT_DELAY, ln_dummy_address
|
from .lnutil import REDEEM_AFTER_DOUBLE_SPENT_DELAY
|
||||||
from .bitcoin import dust_threshold
|
from .bitcoin import dust_threshold, get_dummy_address
|
||||||
from .logging import Logger
|
from .logging import Logger
|
||||||
from .lnutil import hex_to_bytes
|
from .lnutil import hex_to_bytes
|
||||||
from .lnaddr import lndecode
|
from .lnaddr import lndecode
|
||||||
@@ -190,6 +190,7 @@ class SwapManager(Logger):
|
|||||||
self.wallet = wallet
|
self.wallet = wallet
|
||||||
self.lnworker = lnworker
|
self.lnworker = lnworker
|
||||||
self.taskgroup = None
|
self.taskgroup = None
|
||||||
|
self.dummy_address = get_dummy_address('swap')
|
||||||
|
|
||||||
self.swaps = self.wallet.db.get_dict('submarine_swaps') # type: Dict[str, SwapData]
|
self.swaps = self.wallet.db.get_dict('submarine_swaps') # type: Dict[str, SwapData]
|
||||||
self._swaps_by_funding_outpoint = {} # type: Dict[TxOutpoint, SwapData]
|
self._swaps_by_funding_outpoint = {} # type: Dict[TxOutpoint, SwapData]
|
||||||
@@ -578,6 +579,11 @@ class SwapManager(Logger):
|
|||||||
"""
|
"""
|
||||||
assert self.network
|
assert self.network
|
||||||
assert self.lnwatcher
|
assert self.lnwatcher
|
||||||
|
swap, invoice = await self.request_normal_swap(lightning_amount_sat, expected_onchain_amount_sat, channels=channels)
|
||||||
|
tx = self.create_funding_tx(swap, tx, password)
|
||||||
|
return await self.wait_for_htlcs_and_broadcast(swap, invoice, tx, channels=channels)
|
||||||
|
|
||||||
|
async def request_normal_swap(self, lightning_amount_sat, expected_onchain_amount_sat, channels=None):
|
||||||
amount_msat = lightning_amount_sat * 1000
|
amount_msat = lightning_amount_sat * 1000
|
||||||
refund_privkey = os.urandom(32)
|
refund_privkey = os.urandom(32)
|
||||||
refund_pubkey = ECPrivkey(refund_privkey).get_public_key_bytes(compressed=True)
|
refund_pubkey = ECPrivkey(refund_privkey).get_public_key_bytes(compressed=True)
|
||||||
@@ -654,8 +660,11 @@ class SwapManager(Logger):
|
|||||||
their_pubkey=claim_pubkey,
|
their_pubkey=claim_pubkey,
|
||||||
invoice=invoice,
|
invoice=invoice,
|
||||||
prepay=False)
|
prepay=False)
|
||||||
|
return swap, invoice
|
||||||
|
|
||||||
tx = self.create_funding_tx(swap, tx, password)
|
async def wait_for_htlcs_and_broadcast(self, swap, invoice, tx, channels=None):
|
||||||
|
payment_hash = swap.payment_hash
|
||||||
|
refund_pubkey = ECPrivkey(swap.privkey).get_public_key_bytes(compressed=True)
|
||||||
if self.server_supports_htlc_first:
|
if self.server_supports_htlc_first:
|
||||||
# send invoice to server and wait for htlcs
|
# send invoice to server and wait for htlcs
|
||||||
request_data = {
|
request_data = {
|
||||||
@@ -679,23 +688,39 @@ class SwapManager(Logger):
|
|||||||
else:
|
else:
|
||||||
# broadcast funding tx right away
|
# broadcast funding tx right away
|
||||||
await self.broadcast_funding_tx(swap, tx)
|
await self.broadcast_funding_tx(swap, tx)
|
||||||
|
# fixme: if broadcast fails, we need to fail htlcs and cancel the swap
|
||||||
return swap.funding_txid
|
return swap.funding_txid
|
||||||
|
|
||||||
def create_funding_tx(self, swap, tx, password):
|
def create_funding_tx(self, swap, tx, password):
|
||||||
# create funding tx
|
# create funding tx
|
||||||
# note: rbf must not decrease payment
|
# note: rbf must not decrease payment
|
||||||
# this is taken care of in wallet._is_rbf_allowed_to_touch_tx_output
|
# this is taken care of in wallet._is_rbf_allowed_to_touch_tx_output
|
||||||
funding_output = PartialTxOutput.from_address_and_value(swap.lockup_address, swap.onchain_amount)
|
|
||||||
if tx is None:
|
if tx is None:
|
||||||
|
funding_output = PartialTxOutput.from_address_and_value(swap.lockup_address, swap.onchain_amount)
|
||||||
tx = self.wallet.create_transaction(outputs=[funding_output], rbf=True, password=password)
|
tx = self.wallet.create_transaction(outputs=[funding_output], rbf=True, password=password)
|
||||||
else:
|
else:
|
||||||
dummy_output = PartialTxOutput.from_address_and_value(ln_dummy_address(), swap.onchain_amount)
|
tx.replace_dummy_output('swap', swap.lockup_address)
|
||||||
tx.outputs().remove(dummy_output)
|
|
||||||
tx.add_outputs([funding_output])
|
|
||||||
tx.set_rbf(True)
|
tx.set_rbf(True)
|
||||||
self.wallet.sign_transaction(tx, password)
|
self.wallet.sign_transaction(tx, password)
|
||||||
return tx
|
return tx
|
||||||
|
|
||||||
|
@log_exceptions
|
||||||
|
async def request_swap_for_tx(self, tx):
|
||||||
|
for o in tx.outputs():
|
||||||
|
if o.address == self.dummy_address:
|
||||||
|
change_amount = o.value
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
await self.get_pairs()
|
||||||
|
assert self.server_supports_htlc_first
|
||||||
|
lightning_amount_sat = self.get_recv_amount(change_amount, is_reverse=False)
|
||||||
|
swap, invoice = await self.request_normal_swap(
|
||||||
|
lightning_amount_sat = lightning_amount_sat,
|
||||||
|
expected_onchain_amount_sat=change_amount)
|
||||||
|
tx.replace_dummy_output('swap', swap.lockup_address)
|
||||||
|
return swap, invoice, tx
|
||||||
|
|
||||||
@log_exceptions
|
@log_exceptions
|
||||||
async def broadcast_funding_tx(self, swap, tx):
|
async def broadcast_funding_tx(self, swap, tx):
|
||||||
await self.network.broadcast_transaction(tx)
|
await self.network.broadcast_transaction(tx)
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ from .bitcoin import (TYPE_ADDRESS, TYPE_SCRIPT, hash_160,
|
|||||||
from .crypto import sha256d
|
from .crypto import sha256d
|
||||||
from .logging import get_logger
|
from .logging import get_logger
|
||||||
from .util import ShortID, OldTaskGroup
|
from .util import ShortID, OldTaskGroup
|
||||||
|
from .bitcoin import get_dummy_address
|
||||||
from .descriptor import Descriptor, MissingSolutionPiece, create_dummy_descriptor_from_address
|
from .descriptor import Descriptor, MissingSolutionPiece, create_dummy_descriptor_from_address
|
||||||
from .json_db import stored_in
|
from .json_db import stored_in
|
||||||
|
|
||||||
@@ -1155,6 +1156,27 @@ class Transaction:
|
|||||||
script = bitcoin.address_to_script(addr)
|
script = bitcoin.address_to_script(addr)
|
||||||
return self.get_output_idxs_from_scriptpubkey(script)
|
return self.get_output_idxs_from_scriptpubkey(script)
|
||||||
|
|
||||||
|
def replace_output_address(self, old_address, new_address):
|
||||||
|
idx = list(self.get_output_idxs_from_address(old_address))
|
||||||
|
assert len(idx) == 1
|
||||||
|
amount = self._outputs[idx[0]].value
|
||||||
|
funding_output = PartialTxOutput.from_address_and_value(new_address, amount)
|
||||||
|
old_output = PartialTxOutput.from_address_and_value(old_address, amount)
|
||||||
|
self._outputs.remove(old_output)
|
||||||
|
self.add_outputs([funding_output])
|
||||||
|
delattr(self, '_script_to_output_idx')
|
||||||
|
|
||||||
|
def get_change_outputs(self):
|
||||||
|
return [o for o in self._outputs if o.is_change]
|
||||||
|
|
||||||
|
def replace_dummy_output(self, purpose, new_address):
|
||||||
|
dummy_addr = get_dummy_address(purpose)
|
||||||
|
self.replace_output_address(dummy_addr, new_address)
|
||||||
|
|
||||||
|
def has_dummy_output(self, purpose):
|
||||||
|
addr = get_dummy_address(purpose)
|
||||||
|
return len(self.get_output_idxs_from_address(addr)) == 1
|
||||||
|
|
||||||
def output_value_for_address(self, addr):
|
def output_value_for_address(self, addr):
|
||||||
# assumes exactly one output has that address
|
# assumes exactly one output has that address
|
||||||
for o in self.outputs():
|
for o in self.outputs():
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ from .util import (NotEnoughFunds, UserCancelled, profiler, OldTaskGroup, ignore
|
|||||||
Fiat, bfh, TxMinedInfo, quantize_feerate, OrderedDictWithIndex)
|
Fiat, bfh, TxMinedInfo, quantize_feerate, OrderedDictWithIndex)
|
||||||
from .simple_config import SimpleConfig, FEE_RATIO_HIGH_WARNING, FEERATE_WARNING_HIGH_FEE
|
from .simple_config import SimpleConfig, FEE_RATIO_HIGH_WARNING, FEERATE_WARNING_HIGH_FEE
|
||||||
from .bitcoin import COIN, TYPE_ADDRESS
|
from .bitcoin import COIN, TYPE_ADDRESS
|
||||||
from .bitcoin import is_address, address_to_script, is_minikey, relayfee, dust_threshold
|
from .bitcoin import is_address, address_to_script, is_minikey, relayfee, dust_threshold, get_dummy_address
|
||||||
from .crypto import sha256d
|
from .crypto import sha256d
|
||||||
from . import keystore
|
from . import keystore
|
||||||
from .keystore import (load_keystore, Hardware_KeyStore, KeyStore, KeyStoreWithMPK,
|
from .keystore import (load_keystore, Hardware_KeyStore, KeyStore, KeyStoreWithMPK,
|
||||||
@@ -1762,6 +1762,14 @@ class Abstract_Wallet(ABC, Logger, EventListener):
|
|||||||
change_addrs=change_addrs,
|
change_addrs=change_addrs,
|
||||||
fee_estimator_vb=fee_estimator,
|
fee_estimator_vb=fee_estimator,
|
||||||
dust_threshold=self.dust_threshold())
|
dust_threshold=self.dust_threshold())
|
||||||
|
if self.lnworker and self.config.WALLET_SEND_CHANGE_TO_LIGHTNING:
|
||||||
|
change = tx.get_change_outputs()
|
||||||
|
# do not use multiple change addresses
|
||||||
|
if len(change) == 1:
|
||||||
|
amount = change[0].value
|
||||||
|
ln_amount = self.lnworker.swap_manager.get_recv_amount(amount, is_reverse=False)
|
||||||
|
if ln_amount and ln_amount <= self.lnworker.num_sats_can_receive():
|
||||||
|
tx.replace_output_address(change[0].address, get_dummy_address('swap'))
|
||||||
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).
|
||||||
|
|||||||
Reference in New Issue
Block a user