From 8a9f4f9e387161268b30b9b4ef499d926f5d2f05 Mon Sep 17 00:00:00 2001 From: tecnovert Date: Sun, 21 Nov 2021 22:59:39 +0200 Subject: [PATCH] ui: Add rate lookup helper when creating offers. --- basicswap/basicswap.py | 27 ++++++++ basicswap/explorers.py | 4 +- basicswap/http_server.py | 15 ++++ basicswap/js_server.py | 63 +++++++++++++++-- basicswap/templates/offer.html | 9 ++- basicswap/templates/offer_confirm.html | 11 ++- basicswap/templates/offer_new_1.html | 94 ++++++++++++++++++++++++-- basicswap/templates/offer_new_2.html | 13 +++- bin/basicswap_prepare.py | 2 +- doc/release-notes.md | 23 ++++--- tests/basicswap/test_other.py | 4 +- 11 files changed, 238 insertions(+), 27 deletions(-) diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index 0cec36b..e65c095 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -14,6 +14,7 @@ import base64 import random import shutil import struct +import urllib.request import hashlib import secrets import datetime as dt @@ -1036,6 +1037,9 @@ class BasicSwap(BaseApp): msg_buf.amount_negotiable = extra_options.get('amount_negotiable', False) msg_buf.rate_negotiable = extra_options.get('rate_negotiable', False) + if msg_buf.amount_negotiable or msg_buf.rate_negotiable: + ensure(auto_accept_bids is False, 'Auto-accept unavailable when amount or rate are variable') + 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: @@ -5519,3 +5523,26 @@ class BasicSwap(BaseApp): if not self._network: return {'Error': 'Not Initialised'} return self._network.get_info() + + def lookupRates(self, coin_from, coin_to): + rv = {} + ci_from = self.ci(int(coin_from)) + ci_to = self.ci(int(coin_to)) + + name_from = ci_from.coin_name().lower() + name_to = ci_to.coin_name().lower() + url = 'https://api.coingecko.com/api/v3/simple/price?ids={},{}&vs_currencies=usd'.format(name_from, name_to) + headers = {'User-Agent': 'Mozilla/5.0'} + req = urllib.request.Request(url, headers=headers) + js = json.loads(urllib.request.urlopen(req).read()) + rate = float(js[name_from]['usd']) / float(js[name_to]['usd']) + js['rate'] = ci_to.format_amount(rate, conv_int=True, r=1) + rv['coingecko'] = js + + url = 'https://api.bittrex.com/api/v1.1/public/getticker?market={}-{}'.format(ci_from.ticker(), ci_to.ticker()) + headers = {'User-Agent': 'Mozilla/5.0'} + req = urllib.request.Request(url, headers=headers) + js = json.loads(urllib.request.urlopen(req).read()) + rv['bittrex'] = js + + return rv diff --git a/basicswap/explorers.py b/basicswap/explorers.py index bff1ab0..0f090a4 100644 --- a/basicswap/explorers.py +++ b/basicswap/explorers.py @@ -1,11 +1,11 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2019 tecnovert +# Copyright (c) 2019-2021 tecnovert # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. -import urllib.request import json +import urllib.request class Explorer(): diff --git a/basicswap/http_server.py b/basicswap/http_server.py index 79833ef..c1c7e97 100644 --- a/basicswap/http_server.py +++ b/basicswap/http_server.py @@ -42,6 +42,8 @@ from .js_server import ( js_network, js_revokeoffer, js_smsgaddresses, + js_rates, + js_rate, js_index, ) from .ui import ( @@ -507,6 +509,12 @@ class HttpHandler(BaseHTTPRequestHandler): if 'amt_to' in parsed_data and 'amt_from' in parsed_data: parsed_data['rate'] = ci_from.make_int(parsed_data['amt_to'] / parsed_data['amt_from'], r=1) + page_data['rate'] = ci_to.format_amount(parsed_data['rate']) + + page_data['amt_var'] = True if have_data_entry(form_data, 'amt_var') else False + parsed_data['amt_var'] = page_data['amt_var'] + page_data['rate_var'] = True if have_data_entry(form_data, 'rate_var') else False + parsed_data['rate_var'] = page_data['rate_var'] if b'step1' in form_data: if len(errors) == 0 and b'continue' in form_data: @@ -616,6 +624,11 @@ class HttpHandler(BaseHTTPRequestHandler): if 'addr_to' in parsed_data: extra_options['addr_send_to'] = parsed_data['addr_to'] + if parsed_data.get('amt_var', False): + extra_options['amount_negotiable'] = parsed_data['amt_var'] + if parsed_data.get('rate_var', False): + extra_options['rate_negotiable'] = parsed_data['rate_var'] + offer_id = swap_client.postOffer( parsed_data['coin_from'], parsed_data['coin_to'], @@ -1129,6 +1142,8 @@ class HttpHandler(BaseHTTPRequestHandler): 'network': js_network, 'revokeoffer': js_revokeoffer, 'smsgaddresses': js_smsgaddresses, + 'rate': js_rate, + 'rates': js_rates, }.get(url_split[2], js_index) return func(self, url_split, post_string, is_json) except Exception as ex: diff --git a/basicswap/js_server.py b/basicswap/js_server.py index b7c2cf3..07403db 100644 --- a/basicswap/js_server.py +++ b/basicswap/js_server.py @@ -19,6 +19,7 @@ from .chainparams import ( ) from .ui import ( PAGE_LIMIT, + getCoinType, inputAmount, describeBid, setCoinFilter, @@ -245,10 +246,6 @@ def js_revokeoffer(self, url_split, post_string, is_json): return bytes(json.dumps({'revoked_offer': offer_id.hex()}), 'UTF-8') -def js_index(self, url_split, post_string, is_json): - return bytes(json.dumps(self.server.swap_client.getSummary()), 'UTF-8') - - def js_smsgaddresses(self, url_split, post_string, is_json): swap_client = self.server.swap_client if len(url_split) > 3: @@ -276,3 +273,61 @@ def js_smsgaddresses(self, url_split, post_string, is_json): return bytes(json.dumps({'edited_address': address}), 'UTF-8') return bytes(json.dumps(swap_client.listAllSMSGAddresses()), 'UTF-8') + + +def js_rates(self, url_split, post_string, is_json): + if post_string == '': + raise ValueError('No post data') + if is_json: + post_data = json.loads(post_string) + post_data['is_json'] = True + else: + post_data = urllib.parse.parse_qs(post_string) + + sc = self.server.swap_client + coin_from = get_data_entry(post_data, 'coin_from') + coin_to = get_data_entry(post_data, 'coin_to') + return bytes(json.dumps(sc.lookupRates(coin_from, coin_to)), 'UTF-8') + + +def js_rate(self, url_split, post_string, is_json): + if post_string == '': + raise ValueError('No post data') + if is_json: + post_data = json.loads(post_string) + post_data['is_json'] = True + else: + post_data = urllib.parse.parse_qs(post_string) + + sc = self.server.swap_client + coin_from = getCoinType(get_data_entry(post_data, 'coin_from')) + ci_from = sc.ci(coin_from) + coin_to = getCoinType(get_data_entry(post_data, 'coin_to')) + ci_to = sc.ci(coin_to) + + # Set amount to if rate is provided + rate = get_data_entry_or(post_data, 'rate', None) + if rate is not None: + amt_from_str = get_data_entry_or(post_data, 'amt_from', None) + amt_to_str = get_data_entry_or(post_data, 'amt_to', None) + + if amt_from_str is not None: + rate = ci_to.make_int(rate, r=1) + amt_from = inputAmount(amt_from_str, ci_from) + amount_to = ci_to.format_amount(int((amt_from * rate) // ci_from.COIN()), r=1) + return bytes(json.dumps({'amount_to': amount_to}), 'UTF-8') + if amt_to_str is not None: + rate = ci_from.make_int(1.0 / float(rate), r=1) + amt_to = inputAmount(amt_to_str, ci_to) + amount_from = ci_from.format_amount(int((amt_to * rate) // ci_to.COIN()), r=1) + return bytes(json.dumps({'amount_from': amount_from}), 'UTF-8') + + amt_from = inputAmount(get_data_entry(post_data, 'amt_from'), ci_from) + amt_to = inputAmount(get_data_entry(post_data, 'amt_to'), ci_to) + + rate = ci_to.format_amount(ci_from.make_int(amt_to / amt_from, r=1)) + return bytes(json.dumps({'rate': rate}), 'UTF-8') + + +def js_index(self, url_split, post_string, is_json): + return bytes(json.dumps(self.server.swap_client.getSummary()), 'UTF-8') diff --git a/basicswap/templates/offer.html b/basicswap/templates/offer.html index 21a1e7a..764ea09 100644 --- a/basicswap/templates/offer.html +++ b/basicswap/templates/offer.html @@ -60,8 +60,15 @@ -Minutes valid +{% if data.amount_negotiable == true %} +Amount +{% endif %} +{% if data.rate_negotiable == true %} +Rate +{% endif %} + +Minutes valid {% else %} diff --git a/basicswap/templates/offer_confirm.html b/basicswap/templates/offer_confirm.html index ba75b9a..26db9bf 100644 --- a/basicswap/templates/offer_confirm.html +++ b/basicswap/templates/offer_confirm.html @@ -59,10 +59,13 @@ {% endif %} +Rate +Amount Variable +Rate Variable Offer valid (hrs) Contract locked (hrs){% if data.swap_style != 'xmr' %}Participate txn will be locked for half the time.{% endif %} -Auto Accept Bids +Auto Accept Bids @@ -77,6 +80,12 @@ {% if data.autoaccept==true %} {% endif %} +{% if data.amt_var==true %} + +{% endif %} +{% if data.rate_var==true %} + +{% endif %} diff --git a/basicswap/templates/offer_new_1.html b/basicswap/templates/offer_new_1.html index d64cc3f..490d4b2 100644 --- a/basicswap/templates/offer_new_1.html +++ b/basicswap/templates/offer_new_1.html @@ -22,26 +22,112 @@ Coin From - {% for c in coins %} {{ c[1] }} {% endfor %} -Amount FromThe amount you will send. +Amount FromThe amount you will send. Coin To - {% for c in coins %} {{ c[1] }} {% endfor %} -Amount ToThe amount you will receive. +Amount ToThe amount you will receive. +RateLock Rate: + +Amount Variable +Rate Variable + +

+

home

+ diff --git a/basicswap/templates/offer_new_2.html b/basicswap/templates/offer_new_2.html index e821c2a..9da3e13 100644 --- a/basicswap/templates/offer_new_2.html +++ b/basicswap/templates/offer_new_2.html @@ -56,10 +56,15 @@ {% endif %} +Rate +Amount Variable +Rate Variable + Offer valid (hrs) Contract locked (hrs){% if data.swap_style != 'xmr' %}Participate txn will be locked for half the time.{% endif %} -Auto Accept Bids +Auto Accept Bids + @@ -69,6 +74,12 @@ +{% if data.amt_var==true %} + +{% endif %} +{% if data.rate_var==true %} + +{% endif %} diff --git a/bin/basicswap_prepare.py b/bin/basicswap_prepare.py index 327d4a7..17744f1 100755 --- a/bin/basicswap_prepare.py +++ b/bin/basicswap_prepare.py @@ -313,7 +313,7 @@ def prepareCore(coin, version_pair, settings, data_dir): extractCore(coin, version_pair, settings, bin_dir, release_path) -def prepareDataDir(coin, settings, chain, particl_mnemonic, use_containers=False)): +def prepareDataDir(coin, settings, chain, particl_mnemonic, use_containers=False): core_settings = settings['chainclients'][coin] bin_dir = core_settings['bindir'] data_dir = core_settings['datadir'] diff --git a/doc/release-notes.md b/doc/release-notes.md index e82197f..01380ca 100644 --- a/doc/release-notes.md +++ b/doc/release-notes.md @@ -2,21 +2,22 @@ 0.0.27 ============== -- Track failed and successful swaps by address +- Track failed and successful swaps by address. +- Added rate lookup helper when creating offer. 0.0.26 ============== -- Added protocol version to order and bid messages +- Added protocol version to order and bid messages. - Moved chain start heights to bid. -- Avoid scantxoutset for decred style swaps -- xmr: spend chain B lock tx will look for existing spends +- Avoid scantxoutset for decred style swaps. +- xmr: spend chain B lock tx will look for existing spends. - xmrswaps: - Setting state to 'Script tx redeemed' will trigger an attempt to redeem the scriptless lock tx. - Node will wait for the chain B lock tx to reach a spendable depth before attempting to spend. - ui: Sort settings page by coin name. -- ui, xmr: List of candidate remote XMR daemon urls can be set through the http ui +- ui, xmr: List of candidate remote XMR daemon urls can be set through the http ui. 0.0.25 @@ -24,8 +25,8 @@ - Fix extra 33 bytes in lock spend fee calculation. - XMR swaps use watchonly addresses to save the lock tx to the wallet - - Instead of scantxoutset -- Add missing check of leader's lock refund tx signature result + - Instead of scantxoutset. +- Add missing check of leader's lock refund tx signature result. - Blind part -> XMR swaps are possible: - The sha256 hash of the chain b view private key is used as the nonce for transactions requiring cooperation to sign. - Follower sends a public key in xmr_swap.dest_af. @@ -45,14 +46,14 @@ 0.0.23 ============== -- Enables private offers +- Enables private offers. 0.0.22 ============== - Improved wallets page - - Consistent wallet order + - Consistent wallet order. - Separated RPC calls into threads. @@ -67,5 +68,5 @@ 0.0.6 ============== -- Experimental support for XMR swaps. - - Single direction only, scriptless -> XMR +- Experimental support for XMR swaps + - Single direction only, scriptless -> XMR. diff --git a/tests/basicswap/test_other.py b/tests/basicswap/test_other.py index aa3f944..a9a89f1 100644 --- a/tests/basicswap/test_other.py +++ b/tests/basicswap/test_other.py @@ -29,10 +29,10 @@ from basicswap.basicswap_util import ( SEQUENCE_LOCK_BLOCKS, SEQUENCE_LOCK_TIME) from basicswap.util import ( - SerialiseNum, - DeserialiseNum, make_int, + SerialiseNum, format_amount, + DeserialiseNum, validate_amount)