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 @@ -