From b3c00efe1652074b5578a5b7997bee4a9174653e Mon Sep 17 00:00:00 2001 From: tecnovert Date: Wed, 16 Dec 2020 23:19:39 +0200 Subject: [PATCH] gui: Added confirm step and fee options. Fee options only apply to the script coin side of xmr swaps. --- basicswap/basicswap.py | 46 ++++--- basicswap/chainparams.py | 4 +- basicswap/http_server.py | 182 +++++++++++++++++++++---- basicswap/interface_btc.py | 8 +- basicswap/interface_xmr.py | 4 +- basicswap/templates/bid.html | 2 + basicswap/templates/bid_xmr.html | 10 +- basicswap/templates/offer.html | 4 +- basicswap/templates/offer_confirm.html | 67 +++++++++ basicswap/templates/offer_new.html | 34 +++-- basicswap/ui.py | 1 + doc/upgrade.md | 24 +++- 12 files changed, 317 insertions(+), 69 deletions(-) create mode 100644 basicswap/templates/offer_confirm.html diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index afec760..92c25ea 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -970,7 +970,7 @@ class BasicSwap(BaseApp): raise ValueError('Unknown locktype') def postOffer(self, coin_from, coin_to, amount, rate, min_bid_amount, swap_type, - lock_type=SEQUENCE_LOCK_TIME, lock_value=48 * 60 * 60, auto_accept_bids=False, addr_send_from=None): + lock_type=SEQUENCE_LOCK_TIME, lock_value=48 * 60 * 60, auto_accept_bids=False, addr_send_from=None, extra_options={}): # Offer to send offer.amount_from of coin_from in exchange for offer.amount_from * offer.rate of coin_to assert(coin_from != coin_to), 'coin_from == coin_to' @@ -1009,6 +1009,30 @@ class BasicSwap(BaseApp): msg_buf.lock_value = lock_value msg_buf.swap_type = swap_type + if 'from_fee_override' in extra_options: + msg_buf.fee_rate_from = make_int(extra_options['from_fee_override'], self.ci(coin_from).exp()) + else: + # TODO: conf_target = ci_from.settings.get('conf_target', 2) + conf_target = 2 + if 'from_fee_conf_target' in extra_options: + conf_target = extra_options['from_fee_conf_target'] + fee_rate, fee_src = self.getFeeRateForCoin(coin_from, conf_target) + if 'from_fee_multiplier_percent' in extra_options: + fee_rate *= extra_options['fee_multiplier'] / 100.0 + msg_buf.fee_rate_from = make_int(fee_rate, self.ci(coin_from).exp()) + + if 'to_fee_override' in extra_options: + msg_buf.fee_rate_to = make_int(extra_options['to_fee_override'], self.ci(coin_to).exp()) + else: + # TODO: conf_target = ci_to.settings.get('conf_target', 2) + conf_target = 2 + if 'to_fee_conf_target' in extra_options: + conf_target = extra_options['to_fee_conf_target'] + fee_rate, fee_src = self.getFeeRateForCoin(coin_to, conf_target) + if 'to_fee_multiplier_percent' in extra_options: + fee_rate *= extra_options['fee_multiplier'] / 100.0 + msg_buf.fee_rate_to = make_int(fee_rate, self.ci(coin_to).exp()) + if swap_type == SwapTypes.XMR_SWAP: xmr_offer = XmrOffer() @@ -1018,18 +1042,8 @@ class BasicSwap(BaseApp): # Delay before the follower can spend from the chain a lock refund tx xmr_offer.lock_time_2 = getExpectedSequence(lock_type, lock_value, coin_from) - # TODO: max fee warning? - chain_client_settings = self.getChainClientSettings(coin_from) - lock_tx_fee_premium = chain_client_settings.get('lock_tx_fee_premium', 0.0) - - xmr_offer.a_fee_rate = make_int(self.getFeeRateForCoin(coin_from) + lock_tx_fee_premium, self.ci(coin_from).exp()) - - # Unused: TODO - Set priority? - # xmr_offer.b_fee_rate = make_int(0.0012595, self.ci(coin_to).exp()) - xmr_offer.b_fee_rate = make_int(0.00002, self.ci(coin_to).exp()) # abs fee - - msg_buf.fee_rate_from = xmr_offer.a_fee_rate - msg_buf.fee_rate_to = xmr_offer.b_fee_rate + xmr_offer.a_fee_rate = msg_buf.fee_rate_from + xmr_offer.b_fee_rate = msg_buf.fee_rate_to # Unused: TODO - Set priority? offer_bytes = msg_buf.SerializeToString() payload_hex = str.format('{:02x}', MessageTypes.OFFER) + offer_bytes.hex() @@ -1243,13 +1257,13 @@ class BasicSwap(BaseApp): def getRelayFeeRateForCoin(self, coin_type): return self.callcoinrpc(coin_type, 'getnetworkinfo')['relayfee'] - def getFeeRateForCoin(self, coin_type): + def getFeeRateForCoin(self, coin_type, conf_target=2): override_feerate = self.coin_clients[coin_type].get('override_feerate', None) if override_feerate: self.log.debug('Fee rate override used for %s: %f', str(coin_type), override_feerate) - return override_feerate + return override_feerate, 'override_feerate' - return self.ci(coin_type).get_fee_rate() + return self.ci(coin_type).get_fee_rate(conf_target) def estimateWithdrawFee(self, coin_type, fee_rate): if coin_type == Coins.XMR: diff --git a/basicswap/chainparams.py b/basicswap/chainparams.py index 27eee4b..0bb1477 100644 --- a/basicswap/chainparams.py +++ b/basicswap/chainparams.py @@ -203,8 +203,8 @@ class CoinInterface: def __init__(self): self._unknown_wallet_seed = True - def make_int(self, amount_in): - return make_int(amount_in, self.exp()) + def make_int(self, amount_in, r=0): + return make_int(amount_in, self.exp(), r=r) def format_amount(self, amount_in, conv_int=False): amount_int = make_int(amount_in, self.exp()) if conv_int else amount_in diff --git a/basicswap/http_server.py b/basicswap/http_server.py index 349a83e..56aaa83 100644 --- a/basicswap/http_server.py +++ b/basicswap/http_server.py @@ -17,7 +17,6 @@ from jinja2 import Environment, PackageLoader from . import __version__ from .util import ( dumpj, - make_int, ) from .chainparams import ( chainparams, @@ -300,61 +299,186 @@ class HttpHandler(BaseHTTPRequestHandler): form_id=os.urandom(8).hex(), ), 'UTF-8') - def postNewOffer(self, form_data): + def parseOfferFormData(self, form_data): swap_client = self.server.swap_client - addr_from = form_data[b'addr_from'][0].decode('utf-8') - if addr_from == '-1': - addr_from = None + + errors = [] + page_data = {} + parsed_data = {} + + if b'addr_from' in form_data: + page_data['addr_from'] = form_data[b'addr_from'][0].decode('utf-8') + parsed_data['addr_from'] = None if page_data['addr_from'] == '-1' else page_data['addr_from'] try: - coin_from = Coins(int(form_data[b'coin_from'][0])) + page_data['coin_from'] = int(form_data[b'coin_from'][0]) + coin_from = Coins(page_data['coin_from']) ci_from = swap_client.ci(coin_from) + parsed_data['coin_from'] = coin_from except Exception: - raise ValueError('Unknown Coin From') + errors.append('Unknown Coin From') + try: - coin_to = Coins(int(form_data[b'coin_to'][0])) + page_data['coin_to'] = int(form_data[b'coin_to'][0]) + coin_to = Coins(page_data['coin_to']) ci_to = swap_client.ci(coin_to) + parsed_data['coin_to'] = coin_to + if coin_to == Coins.XMR: + page_data['swap_style'] = 'xmr' except Exception: - raise ValueError('Unknown Coin To') + errors.append('Unknown Coin To') - value_from = inputAmount(form_data[b'amt_from'][0].decode('utf-8'), ci_from) - value_to = inputAmount(form_data[b'amt_to'][0].decode('utf-8'), ci_to) + page_data['fee_from_conf'] = int(form_data[b'fee_from_conf'][0]) + page_data['fee_from_extra'] = int(form_data[b'fee_from_extra'][0]) - min_bid = int(value_from) - rate = int((value_to / value_from) * ci_from.COIN()) - autoaccept = True if b'autoaccept' in form_data else False - lock_seconds = int(form_data[b'lockhrs'][0]) * 60 * 60 - # TODO: More accurate rate - # assert(value_to == (value_from * rate) // COIN) + parsed_data['fee_from_conf'] = page_data['fee_from_conf'] + parsed_data['fee_from_extra'] = page_data['fee_from_extra'] - if swap_client.coin_clients[coin_from]['use_csv'] and swap_client.coin_clients[coin_to]['use_csv']: + page_data['fee_to_conf'] = int(form_data[b'fee_to_conf'][0]) + page_data['fee_to_extra'] = int(form_data[b'fee_to_extra'][0]) + + parsed_data['fee_to_conf'] = page_data['fee_to_conf'] + parsed_data['fee_to_extra'] = page_data['fee_to_extra'] + + if b'check_offer' in form_data: + page_data['check_offer'] = True + if b'submit_offer' in form_data: + page_data['submit_offer'] = True + + try: + page_data['amt_from'] = form_data[b'amt_from'][0].decode('utf-8') + parsed_data['amt_from'] = inputAmount(page_data['amt_from'], ci_from) + parsed_data['min_bid'] = int(parsed_data['amt_from']) + except Exception as e: + errors.append('Amount From') + + try: + page_data['amt_to'] = form_data[b'amt_to'][0].decode('utf-8') + parsed_data['amt_to'] = inputAmount(page_data['amt_to'], ci_to) + except Exception as e: + errors.append('Amount To') + + if 'amt_to' in parsed_data and 'amt_from' in parsed_data: + parsed_data['rate'] = int((parsed_data['amt_to'] / parsed_data['amt_from']) * ci_from.COIN()) + + page_data['lockhrs'] = int(form_data[b'lockhrs'][0]) + parsed_data['lock_seconds'] = page_data['lockhrs'] * 60 * 60 + + page_data['autoaccept'] = True if b'autoaccept' in form_data else False + parsed_data['autoaccept'] = page_data['autoaccept'] + + if len(errors) == 0: + if b'fee_rate_from' in form_data: + page_data['from_fee_override'] = form_data[b'fee_rate_from'][0].decode('utf-8') + parsed_data['from_fee_override'] = page_data['from_fee_override'] + else: + from_fee_override, page_data['from_fee_src'] = swap_client.getFeeRateForCoin(parsed_data['coin_from'], page_data['fee_from_conf']) + if page_data['fee_from_extra'] > 0: + from_fee_override += from_fee_override * (float(page_data['fee_from_extra']) / 100.0) + page_data['from_fee_override'] = ci_from.format_amount(ci_from.make_int(from_fee_override, r=1)) + parsed_data['from_fee_override'] = page_data['from_fee_override'] + + if b'fee_rate_to' in form_data: + page_data['to_fee_override'] = form_data[b'fee_rate_to'][0].decode('utf-8') + parsed_data['to_fee_override'] = page_data['to_fee_override'] + else: + to_fee_override, page_data['to_fee_src'] = swap_client.getFeeRateForCoin(parsed_data['coin_to'], page_data['fee_to_conf']) + if page_data['fee_to_extra'] > 0: + to_fee_override += to_fee_override * (float(page_data['fee_to_extra']) / 100.0) + + page_data['to_fee_override'] = ci_to.format_amount(ci_to.make_int(to_fee_override, r=1)) + parsed_data['to_fee_override'] = page_data['to_fee_override'] + + return page_data, parsed_data, errors + + def postNewOfferFromParsed(self, parsed_data): + swap_client = self.server.swap_client + + swap_type = SwapTypes.SELLER_FIRST + if parsed_data['coin_to'] == Coins.XMR: + swap_type = SwapTypes.XMR_SWAP + + if swap_client.coin_clients[parsed_data['coin_from']]['use_csv'] and swap_client.coin_clients[parsed_data['coin_to']]['use_csv']: lock_type = SEQUENCE_LOCK_TIME else: lock_type = ABS_LOCK_TIME - swap_type = SwapTypes.SELLER_FIRST - if coin_to == Coins.XMR: - swap_type = SwapTypes.XMR_SWAP + extra_options = {} - offer_id = swap_client.postOffer(coin_from, coin_to, value_from, rate, min_bid, swap_type, lock_type=lock_type, lock_value=lock_seconds, auto_accept_bids=autoaccept, addr_send_from=addr_from) + if 'fee_from_conf' in parsed_data: + extra_options['from_fee_conf_target'] = parsed_data['fee_from_conf'] + if 'from_fee_multiplier_percent' in parsed_data: + extra_options['from_fee_multiplier_percent'] = parsed_data['fee_from_extra'] + if 'from_fee_override' in parsed_data: + extra_options['from_fee_override'] = parsed_data['from_fee_override'] + + if 'fee_to_conf' in parsed_data: + extra_options['to_fee_conf_target'] = parsed_data['fee_to_conf'] + if 'to_fee_multiplier_percent' in parsed_data: + extra_options['to_fee_multiplier_percent'] = parsed_data['fee_to_extra'] + if 'to_fee_override' in parsed_data: + extra_options['to_fee_override'] = parsed_data['to_fee_override'] + + offer_id = swap_client.postOffer( + parsed_data['coin_from'], + parsed_data['coin_to'], + parsed_data['amt_from'], + parsed_data['rate'], + parsed_data['min_bid'], + swap_type, + lock_type=lock_type, + lock_value=parsed_data['lock_seconds'], + auto_accept_bids=parsed_data['autoaccept'], + addr_send_from=parsed_data['addr_from'], + extra_options=extra_options) return offer_id + def postNewOffer(self, form_data): + page_data, parsed_data = self.parseOfferFormData(form_data) + return self.postNewOfferFromParsed(parsed_data) + def page_newoffer(self, url_split, post_string): swap_client = self.server.swap_client messages = [] + page_data = {} form_data = self.checkForm(post_string, 'newoffer', messages) - if form_data: - offer_id = self.postNewOffer(form_data) - messages.append('Sent Offer {}'.format(offer_id.hex())) - template = env.get_template('offer_new.html') + if form_data: + try: + page_data, parsed_data, errors = self.parseOfferFormData(form_data) + for e in errors: + messages.append('Error: {}'.format(str(e))) + except Exception as e: + messages.append('Error: {}'.format(str(e))) + + if len(messages) == 0 and 'submit_offer' in page_data: + try: + offer_id = self.postNewOfferFromParsed(parsed_data) + messages.append('Sent Offer {}'.format(offer_id.hex())) + page_data = {} + except Exception as e: + messages.append('Error: {}'.format(str(e))) + + if not page_data: + # Set defaults + page_data['fee_from_conf'] = 2 + page_data['fee_to_conf'] = 2 + page_data['lockhrs'] = 32 + page_data['autoaccept'] = True + + if len(messages) == 0 and 'check_offer' in page_data: + template = env.get_template('offer_confirm.html') + else: + template = env.get_template('offer_new.html') + return bytes(template.render( title=self.server.title, h2=self.server.title, messages=messages, coins=listAvailableCoins(swap_client), addrs=swap_client.listSmsgAddresses('offer'), + data=page_data, form_id=os.urandom(8).hex(), ), 'UTF-8') @@ -412,10 +536,12 @@ class HttpHandler(BaseHTTPRequestHandler): } if xmr_offer: - int_fee_rate_now = make_int(ci_from.get_fee_rate(), ci_from.exp()) + + int_fee_rate_now, fee_source = ci_from.get_fee_rate() data['xmr_type'] = True data['a_fee_rate'] = ci_from.format_amount(xmr_offer.a_fee_rate) - data['a_fee_rate_verify'] = ci_from.format_amount(int_fee_rate_now) + data['a_fee_rate_verify'] = ci_from.format_amount(int_fee_rate_now, conv_int=True) + data['a_fee_rate_verify_src'] = fee_source data['a_fee_warn'] = xmr_offer.a_fee_rate < int_fee_rate_now if offer.was_sent: diff --git a/basicswap/interface_btc.py b/basicswap/interface_btc.py index 5e9b193..98c4eb2 100644 --- a/basicswap/interface_btc.py +++ b/basicswap/interface_btc.py @@ -156,16 +156,16 @@ class BTCInterface(CoinInterface): args.append('bech32') return self.rpc_callback('getnewaddress', args) - def get_fee_rate(self): + def get_fee_rate(self, conf_target=2): try: - return self.rpc_callback('estimatesmartfee', [2])['feerate'] + return self.rpc_callback('estimatesmartfee', [conf_target])['feerate'], 'estimatesmartfee' except Exception: try: - fee_rate = self.rpc_callback('getwalletinfo')['paytxfee'] + fee_rate = self.rpc_callback('getwalletinfo')['paytxfee'], 'paytxfee' assert(fee_rate > 0.0), '0 feerate' return fee_rate except Exception: - return self.rpc_callback('getnetworkinfo')['relayfee'] + return self.rpc_callback('getnetworkinfo')['relayfee'], 'relayfee' def decodeAddress(self, address): bech32_prefix = chainparams[self.coin_type()][self._network]['hrp'] diff --git a/basicswap/interface_xmr.py b/basicswap/interface_xmr.py index 01f266e..d1180e9 100644 --- a/basicswap/interface_xmr.py +++ b/basicswap/interface_xmr.py @@ -126,9 +126,9 @@ class XMRInterface(CoinInterface): self.rpc_wallet_cb('open_wallet', {'filename': self._wallet_filename}) return self.rpc_wallet_cb('get_address')['address'] - def get_fee_rate(self): + def get_fee_rate(self, conf_target=2): logging.warning('TODO - estimate fee rate?') - return 0.0012595 + return 0.0, 'unused' def isValidKey(self, key_bytes): ki = b2i(key_bytes) diff --git a/basicswap/templates/bid.html b/basicswap/templates/bid.html index b1a90ac..ca69482 100644 --- a/basicswap/templates/bid.html +++ b/basicswap/templates/bid.html @@ -53,7 +53,9 @@ {% if data.was_received == 'True' %}
{% endif %} +{% if data.can_abandon == true %} +{% endif %} {% if data.show_txns %} {% else %} diff --git a/basicswap/templates/bid_xmr.html b/basicswap/templates/bid_xmr.html index dbb3412..2471838 100644 --- a/basicswap/templates/bid_xmr.html +++ b/basicswap/templates/bid_xmr.html @@ -36,9 +36,11 @@ {% else %} {% if data.was_received == 'True' %} -
+
+{% endif %} +{% if data.can_abandon == true %} + {% endif %} - {% if data.show_txns %} {% else %} @@ -91,8 +93,8 @@

home

diff --git a/basicswap/templates/offer.html b/basicswap/templates/offer.html index e88edd1..875734e 100644 --- a/basicswap/templates/offer.html +++ b/basicswap/templates/offer.html @@ -33,7 +33,7 @@ {% if data.xmr_type == true %} Chain A offer fee rate{{ data.a_fee_rate }} -Chain A local fee rate{{ data.a_fee_rate_verify }} {% if data.a_fee_warn == true %} WARNING {% endif %} +Chain A local fee rate{{ data.a_fee_rate_verify }}, fee source: {{ data.a_fee_rate_verify_src }} {% if data.a_fee_warn == true %} WARNING {% endif %} {% endif %} @@ -57,7 +57,7 @@ {% else %} {% endif %} -{% if data.sent == 'True' %} +{% if data.sent == 'True' and data.was_revoked != true %} {% endif %} diff --git a/basicswap/templates/offer_confirm.html b/basicswap/templates/offer_confirm.html new file mode 100644 index 0000000..9543f64 --- /dev/null +++ b/basicswap/templates/offer_confirm.html @@ -0,0 +1,67 @@ +{% include 'header.html' %} + +

Confirm New Offer

+{% for m in messages %} +

{{ m }}

+{% endfor %} + +
+ + + + + + + + + + + + + + +{% if data.swap_style != 'xmr' %}{% endif %} + +
Send From Address
Coin From + +Amount FromThe amount you will send.
Fee Rate FromFee Rate Source{{ data.from_fee_src }}
Fee From Confirm Target
Fee From Extra Fee +
Coin To + +Amount ToThe amount you will receive.
Fee Rate FromFee Rate Source{{ data.to_fee_src }}
Fee To Confirm Target
Fee To Extra Fee +
Contract locked (hrs)Participate txn will be locked for half the time.
Auto Accept Bids
+ + + + + + + + + +
+ +

home

+ diff --git a/basicswap/templates/offer_new.html b/basicswap/templates/offer_new.html index 09dff2e..0e69fbc 100644 --- a/basicswap/templates/offer_new.html +++ b/basicswap/templates/offer_new.html @@ -10,32 +10,48 @@ + + + + + + - - + +
Send From Address
Coin From -Amount From
Amount FromThe amount you will send.
Fee From Confirm Target
Fee From Extra Fee +
Coin To -Amount To
Amount ToThe amount you will receive.
Fee To Confirm Target
Fee To Extra Fee +
Contract locked (hrs)Participate txn will be locked for half the time.
Auto Accept Bids
Contract locked (hrs)Participate txn will be locked for half the time.
Auto Accept Bids
- + diff --git a/basicswap/ui.py b/basicswap/ui.py index cc0bb47..d05f4c7 100644 --- a/basicswap/ui.py +++ b/basicswap/ui.py @@ -142,6 +142,7 @@ def describeBid(swap_client, bid, xmr_swap, offer, xmr_offer, bid_events, edit_b 'participate_tx': getTxIdHex(bid, TxTypes.PTX, ' ' + ticker_to), 'participate_conf': 'None' if (not bid.participate_tx or not bid.participate_tx.conf) else bid.participate_tx.conf, 'show_txns': show_txns, + 'can_abandon': True if bid.state not in (BidStates.BID_ABANDONED, BidStates.SWAP_COMPLETED) else False, } if edit_bid: diff --git a/doc/upgrade.md b/doc/upgrade.md index 5bf90df..235841a 100644 --- a/doc/upgrade.md +++ b/doc/upgrade.md @@ -1,7 +1,27 @@ ## Update basicswap version -If installed through pip: +### Docker + +Update only the code: + + basicswap]$ git pull + $ cd docker + $ docker-compose build + $ export COINDATA_PATH=[PATH_TO] + $ docker-compose up + +If the dependencies and db format have changed the container must be built with `--no-cache` and the db file moved to a backup. + + basicswap]$ git pull + $ cd docker + $ docker-compose build --no-cache + $ export COINDATA_PATH=[PATH_TO] + $ mv --backup=numbered $COINDATA_PATH/db.sqlite $COINDATA_PATH/db_bkp.sqlite + $ docker-compose up + + +### If installed through pip: cd basicswap git pull @@ -10,4 +30,4 @@ If installed through pip: ## Update core versions - basicswap-prepare -preparebinonly \ No newline at end of file + basicswap-prepare -preparebinonly