mirror of
https://github.com/LBRYFoundation/LBRY-Vault.git
synced 2025-09-13 05:59:51 +00:00
swaps: move fee logic to swap_manager, fix command line
This commit is contained in:
parent
3874f7ec77
commit
5fa09970b6
3 changed files with 119 additions and 91 deletions
|
@ -79,6 +79,8 @@ def satoshis(amount):
|
||||||
# satoshi conversion must not be performed by the parser
|
# satoshi conversion must not be performed by the parser
|
||||||
return int(COIN*Decimal(amount)) if amount not in ['!', None] else amount
|
return int(COIN*Decimal(amount)) if amount not in ['!', None] else amount
|
||||||
|
|
||||||
|
def format_satoshis(x):
|
||||||
|
return str(Decimal(x)/COIN) if x is not None else None
|
||||||
|
|
||||||
def json_normalize(x):
|
def json_normalize(x):
|
||||||
# note: The return value of commands, when going through the JSON-RPC interface,
|
# note: The return value of commands, when going through the JSON-RPC interface,
|
||||||
|
@ -1102,12 +1104,56 @@ class Commands:
|
||||||
return await self.network.local_watchtower.sweepstore.get_ctn(channel_point, None)
|
return await self.network.local_watchtower.sweepstore.get_ctn(channel_point, None)
|
||||||
|
|
||||||
@command('wnp')
|
@command('wnp')
|
||||||
async def submarine_swap(self, amount, password=None, wallet: Abstract_Wallet = None):
|
async def normal_swap(self, onchain_amount, lightning_amount, password=None, wallet: Abstract_Wallet = None):
|
||||||
return await wallet.lnworker.swap_manager.normal_swap(satoshis(amount), password)
|
"""
|
||||||
|
Normal submarine swap: send on-chain BTC, receive on Lightning
|
||||||
|
Note that your funds will be locked for 24h if you do not have enough incoming capacity.
|
||||||
|
"""
|
||||||
|
sm = wallet.lnworker.swap_manager
|
||||||
|
if lightning_amount == 'dryrun':
|
||||||
|
await sm.get_pairs()
|
||||||
|
onchain_amount_sat = satoshis(onchain_amount)
|
||||||
|
lightning_amount_sat = sm.get_recv_amount(onchain_amount_sat, is_reverse=False)
|
||||||
|
txid = None
|
||||||
|
elif onchain_amount == 'dryrun':
|
||||||
|
await sm.get_pairs()
|
||||||
|
lightning_amount_sat = satoshis(lightning_amount)
|
||||||
|
onchain_amount_sat = sm.get_send_amount(lightning_amount_sat, is_reverse=False)
|
||||||
|
txid = None
|
||||||
|
else:
|
||||||
|
lightning_amount_sat = satoshis(lightning_amount)
|
||||||
|
onchain_amount_sat = satoshis(onchain_amount)
|
||||||
|
txid = await wallet.lnworker.swap_manager.normal_swap(lightning_amount_sat, onchain_amount_sat, password)
|
||||||
|
return {
|
||||||
|
'txid': txid,
|
||||||
|
'lightning_amount': format_satoshis(lightning_amount_sat),
|
||||||
|
'onchain_amount': format_satoshis(onchain_amount_sat),
|
||||||
|
}
|
||||||
|
|
||||||
@command('wn')
|
@command('wn')
|
||||||
async def reverse_swap(self, amount, wallet: Abstract_Wallet = None):
|
async def reverse_swap(self, lightning_amount, onchain_amount, wallet: Abstract_Wallet = None):
|
||||||
return await wallet.lnworker.swap_manager.reverse_swap(satoshis(amount))
|
"""Reverse submarine swap: send on Lightning, receive on-chain
|
||||||
|
"""
|
||||||
|
sm = wallet.lnworker.swap_manager
|
||||||
|
if onchain_amount == 'dryrun':
|
||||||
|
await sm.get_pairs()
|
||||||
|
lightning_amount_sat = satoshis(lightning_amount)
|
||||||
|
onchain_amount_sat = sm.get_recv_amount(lightning_amount_sat, is_reverse=True)
|
||||||
|
success = None
|
||||||
|
elif lightning_amount == 'dryrun':
|
||||||
|
await sm.get_pairs()
|
||||||
|
onchain_amount_sat = satoshis(onchain_amount)
|
||||||
|
lightning_amount_sat = sm.get_send_amount(onchain_amount_sat, is_reverse=True)
|
||||||
|
success = None
|
||||||
|
else:
|
||||||
|
lightning_amount_sat = satoshis(lightning_amount)
|
||||||
|
onchain_amount_sat = satoshis(onchain_amount)
|
||||||
|
success = await wallet.lnworker.swap_manager.reverse_swap(lightning_amount_sat, onchain_amount_sat)
|
||||||
|
return {
|
||||||
|
'success': success,
|
||||||
|
'lightning_amount': format_satoshis(lightning_amount_sat),
|
||||||
|
'onchain_amount': format_satoshis(onchain_amount_sat),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def eval_bool(x: str) -> bool:
|
def eval_bool(x: str) -> bool:
|
||||||
|
@ -1135,6 +1181,8 @@ param_descriptions = {
|
||||||
'requested_amount': 'Requested amount (in BTC).',
|
'requested_amount': 'Requested amount (in BTC).',
|
||||||
'outputs': 'list of ["address", amount]',
|
'outputs': 'list of ["address", amount]',
|
||||||
'redeem_script': 'redeem script (hexadecimal)',
|
'redeem_script': 'redeem script (hexadecimal)',
|
||||||
|
'lightning_amount': "Amount sent or received in a submarine swap. Set it to 'dryrun' to receive a value",
|
||||||
|
'onchain_amount': "Amount sent or received in a submarine swap. Set it to 'dryrun' to receive a value",
|
||||||
}
|
}
|
||||||
|
|
||||||
command_options = {
|
command_options = {
|
||||||
|
|
|
@ -29,12 +29,6 @@ class SwapDialog(WindowModalDialog):
|
||||||
self.config = window.config
|
self.config = window.config
|
||||||
self.swap_manager = self.window.wallet.lnworker.swap_manager
|
self.swap_manager = self.window.wallet.lnworker.swap_manager
|
||||||
self.network = window.network
|
self.network = window.network
|
||||||
self.normal_fee = 0
|
|
||||||
self.lockup_fee = 0
|
|
||||||
self.claim_fee = self.swap_manager.get_tx_fee()
|
|
||||||
self.percentage = 0
|
|
||||||
self.min_amount = 0
|
|
||||||
self.max_amount = 0
|
|
||||||
vbox = QVBoxLayout(self)
|
vbox = QVBoxLayout(self)
|
||||||
vbox.addWidget(WWLabel('Swap lightning funds for on-chain funds if you need to increase your receiving capacity. This service is powered by the Boltz backend.'))
|
vbox.addWidget(WWLabel('Swap lightning funds for on-chain funds if you need to increase your receiving capacity. This service is powered by the Boltz backend.'))
|
||||||
self.send_amount_e = BTCAmountEdit(self.window.get_decimal_point)
|
self.send_amount_e = BTCAmountEdit(self.window.get_decimal_point)
|
||||||
|
@ -82,8 +76,6 @@ class SwapDialog(WindowModalDialog):
|
||||||
self.config.set_key('fee_level', pos, False)
|
self.config.set_key('fee_level', pos, False)
|
||||||
else:
|
else:
|
||||||
self.config.set_key('fee_per_kb', fee_rate, False)
|
self.config.set_key('fee_per_kb', fee_rate, False)
|
||||||
# read claim_fee from config
|
|
||||||
self.claim_fee = self.swap_manager.get_tx_fee()
|
|
||||||
if self.send_follows:
|
if self.send_follows:
|
||||||
self.on_recv_edited()
|
self.on_recv_edited()
|
||||||
else:
|
else:
|
||||||
|
@ -102,7 +94,7 @@ class SwapDialog(WindowModalDialog):
|
||||||
self.send_amount_e.setStyleSheet(ColorScheme.DEFAULT.as_stylesheet())
|
self.send_amount_e.setStyleSheet(ColorScheme.DEFAULT.as_stylesheet())
|
||||||
amount = self.send_amount_e.get_amount()
|
amount = self.send_amount_e.get_amount()
|
||||||
self.recv_amount_e.follows = True
|
self.recv_amount_e.follows = True
|
||||||
self.recv_amount_e.setAmount(self.get_recv_amount(amount))
|
self.recv_amount_e.setAmount(self.swap_manager.get_recv_amount(amount, self.is_reverse))
|
||||||
self.recv_amount_e.setStyleSheet(ColorScheme.BLUE.as_stylesheet())
|
self.recv_amount_e.setStyleSheet(ColorScheme.BLUE.as_stylesheet())
|
||||||
self.recv_amount_e.follows = False
|
self.recv_amount_e.follows = False
|
||||||
self.send_follows = False
|
self.send_follows = False
|
||||||
|
@ -113,72 +105,26 @@ class SwapDialog(WindowModalDialog):
|
||||||
self.recv_amount_e.setStyleSheet(ColorScheme.DEFAULT.as_stylesheet())
|
self.recv_amount_e.setStyleSheet(ColorScheme.DEFAULT.as_stylesheet())
|
||||||
amount = self.recv_amount_e.get_amount()
|
amount = self.recv_amount_e.get_amount()
|
||||||
self.send_amount_e.follows = True
|
self.send_amount_e.follows = True
|
||||||
self.send_amount_e.setAmount(self.get_send_amount(amount))
|
self.send_amount_e.setAmount(self.swap_manager.get_send_amount(amount, self.is_reverse))
|
||||||
self.send_amount_e.setStyleSheet(ColorScheme.BLUE.as_stylesheet())
|
self.send_amount_e.setStyleSheet(ColorScheme.BLUE.as_stylesheet())
|
||||||
self.send_amount_e.follows = False
|
self.send_amount_e.follows = False
|
||||||
self.send_follows = True
|
self.send_follows = True
|
||||||
|
|
||||||
def on_pairs(self, pairs):
|
|
||||||
fees = pairs['pairs']['BTC/BTC']['fees']
|
|
||||||
self.percentage = fees['percentage']
|
|
||||||
self.normal_fee = fees['minerFees']['baseAsset']['normal']
|
|
||||||
self.lockup_fee = fees['minerFees']['baseAsset']['reverse']['lockup']
|
|
||||||
#self.claim_fee = fees['minerFees']['baseAsset']['reverse']['claim']
|
|
||||||
limits = pairs['pairs']['BTC/BTC']['limits']
|
|
||||||
self.min_amount = limits['minimal']
|
|
||||||
self.max_amount = limits['maximal']
|
|
||||||
self.update()
|
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
|
sm = self.swap_manager
|
||||||
self.send_button.setIcon(read_QIcon("lightning.png" if self.is_reverse else "bitcoin.png"))
|
self.send_button.setIcon(read_QIcon("lightning.png" if self.is_reverse else "bitcoin.png"))
|
||||||
self.recv_button.setIcon(read_QIcon("lightning.png" if not self.is_reverse else "bitcoin.png"))
|
self.recv_button.setIcon(read_QIcon("lightning.png" if not self.is_reverse else "bitcoin.png"))
|
||||||
fee = self.lockup_fee + self.claim_fee if self.is_reverse else self.normal_fee
|
fee = sm.lockup_fee + sm.get_claim_fee() if self.is_reverse else sm.normal_fee
|
||||||
self.fee_label.setText(self.window.format_amount(fee) + ' ' + self.window.base_unit())
|
self.fee_label.setText(self.window.format_amount(fee) + ' ' + self.window.base_unit())
|
||||||
self.percentage_label.setText('%.2f'%self.percentage + '%')
|
self.percentage_label.setText('%.2f'%sm.percentage + '%')
|
||||||
|
|
||||||
def set_minimum(self):
|
|
||||||
self.send_amount_e.setAmount(self.min_amount)
|
|
||||||
|
|
||||||
def set_maximum(self):
|
|
||||||
self.send_amount_e.setAmount(self.max_amount)
|
|
||||||
|
|
||||||
def get_recv_amount(self, send_amount):
|
|
||||||
if send_amount is None:
|
|
||||||
return
|
|
||||||
if send_amount < self.min_amount or send_amount > self.max_amount:
|
|
||||||
return
|
|
||||||
x = send_amount
|
|
||||||
if self.is_reverse:
|
|
||||||
x = int(x * (100 - self.percentage) / 100)
|
|
||||||
x -= self.lockup_fee
|
|
||||||
x -= self.claim_fee
|
|
||||||
else:
|
|
||||||
x -= self.normal_fee
|
|
||||||
x = int(x * (100 - self.percentage) / 100)
|
|
||||||
if x < 0:
|
|
||||||
return
|
|
||||||
return x
|
|
||||||
|
|
||||||
def get_send_amount(self, recv_amount):
|
|
||||||
if not recv_amount:
|
|
||||||
return
|
|
||||||
x = recv_amount
|
|
||||||
if self.is_reverse:
|
|
||||||
x += self.lockup_fee
|
|
||||||
x += self.claim_fee
|
|
||||||
x = int(x * 100 / (100 - self.percentage)) + 1
|
|
||||||
else:
|
|
||||||
x = int(x * 100 / (100 - self.percentage)) + 1
|
|
||||||
x += self.normal_fee
|
|
||||||
return x
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.window.run_coroutine_from_thread(self.swap_manager.get_pairs(), self.on_pairs)
|
self.window.run_coroutine_from_thread(self.swap_manager.get_pairs(), lambda x: self.update())
|
||||||
if not self.exec_():
|
if not self.exec_():
|
||||||
return
|
return
|
||||||
if self.is_reverse:
|
if self.is_reverse:
|
||||||
lightning_amount = self.send_amount_e.get_amount()
|
lightning_amount = self.send_amount_e.get_amount()
|
||||||
onchain_amount = self.recv_amount_e.get_amount() + self.claim_fee
|
onchain_amount = self.recv_amount_e.get_amount() + self.swap_manager.get_claim_fee()
|
||||||
coro = self.swap_manager.reverse_swap(lightning_amount, onchain_amount)
|
coro = self.swap_manager.reverse_swap(lightning_amount, onchain_amount)
|
||||||
self.window.run_coroutine_from_thread(coro)
|
self.window.run_coroutine_from_thread(coro)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -96,6 +96,23 @@ def create_claim_tx(txin, witness_script, preimage, privkey:bytes, address, amou
|
||||||
|
|
||||||
class SwapManager(Logger):
|
class SwapManager(Logger):
|
||||||
|
|
||||||
|
def __init__(self, wallet: 'Abstract_Wallet', network:'Network'):
|
||||||
|
Logger.__init__(self)
|
||||||
|
self.normal_fee = 0
|
||||||
|
self.lockup_fee = 0
|
||||||
|
self.percentage = 0
|
||||||
|
self.min_amount = 0
|
||||||
|
self.max_amount = 0
|
||||||
|
self.network = network
|
||||||
|
self.wallet = wallet
|
||||||
|
self.lnworker = wallet.lnworker
|
||||||
|
self.lnwatcher = self.wallet.lnworker.lnwatcher
|
||||||
|
self.swaps = self.wallet.db.get_dict('submarine_swaps')
|
||||||
|
for swap in self.swaps.values():
|
||||||
|
if swap.is_redeemed:
|
||||||
|
continue
|
||||||
|
self.add_lnwatcher_callback(swap)
|
||||||
|
|
||||||
@log_exceptions
|
@log_exceptions
|
||||||
async def _claim_swap(self, swap):
|
async def _claim_swap(self, swap):
|
||||||
if not self.lnwatcher.is_up_to_date():
|
if not self.lnwatcher.is_up_to_date():
|
||||||
|
@ -117,7 +134,7 @@ class SwapManager(Logger):
|
||||||
self.lnwatcher.remove_callback(swap.lockup_address)
|
self.lnwatcher.remove_callback(swap.lockup_address)
|
||||||
swap.is_redeemed = True
|
swap.is_redeemed = True
|
||||||
continue
|
continue
|
||||||
amount_sat = txin._trusted_value_sats - self.get_tx_fee()
|
amount_sat = txin._trusted_value_sats - self.get_claim_fee()
|
||||||
if amount_sat < dust_threshold():
|
if amount_sat < dust_threshold():
|
||||||
self.logger.info('utxo value below dust threshold')
|
self.logger.info('utxo value below dust threshold')
|
||||||
continue
|
continue
|
||||||
|
@ -128,21 +145,9 @@ class SwapManager(Logger):
|
||||||
# save txid
|
# save txid
|
||||||
swap.spending_txid = tx.txid()
|
swap.spending_txid = tx.txid()
|
||||||
|
|
||||||
def get_tx_fee(self):
|
def get_claim_fee(self):
|
||||||
return self.lnwatcher.config.estimate_fee(136, allow_fallback_to_static_rates=True)
|
return self.lnwatcher.config.estimate_fee(136, allow_fallback_to_static_rates=True)
|
||||||
|
|
||||||
def __init__(self, wallet: 'Abstract_Wallet', network:'Network'):
|
|
||||||
Logger.__init__(self)
|
|
||||||
self.network = network
|
|
||||||
self.wallet = wallet
|
|
||||||
self.lnworker = wallet.lnworker
|
|
||||||
self.lnwatcher = self.wallet.lnworker.lnwatcher
|
|
||||||
self.swaps = self.wallet.db.get_dict('submarine_swaps')
|
|
||||||
for swap in self.swaps.values():
|
|
||||||
if swap.is_redeemed:
|
|
||||||
continue
|
|
||||||
self.add_lnwatcher_callback(swap)
|
|
||||||
|
|
||||||
def get_swap(self, payment_hash):
|
def get_swap(self, payment_hash):
|
||||||
return self.swaps.get(payment_hash.hex())
|
return self.swaps.get(payment_hash.hex())
|
||||||
|
|
||||||
|
@ -211,12 +216,7 @@ class SwapManager(Logger):
|
||||||
self.swaps[payment_hash.hex()] = swap
|
self.swaps[payment_hash.hex()] = swap
|
||||||
self.add_lnwatcher_callback(swap)
|
self.add_lnwatcher_callback(swap)
|
||||||
await self.network.broadcast_transaction(tx)
|
await self.network.broadcast_transaction(tx)
|
||||||
#
|
return tx.txid()
|
||||||
attempt = await self.lnworker.await_payment(payment_hash)
|
|
||||||
return {
|
|
||||||
'id':response_id,
|
|
||||||
'success':attempt.success,
|
|
||||||
}
|
|
||||||
|
|
||||||
@log_exceptions
|
@log_exceptions
|
||||||
async def reverse_swap(self, amount_sat, expected_amount):
|
async def reverse_swap(self, amount_sat, expected_amount):
|
||||||
|
@ -278,10 +278,7 @@ class SwapManager(Logger):
|
||||||
self.add_lnwatcher_callback(swap)
|
self.add_lnwatcher_callback(swap)
|
||||||
# initiate payment.
|
# initiate payment.
|
||||||
success, log = await self.lnworker._pay(invoice, attempts=10)
|
success, log = await self.lnworker._pay(invoice, attempts=10)
|
||||||
return {
|
return success
|
||||||
'id':response_id,
|
|
||||||
'success':success,
|
|
||||||
}
|
|
||||||
|
|
||||||
@log_exceptions
|
@log_exceptions
|
||||||
async def get_pairs(self):
|
async def get_pairs(self):
|
||||||
|
@ -289,5 +286,42 @@ class SwapManager(Logger):
|
||||||
'get',
|
'get',
|
||||||
API_URL + '/getpairs',
|
API_URL + '/getpairs',
|
||||||
timeout=30)
|
timeout=30)
|
||||||
data = json.loads(response)
|
pairs = json.loads(response)
|
||||||
return data
|
fees = pairs['pairs']['BTC/BTC']['fees']
|
||||||
|
self.percentage = fees['percentage']
|
||||||
|
self.normal_fee = fees['minerFees']['baseAsset']['normal']
|
||||||
|
self.lockup_fee = fees['minerFees']['baseAsset']['reverse']['lockup']
|
||||||
|
limits = pairs['pairs']['BTC/BTC']['limits']
|
||||||
|
self.min_amount = limits['minimal']
|
||||||
|
self.max_amount = limits['maximal']
|
||||||
|
|
||||||
|
def get_recv_amount(self, send_amount, is_reverse):
|
||||||
|
if send_amount is None:
|
||||||
|
return
|
||||||
|
if send_amount < self.min_amount or send_amount > self.max_amount:
|
||||||
|
return
|
||||||
|
x = send_amount
|
||||||
|
if is_reverse:
|
||||||
|
x = int(x * (100 - self.percentage) / 100)
|
||||||
|
x -= self.lockup_fee
|
||||||
|
x -= self.get_claim_fee()
|
||||||
|
else:
|
||||||
|
x -= self.normal_fee
|
||||||
|
x = int(x * (100 - self.percentage) / 100)
|
||||||
|
if x < 0:
|
||||||
|
return
|
||||||
|
return x
|
||||||
|
|
||||||
|
def get_send_amount(self, recv_amount, is_reverse):
|
||||||
|
if not recv_amount:
|
||||||
|
return
|
||||||
|
x = recv_amount
|
||||||
|
if is_reverse:
|
||||||
|
x += self.lockup_fee
|
||||||
|
x += self.get_claim_fee()
|
||||||
|
x = int(x * 100 / (100 - self.percentage)) + 1
|
||||||
|
else:
|
||||||
|
x = int(x * 100 / (100 - self.percentage)) + 1
|
||||||
|
x += self.normal_fee
|
||||||
|
return x
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue