LNWorker: Add suggest_rebalance methods for sending and receiving.
These methods return a list of channels that can be rebalanced, in order to receive or send a given amount. Also add 'channels' parameter to submarine swaps. Previously, swaps were not considering which channel to use. When we do not have liquidity to pay an invoice: - add 'rebalance' option in order to pay an invoice - use the suggested channel in the 'swap' option When we do not have the liquidity to receive an invoice: - add 'Rebalance' and 'Swap' buttons to the receive tab
This commit is contained in:
@@ -1185,9 +1185,31 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
||||
self.receive_address_help.setVisible(False)
|
||||
self.receive_URI_e = ButtonsTextEdit()
|
||||
self.receive_lightning_e = ButtonsTextEdit()
|
||||
self.receive_lightning_help = WWLabel('')
|
||||
|
||||
self.receive_lightning_help_text = WWLabel('')
|
||||
self.receive_rebalance_button = QPushButton('Rebalance')
|
||||
self.receive_rebalance_button.suggestion = None
|
||||
def on_receive_rebalance():
|
||||
if self.receive_rebalance_button.suggestion:
|
||||
chan1, chan2, delta = self.receive_rebalance_button.suggestion
|
||||
self.rebalance_dialog(chan1, chan2, amount_sat=delta)
|
||||
self.receive_rebalance_button.clicked.connect(on_receive_rebalance)
|
||||
self.receive_swap_button = QPushButton('Swap')
|
||||
self.receive_swap_button.suggestion = None
|
||||
def on_receive_swap():
|
||||
if self.receive_swap_button.suggestion:
|
||||
chan, swap_recv_amount_sat = self.receive_swap_button.suggestion
|
||||
self.run_swap_dialog(is_reverse=True, recv_amount_sat=swap_recv_amount_sat, channels=[chan])
|
||||
self.receive_swap_button.clicked.connect(on_receive_swap)
|
||||
buttons = QHBoxLayout()
|
||||
buttons.addWidget(self.receive_rebalance_button)
|
||||
buttons.addWidget(self.receive_swap_button)
|
||||
vbox = QVBoxLayout()
|
||||
vbox.addWidget(self.receive_lightning_help_text)
|
||||
vbox.addLayout(buttons)
|
||||
self.receive_lightning_help = QWidget()
|
||||
self.receive_lightning_help.setVisible(False)
|
||||
#self.receive_URI_e.setFocusPolicy(Qt.ClickFocus)
|
||||
self.receive_lightning_help.setLayout(vbox)
|
||||
|
||||
fixedSize = 200
|
||||
for e in [self.receive_address_e, self.receive_URI_e, self.receive_lightning_e]:
|
||||
@@ -1273,12 +1295,13 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
||||
|
||||
def show_receive_request(self, req):
|
||||
addr = req.get_address() or ''
|
||||
amount_sat = req.get_amount_sat() or 0
|
||||
address_help = '' if addr else _('Amount too small to be received onchain')
|
||||
lnaddr = req.lightning_invoice
|
||||
bip21_lightning = lnaddr if self.config.get('bip21_lightning', False) else None
|
||||
URI = req.get_bip21_URI(lightning=bip21_lightning)
|
||||
lightning_online = self.wallet.lnworker and self.wallet.lnworker.num_peers() > 0
|
||||
can_receive_lightning = self.wallet.lnworker and (req.get_amount_sat() or 0) <= self.wallet.lnworker.num_sats_can_receive()
|
||||
can_receive_lightning = self.wallet.lnworker and amount_sat <= self.wallet.lnworker.num_sats_can_receive()
|
||||
if lnaddr is None:
|
||||
ln_help = _('This request does not have a Lightning invoice.')
|
||||
lnaddr = ''
|
||||
@@ -1286,7 +1309,15 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
||||
ln_help = _('You must be online to receive Lightning payments.')
|
||||
lnaddr = ''
|
||||
elif not can_receive_lightning:
|
||||
self.receive_rebalance_button.suggestion = self.wallet.lnworker.suggest_rebalance_to_receive(amount_sat)
|
||||
self.receive_swap_button.suggestion = self.wallet.lnworker.suggest_swap_to_receive(amount_sat)
|
||||
ln_help = _('Your Lightning channels do not have the capacity to receive this amount.')
|
||||
can_rebalance = bool(self.receive_rebalance_button.suggestion)
|
||||
can_swap = bool(self.receive_swap_button.suggestion)
|
||||
self.receive_rebalance_button.setEnabled(can_rebalance)
|
||||
self.receive_rebalance_button.setVisible(can_rebalance)
|
||||
self.receive_swap_button.setEnabled(can_swap)
|
||||
self.receive_swap_button.setVisible(can_swap)
|
||||
lnaddr = ''
|
||||
else:
|
||||
ln_help = ''
|
||||
@@ -1303,7 +1334,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
||||
self.receive_URI_e.setText(URI)
|
||||
self.receive_URI_qr.setData(URI)
|
||||
self.receive_lightning_e.setText(lnaddr) # TODO maybe prepend "lightning:" ??
|
||||
self.receive_lightning_help.setText(ln_help)
|
||||
self.receive_lightning_help_text.setText(ln_help)
|
||||
self.receive_lightning_qr.setData(lnaddr_qr)
|
||||
# macOS hack (similar to #4777)
|
||||
self.receive_lightning_e.repaint()
|
||||
@@ -1691,26 +1722,33 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
||||
coins = self.get_coins(nonlocal_only=True)
|
||||
can_pay_onchain = invoice.get_address() and self.wallet.can_pay_onchain(invoice.get_outputs(), coins=coins)
|
||||
can_pay_with_new_channel, channel_funding_sat = self.wallet.can_pay_with_new_channel(amount_sat, coins=coins)
|
||||
can_pay_with_swap, swap_recv_amount_sat = self.wallet.can_pay_with_swap(amount_sat, coins=coins)
|
||||
can_pay_with_swap = self.wallet.lnworker.suggest_swap_to_send(amount_sat, coins=coins)
|
||||
can_rebalance = self.wallet.lnworker.suggest_rebalance_to_send(amount_sat)
|
||||
choices = {}
|
||||
if can_rebalance:
|
||||
msg = ''.join([
|
||||
_('Rebalance channels'), '\n',
|
||||
_('Funds will be sent between your channels in order to raise your sending capacity.')
|
||||
])
|
||||
choices[0] = msg
|
||||
if can_pay_onchain:
|
||||
msg = ''.join([
|
||||
_('Pay onchain'), '\n',
|
||||
_('Funds will be sent to the invoice fallback address.')
|
||||
])
|
||||
choices[0] = msg
|
||||
choices[1] = msg
|
||||
if can_pay_with_new_channel:
|
||||
msg = ''.join([
|
||||
_('Open a new channel'), '\n',
|
||||
_('You will be able to pay once the channel is open.')
|
||||
])
|
||||
choices[1] = msg
|
||||
choices[2] = msg
|
||||
if can_pay_with_swap:
|
||||
msg = ''.join([
|
||||
_('Rebalance your channels with a submarine swap'), '\n',
|
||||
_('You will be able to pay once the swap is confirmed.')
|
||||
])
|
||||
choices[2] = msg
|
||||
choices[3] = msg
|
||||
if not choices:
|
||||
raise NotEnoughFunds()
|
||||
msg = _('You cannot pay that invoice using Lightning.')
|
||||
@@ -1720,11 +1758,15 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
||||
if r is not None:
|
||||
self.save_pending_invoice()
|
||||
if r == 0:
|
||||
self.pay_onchain_dialog(coins, invoice.get_outputs())
|
||||
chan1, chan2, delta = can_rebalance
|
||||
self.rebalance_dialog(chan1, chan2, amount_sat=delta)
|
||||
elif r == 1:
|
||||
self.channels_list.new_channel_dialog(amount_sat=channel_funding_sat)
|
||||
self.pay_onchain_dialog(coins, invoice.get_outputs())
|
||||
elif r == 2:
|
||||
self.run_swap_dialog(is_reverse=False, recv_amount_sat=swap_recv_amount_sat)
|
||||
self.channels_list.new_channel_dialog(amount_sat=channel_funding_sat)
|
||||
elif r == 3:
|
||||
chan, swap_recv_amount_sat = can_pay_with_swap
|
||||
self.run_swap_dialog(is_reverse=False, recv_amount_sat=swap_recv_amount_sat, channels=[chan])
|
||||
return
|
||||
|
||||
# FIXME this is currently lying to user as we truncate to satoshis
|
||||
@@ -1736,14 +1778,14 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
||||
coro = self.wallet.lnworker.pay_invoice(invoice.lightning_invoice, amount_msat=amount_msat)
|
||||
self.run_coroutine_from_thread(coro)
|
||||
|
||||
def run_swap_dialog(self, is_reverse=None, recv_amount_sat=None):
|
||||
def run_swap_dialog(self, is_reverse=None, recv_amount_sat=None, channels=None):
|
||||
if not self.network:
|
||||
self.window.show_error(_("You are offline."))
|
||||
return
|
||||
def get_pairs_thread():
|
||||
self.network.run_from_another_thread(self.wallet.lnworker.swap_manager.get_pairs())
|
||||
BlockingWaitingDialog(self, _('Please wait...'), get_pairs_thread)
|
||||
d = SwapDialog(self, is_reverse=is_reverse, recv_amount_sat=recv_amount_sat)
|
||||
d = SwapDialog(self, is_reverse=is_reverse, recv_amount_sat=recv_amount_sat, channels=channels)
|
||||
return d.run()
|
||||
|
||||
def on_request_status(self, wallet, key, status):
|
||||
@@ -2079,7 +2121,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
||||
|
||||
def query_choice(self, msg, choices):
|
||||
# Needed by QtHandler for hardware wallets
|
||||
dialog = WindowModalDialog(self.top_level_window())
|
||||
dialog = WindowModalDialog(self.top_level_window(), title='Question')
|
||||
clayout = ChoicesLayout(msg, choices)
|
||||
vbox = QVBoxLayout(dialog)
|
||||
vbox.addLayout(clayout.layout())
|
||||
|
||||
@@ -28,7 +28,7 @@ class SwapDialog(WindowModalDialog):
|
||||
|
||||
tx: Optional[PartialTransaction]
|
||||
|
||||
def __init__(self, window: 'ElectrumWindow', is_reverse=None, recv_amount_sat=None):
|
||||
def __init__(self, window: 'ElectrumWindow', is_reverse=None, recv_amount_sat=None, channels=None):
|
||||
WindowModalDialog.__init__(self, window, _('Submarine Swap'))
|
||||
self.window = window
|
||||
self.config = window.config
|
||||
@@ -36,6 +36,7 @@ class SwapDialog(WindowModalDialog):
|
||||
self.swap_manager = self.lnworker.swap_manager
|
||||
self.network = window.network
|
||||
self.tx = None # for the forward-swap only
|
||||
self.channels = channels
|
||||
self.is_reverse = is_reverse if is_reverse is not None else True
|
||||
vbox = QVBoxLayout(self)
|
||||
self.description_label = WWLabel(self.get_description())
|
||||
@@ -287,6 +288,7 @@ class SwapDialog(WindowModalDialog):
|
||||
expected_onchain_amount_sat=onchain_amount,
|
||||
password=password,
|
||||
tx=tx,
|
||||
channels=self.channels,
|
||||
)
|
||||
self.window.run_coroutine_from_thread(coro)
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ from . import constants, util
|
||||
from . import keystore
|
||||
from .util import profiler, chunks, OldTaskGroup
|
||||
from .invoices import Invoice, PR_UNPAID, PR_EXPIRED, PR_PAID, PR_INFLIGHT, PR_FAILED, PR_ROUTING, LN_EXPIRY_NEVER
|
||||
from .util import NetworkRetryManager, JsonRPCClient
|
||||
from .util import NetworkRetryManager, JsonRPCClient, NotEnoughFunds
|
||||
from .lnutil import LN_MAX_FUNDING_SAT
|
||||
from .keystore import BIP32_KeyStore
|
||||
from .bitcoin import COIN
|
||||
@@ -1998,7 +1998,7 @@ class LNWallet(LNWorker):
|
||||
"""calculate routing hints (BOLT-11 'r' field)"""
|
||||
routing_hints = []
|
||||
if channels is None:
|
||||
channels = list(self.get_channels_to_include_in_invoice(amount_msat))
|
||||
channels = list(self.get_channels_for_receiving(amount_msat))
|
||||
random.shuffle(channels) # let's not leak channel order
|
||||
scid_to_my_channels = {chan.short_channel_id: chan for chan in channels
|
||||
if chan.short_channel_id is not None}
|
||||
@@ -2052,29 +2052,50 @@ class LNWallet(LNWorker):
|
||||
chan.balance(LOCAL) if not chan.is_closed() and (chan.is_frozen_for_sending() if frozen else True) else 0
|
||||
for chan in self.channels.values())) / 1000
|
||||
|
||||
def num_sats_can_send(self) -> Decimal:
|
||||
can_send_dict = defaultdict(int)
|
||||
with self.lock:
|
||||
if self.channels:
|
||||
for c in self.channels.values():
|
||||
if c.is_active() and not c.is_frozen_for_sending():
|
||||
if not self.channel_db and not self.is_trampoline_peer(c.node_id):
|
||||
continue
|
||||
if self.channel_db:
|
||||
can_send_dict[0] += c.available_to_spend(LOCAL)
|
||||
else:
|
||||
can_send_dict[c.node_id] += c.available_to_spend(LOCAL)
|
||||
can_send = max(can_send_dict.values()) if can_send_dict else 0
|
||||
def get_channels_for_sending(self):
|
||||
for c in self.channels.values():
|
||||
if c.is_active() and not c.is_frozen_for_sending():
|
||||
if self.channel_db or self.is_trampoline_peer(c.node_id):
|
||||
yield c
|
||||
|
||||
def fee_estimate(self, amount_sat):
|
||||
# Here we have to guess a fee, because some callers (submarine swaps)
|
||||
# use this method to initiate a payment, which would otherwise fail.
|
||||
fee_base_msat = TRAMPOLINE_FEES[3]['fee_base_msat']
|
||||
fee_proportional_millionths = TRAMPOLINE_FEES[3]['fee_proportional_millionths']
|
||||
# inverse of fee_for_edge_msat
|
||||
can_send_minus_fees = (can_send - fee_base_msat) * 1_000_000 // ( 1_000_000 + fee_proportional_millionths)
|
||||
can_send_minus_fees = max(0, can_send_minus_fees)
|
||||
return Decimal(can_send_minus_fees) / 1000
|
||||
amount_msat = amount_sat * 1000
|
||||
amount_minus_fees = (amount_msat - fee_base_msat) * 1_000_000 // ( 1_000_000 + fee_proportional_millionths)
|
||||
return Decimal(amount_msat - amount_minus_fees) / 1000
|
||||
|
||||
def get_channels_to_include_in_invoice(self, amount_msat=None) -> Sequence[Channel]:
|
||||
def num_sats_can_send(self, deltas=None) -> Decimal:
|
||||
"""
|
||||
without trampoline, sum of all channel capacity
|
||||
with trampoline, MPP must use a single trampoline
|
||||
"""
|
||||
if deltas is None:
|
||||
deltas = {}
|
||||
def send_capacity(chan):
|
||||
if chan in deltas:
|
||||
delta_msat = deltas[chan] * 1000
|
||||
if delta_msat > chan.available_to_spend(REMOTE):
|
||||
delta_msat = 0
|
||||
else:
|
||||
delta_msat = 0
|
||||
return chan.available_to_spend(LOCAL) + delta_msat
|
||||
can_send_dict = defaultdict(int)
|
||||
with self.lock:
|
||||
for c in self.get_channels_for_sending():
|
||||
if self.channel_db:
|
||||
can_send_dict[0] += send_capacity(c)
|
||||
else:
|
||||
can_send_dict[c.node_id] += send_capacity(c)
|
||||
can_send = max(can_send_dict.values()) if can_send_dict else 0
|
||||
can_send_sat = Decimal(can_send)/1000
|
||||
can_send_sat -= self.fee_estimate(can_send_sat)
|
||||
return max(can_send_sat, 0)
|
||||
|
||||
def get_channels_for_receiving(self, amount_msat=None) -> Sequence[Channel]:
|
||||
if not amount_msat: # assume we want to recv a large amt, e.g. finding max.
|
||||
amount_msat = float('inf')
|
||||
with self.lock:
|
||||
@@ -2103,16 +2124,26 @@ class LNWallet(LNWorker):
|
||||
channels = channels[:10]
|
||||
return channels
|
||||
|
||||
def num_sats_can_receive(self) -> Decimal:
|
||||
def num_sats_can_receive(self, deltas=None) -> Decimal:
|
||||
"""Return a conservative estimate of max sat value we can realistically receive
|
||||
in a single payment. (MPP is allowed)
|
||||
|
||||
The theoretical max would be `sum(chan.available_to_spend(REMOTE) for chan in self.channels)`,
|
||||
but that would require a sender using MPP to magically guess all our channel liquidities.
|
||||
"""
|
||||
if deltas is None:
|
||||
deltas = {}
|
||||
def recv_capacity(chan):
|
||||
if chan in deltas:
|
||||
delta_msat = deltas[chan] * 1000
|
||||
if delta_msat > chan.available_to_spend(LOCAL):
|
||||
delta_msat = 0
|
||||
else:
|
||||
delta_msat = 0
|
||||
return chan.available_to_spend(REMOTE) + delta_msat
|
||||
with self.lock:
|
||||
recv_channels = self.get_channels_to_include_in_invoice()
|
||||
recv_chan_msats = [chan.available_to_spend(REMOTE) for chan in recv_channels]
|
||||
recv_channels = self.get_channels_for_receiving()
|
||||
recv_chan_msats = [recv_capacity(chan) for chan in recv_channels]
|
||||
if not recv_chan_msats:
|
||||
return Decimal(0)
|
||||
can_receive_msat = max(
|
||||
@@ -2121,6 +2152,90 @@ class LNWallet(LNWorker):
|
||||
)
|
||||
return Decimal(can_receive_msat) / 1000
|
||||
|
||||
def _suggest_channels_for_rebalance(self, direction, amount_sat) -> Sequence[Tuple[Channel, int]]:
|
||||
"""
|
||||
Suggest a channel and amount to send/receive with that channel, so that we will be able to receive/send amount_sat
|
||||
This is used when suggesting a swap or rebalance in order to receive a payment
|
||||
"""
|
||||
with self.lock:
|
||||
func = self.num_sats_can_send if direction == SENT else self.num_sats_can_receive
|
||||
delta = amount_sat - func()
|
||||
assert delta > 0
|
||||
delta += self.fee_estimate(amount_sat)
|
||||
# add safety margin, for example if channel reserves is not met
|
||||
# also covers swap server percentage fee
|
||||
delta += delta // 20
|
||||
suggestions = []
|
||||
channels = self.get_channels_for_sending() if direction == SENT else self.get_channels_for_receiving()
|
||||
for chan in channels:
|
||||
if func(deltas={chan:delta}) >= amount_sat:
|
||||
suggestions.append((chan, delta))
|
||||
if not suggestions:
|
||||
raise NotEnoughFunds
|
||||
return suggestions
|
||||
|
||||
def _suggest_rebalance(self, direction, amount_sat):
|
||||
"""
|
||||
Suggest a rebalance in order to be able to send or receive amount_sat.
|
||||
Returns (from_channel, to_channel, amount to shuffle)
|
||||
"""
|
||||
try:
|
||||
suggestions = self._suggest_channels_for_rebalance(direction, amount_sat)
|
||||
except NotEnoughFunds:
|
||||
return False
|
||||
for chan2, delta in suggestions:
|
||||
# margin for fee caused by rebalancing
|
||||
delta += self.fee_estimate(amount_sat)
|
||||
# find other channel or trampoline that can send delta
|
||||
for chan1 in self.channels.values():
|
||||
if chan1.is_frozen_for_sending() or not chan1.is_active():
|
||||
continue
|
||||
if chan1 == chan2:
|
||||
continue
|
||||
if not self.channel_db and chan1.node_id == chan2.node_id:
|
||||
continue
|
||||
if direction == SENT:
|
||||
if chan1.can_pay(delta*1000):
|
||||
return (chan1, chan2, delta)
|
||||
else:
|
||||
if chan1.can_receive(delta*1000):
|
||||
return (chan2, chan1, delta)
|
||||
else:
|
||||
continue
|
||||
else:
|
||||
return False
|
||||
|
||||
def suggest_rebalance_to_send(self, amount_sat):
|
||||
return self._suggest_rebalance(SENT, amount_sat)
|
||||
|
||||
def suggest_rebalance_to_receive(self, amount_sat):
|
||||
return self._suggest_rebalance(RECEIVED, amount_sat)
|
||||
|
||||
def suggest_swap_to_send(self, amount_sat, coins):
|
||||
# fixme: if swap_amount_sat is lower than the minimum swap amount, we need to propose a higher value
|
||||
assert amount_sat > self.num_sats_can_send()
|
||||
try:
|
||||
suggestions = self._suggest_channels_for_rebalance(SENT, amount_sat)
|
||||
except NotEnoughFunds:
|
||||
return
|
||||
for chan, swap_recv_amount in suggestions:
|
||||
# check that we can send onchain
|
||||
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_output = PartialTxOutput.from_address_and_value(ln_dummy_address(), int(swap_funding_sat))
|
||||
if not self.wallet.can_pay_onchain([swap_output], coins=coins):
|
||||
continue
|
||||
return (chan, swap_recv_amount)
|
||||
|
||||
def suggest_swap_to_receive(self, amount_sat):
|
||||
assert amount_sat > self.num_sats_can_receive()
|
||||
try:
|
||||
suggestions = self._suggest_channels_for_rebalance(RECEIVED, amount_sat)
|
||||
except NotEnoughFunds:
|
||||
return
|
||||
for chan, swap_recv_amount in suggestions:
|
||||
return (chan, swap_recv_amount)
|
||||
|
||||
async def rebalance_channels(self, chan1, chan2, amount_msat):
|
||||
lnaddr, invoice = self.create_invoice(
|
||||
amount_msat=amount_msat,
|
||||
|
||||
@@ -262,6 +262,7 @@ class SwapManager(Logger):
|
||||
expected_onchain_amount_sat: int,
|
||||
password,
|
||||
tx: PartialTransaction = None,
|
||||
channels = None,
|
||||
) -> str:
|
||||
"""send on-chain BTC, receive on Lightning
|
||||
|
||||
@@ -279,6 +280,7 @@ class SwapManager(Logger):
|
||||
message='swap',
|
||||
expiry=3600 * 24,
|
||||
fallback_address=None,
|
||||
channels=channels,
|
||||
)
|
||||
payment_hash = lnaddr.paymenthash
|
||||
preimage = self.lnworker.get_preimage(payment_hash)
|
||||
@@ -358,6 +360,7 @@ class SwapManager(Logger):
|
||||
*,
|
||||
lightning_amount_sat: int,
|
||||
expected_onchain_amount_sat: int,
|
||||
channels = None,
|
||||
) -> bool:
|
||||
"""send on Lightning, receive on-chain
|
||||
|
||||
@@ -457,7 +460,7 @@ class SwapManager(Logger):
|
||||
self.prepayments[prepay_hash] = preimage_hash
|
||||
asyncio.ensure_future(self.lnworker.pay_invoice(fee_invoice, attempts=10))
|
||||
# initiate payment.
|
||||
success, log = await self.lnworker.pay_invoice(invoice, attempts=10)
|
||||
success, log = await self.lnworker.pay_invoice(invoice, attempts=10, channels=channels)
|
||||
return success
|
||||
|
||||
def _add_or_reindex_swap(self, swap: SwapData) -> None:
|
||||
|
||||
@@ -243,7 +243,7 @@ class MockLNWallet(Logger, NetworkRetryManager[LNPeerAddr]):
|
||||
get_channel_by_id = LNWallet.get_channel_by_id
|
||||
channels_for_peer = LNWallet.channels_for_peer
|
||||
calc_routing_hints_for_invoice = LNWallet.calc_routing_hints_for_invoice
|
||||
get_channels_to_include_in_invoice = LNWallet.get_channels_to_include_in_invoice
|
||||
get_channels_for_receiving = LNWallet.get_channels_for_receiving
|
||||
handle_error_code_from_failed_htlc = LNWallet.handle_error_code_from_failed_htlc
|
||||
is_trampoline_peer = LNWallet.is_trampoline_peer
|
||||
wait_for_received_pending_htlcs_to_get_removed = LNWallet.wait_for_received_pending_htlcs_to_get_removed
|
||||
|
||||
@@ -82,7 +82,6 @@ from .logging import get_logger
|
||||
from .lnworker import LNWallet
|
||||
from .paymentrequest import PaymentRequest
|
||||
from .util import read_json_file, write_json_file, UserFacingException
|
||||
from .lnutil import ln_dummy_address
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .network import Network
|
||||
@@ -1359,25 +1358,6 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
||||
return False, None
|
||||
return True, channel_funding_sat
|
||||
|
||||
def can_pay_with_swap(self, amount_sat, coins=None):
|
||||
# fixme: if swap_amount_sat is lower than the minimum swap amount, we need to propose a higher value
|
||||
if self.lnworker is None:
|
||||
return False, None
|
||||
num_sats_can_send = int(self.lnworker.num_sats_can_send())
|
||||
if amount_sat <= num_sats_can_send:
|
||||
return True, None
|
||||
lightning_needed = amount_sat - num_sats_can_send
|
||||
lightning_needed += (lightning_needed // 20) # operational safety margin
|
||||
swap_recv_amount = lightning_needed # the server's percentage fee is presumably within the above margin
|
||||
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_output = PartialTxOutput.from_address_and_value(ln_dummy_address(), swap_funding_sat)
|
||||
can_do_swap_onchain = self.can_pay_onchain([swap_output], coins=coins)
|
||||
can_do_swap_lightning = self.lnworker.num_sats_can_receive() >= swap_recv_amount
|
||||
if can_do_swap_onchain and can_do_swap_lightning:
|
||||
return True, swap_recv_amount
|
||||
return False, None
|
||||
|
||||
def make_unsigned_transaction(
|
||||
self, *,
|
||||
coins: Sequence[PartialTxInput],
|
||||
|
||||
Reference in New Issue
Block a user