Browse Source

swaps: move fee logic to swap_manager, fix command line

bip39-recovery
ThomasV 5 years ago
parent
commit
5fa09970b6
  1. 56
      electrum/commands.py
  2. 68
      electrum/gui/qt/swap_dialog.py
  3. 86
      electrum/submarine_swaps.py

56
electrum/commands.py

@ -79,6 +79,8 @@ def satoshis(amount):
# satoshi conversion must not be performed by the parser
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):
# 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)
@command('wnp')
async def submarine_swap(self, amount, password=None, wallet: Abstract_Wallet = None):
return await wallet.lnworker.swap_manager.normal_swap(satoshis(amount), password)
async def normal_swap(self, onchain_amount, lightning_amount, password=None, wallet: Abstract_Wallet = None):
"""
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')
async def reverse_swap(self, amount, wallet: Abstract_Wallet = None):
return await wallet.lnworker.swap_manager.reverse_swap(satoshis(amount))
async def reverse_swap(self, lightning_amount, onchain_amount, wallet: Abstract_Wallet = None):
"""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:
@ -1135,6 +1181,8 @@ param_descriptions = {
'requested_amount': 'Requested amount (in BTC).',
'outputs': 'list of ["address", amount]',
'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 = {

68
electrum/gui/qt/swap_dialog.py

@ -29,12 +29,6 @@ class SwapDialog(WindowModalDialog):
self.config = window.config
self.swap_manager = self.window.wallet.lnworker.swap_manager
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.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)
@ -82,8 +76,6 @@ class SwapDialog(WindowModalDialog):
self.config.set_key('fee_level', pos, False)
else:
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:
self.on_recv_edited()
else:
@ -102,7 +94,7 @@ class SwapDialog(WindowModalDialog):
self.send_amount_e.setStyleSheet(ColorScheme.DEFAULT.as_stylesheet())
amount = self.send_amount_e.get_amount()
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.follows = False
self.send_follows = False
@ -113,72 +105,26 @@ class SwapDialog(WindowModalDialog):
self.recv_amount_e.setStyleSheet(ColorScheme.DEFAULT.as_stylesheet())
amount = self.recv_amount_e.get_amount()
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.follows = False
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):
sm = self.swap_manager
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"))
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.percentage_label.setText('%.2f'%self.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
self.percentage_label.setText('%.2f'%sm.percentage + '%')
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_():
return
if self.is_reverse:
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)
self.window.run_coroutine_from_thread(coro)
else:

86
electrum/submarine_swaps.py

@ -96,6 +96,23 @@ def create_claim_tx(txin, witness_script, preimage, privkey:bytes, address, amou
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
async def _claim_swap(self, swap):
if not self.lnwatcher.is_up_to_date():
@ -117,7 +134,7 @@ class SwapManager(Logger):
self.lnwatcher.remove_callback(swap.lockup_address)
swap.is_redeemed = True
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():
self.logger.info('utxo value below dust threshold')
continue
@ -128,21 +145,9 @@ class SwapManager(Logger):
# save 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)
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):
return self.swaps.get(payment_hash.hex())
@ -211,12 +216,7 @@ class SwapManager(Logger):
self.swaps[payment_hash.hex()] = swap
self.add_lnwatcher_callback(swap)
await self.network.broadcast_transaction(tx)
#
attempt = await self.lnworker.await_payment(payment_hash)
return {
'id':response_id,
'success':attempt.success,
}
return tx.txid()
@log_exceptions
async def reverse_swap(self, amount_sat, expected_amount):
@ -278,10 +278,7 @@ class SwapManager(Logger):
self.add_lnwatcher_callback(swap)
# initiate payment.
success, log = await self.lnworker._pay(invoice, attempts=10)
return {
'id':response_id,
'success':success,
}
return success
@log_exceptions
async def get_pairs(self):
@ -289,5 +286,42 @@ class SwapManager(Logger):
'get',
API_URL + '/getpairs',
timeout=30)
data = json.loads(response)
return data
pairs = json.loads(response)
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…
Cancel
Save