|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
# Copyright (c) 2022-2023 tecnovert
|
|
|
|
# Distributed under the MIT software license, see the accompanying
|
|
|
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
|
|
|
|
import traceback
|
|
|
|
|
|
|
|
from urllib import parse
|
|
|
|
from .util import (
|
|
|
|
PAGE_LIMIT,
|
|
|
|
getCoinType,
|
|
|
|
inputAmount,
|
|
|
|
setCoinFilter,
|
|
|
|
get_data_entry,
|
|
|
|
have_data_entry,
|
|
|
|
get_data_entry_or,
|
|
|
|
listAvailableCoins,
|
|
|
|
set_pagination_filters,
|
|
|
|
)
|
|
|
|
from basicswap.db import (
|
|
|
|
Concepts,
|
|
|
|
)
|
|
|
|
from basicswap.util import (
|
|
|
|
ensure,
|
|
|
|
format_amount,
|
|
|
|
format_timestamp,
|
|
|
|
)
|
|
|
|
from basicswap.basicswap_util import (
|
|
|
|
SwapTypes,
|
|
|
|
DebugTypes,
|
|
|
|
getLockName,
|
|
|
|
strBidState,
|
|
|
|
strSwapDesc,
|
|
|
|
strSwapType,
|
|
|
|
TxLockTypes,
|
|
|
|
strOfferState,
|
|
|
|
)
|
|
|
|
from basicswap.chainparams import (
|
|
|
|
Coins,
|
|
|
|
)
|
|
|
|
from basicswap.protocols.xmr_swap_1 import reverseBidAmountAndRate
|
|
|
|
|
|
|
|
|
|
|
|
def value_or_none(v):
|
|
|
|
if v == -1 or v == '-1':
|
|
|
|
return None
|
|
|
|
return v
|
|
|
|
|
|
|
|
|
|
|
|
def decode_offer_id(v):
|
|
|
|
try:
|
|
|
|
offer_id = bytes.fromhex(v)
|
|
|
|
ensure(len(offer_id) == 28, 'Bad offer ID')
|
|
|
|
return offer_id
|
|
|
|
except Exception:
|
|
|
|
raise ValueError('Bad offer ID')
|
|
|
|
|
|
|
|
|
|
|
|
def swap_type_from_string(str_swap_type: str) -> SwapTypes:
|
|
|
|
if str_swap_type == 'seller_first' or str_swap_type == 'secret_hash':
|
|
|
|
return SwapTypes.SELLER_FIRST
|
|
|
|
elif str_swap_type == 'xmr_swap' or str_swap_type == 'adaptor_sig':
|
|
|
|
return SwapTypes.XMR_SWAP
|
|
|
|
else:
|
|
|
|
raise ValueError('Unknown swap type')
|
|
|
|
|
|
|
|
|
|
|
|
def parseOfferFormData(swap_client, form_data, page_data, options={}):
|
|
|
|
errors = []
|
|
|
|
parsed_data = {}
|
|
|
|
|
|
|
|
if have_data_entry(form_data, 'addr_to'):
|
|
|
|
page_data['addr_to'] = get_data_entry(form_data, 'addr_to')
|
|
|
|
addr_to = value_or_none(page_data['addr_to'])
|
|
|
|
if addr_to is not None:
|
|
|
|
parsed_data['addr_to'] = addr_to
|
|
|
|
|
|
|
|
if have_data_entry(form_data, 'addr_from'):
|
|
|
|
page_data['addr_from'] = get_data_entry(form_data, 'addr_from')
|
|
|
|
parsed_data['addr_from'] = value_or_none(page_data['addr_from'])
|
|
|
|
else:
|
|
|
|
parsed_data['addr_from'] = None
|
|
|
|
|
|
|
|
try:
|
|
|
|
page_data['coin_from'] = getCoinType(get_data_entry(form_data, 'coin_from'))
|
|
|
|
coin_from = Coins(page_data['coin_from'])
|
|
|
|
ci_from = swap_client.ci(coin_from)
|
|
|
|
if coin_from != Coins.XMR:
|
|
|
|
page_data['fee_from_conf'] = ci_from._conf_target # Set default value
|
|
|
|
parsed_data['coin_from'] = coin_from
|
|
|
|
except Exception:
|
|
|
|
errors.append('Unknown Coin From')
|
|
|
|
|
|
|
|
try:
|
|
|
|
page_data['coin_to'] = getCoinType(get_data_entry(form_data, 'coin_to'))
|
|
|
|
coin_to = Coins(page_data['coin_to'])
|
|
|
|
ci_to = swap_client.ci(coin_to)
|
|
|
|
if coin_to != Coins.XMR:
|
|
|
|
page_data['fee_to_conf'] = ci_to._conf_target # Set default value
|
|
|
|
parsed_data['coin_to'] = coin_to
|
|
|
|
except Exception:
|
|
|
|
errors.append('Unknown Coin To')
|
|
|
|
|
|
|
|
if coin_from == coin_to:
|
|
|
|
errors.append('Coins from and to must be different.')
|
|
|
|
|
|
|
|
try:
|
|
|
|
page_data['amt_from'] = get_data_entry(form_data, 'amt_from')
|
|
|
|
parsed_data['amt_from'] = inputAmount(page_data['amt_from'], ci_from)
|
|
|
|
except Exception:
|
|
|
|
errors.append('Amount From')
|
|
|
|
|
|
|
|
try:
|
|
|
|
if 'amt_bid_min' not in page_data:
|
|
|
|
if options.get('add_min_bid_amt', False) is True:
|
|
|
|
parsed_data['amt_bid_min'] = ci_from.chainparams_network()['min_amount']
|
|
|
|
else:
|
|
|
|
raise ValueError('missing')
|
|
|
|
else:
|
|
|
|
page_data['amt_bid_min'] = get_data_entry(form_data, 'amt_bid_min')
|
|
|
|
parsed_data['amt_bid_min'] = inputAmount(page_data['amt_bid_min'], ci_from)
|
|
|
|
|
|
|
|
if parsed_data['amt_bid_min'] < 0 or parsed_data['amt_bid_min'] > parsed_data['amt_from']:
|
|
|
|
errors.append('Minimum Bid Amount out of range')
|
|
|
|
except Exception:
|
|
|
|
errors.append('Minimum Bid Amount')
|
|
|
|
|
|
|
|
if (have_data_entry(form_data, 'rate') and not have_data_entry(form_data, 'amt_to')):
|
|
|
|
parsed_data['rate'] = ci_to.make_int(form_data['rate'], r=1)
|
|
|
|
page_data['rate'] = ci_to.format_amount(parsed_data['rate'])
|
|
|
|
else:
|
|
|
|
try:
|
|
|
|
page_data['amt_to'] = get_data_entry(form_data, 'amt_to')
|
|
|
|
parsed_data['amt_to'] = inputAmount(page_data['amt_to'], ci_to)
|
|
|
|
except Exception:
|
|
|
|
errors.append('Amount To')
|
|
|
|
|
|
|
|
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']
|
|
|
|
|
|
|
|
page_data['automation_strat_id'] = int(get_data_entry_or(form_data, 'automation_strat_id', -1))
|
|
|
|
parsed_data['automation_strat_id'] = page_data['automation_strat_id']
|
|
|
|
swap_type = -1
|
|
|
|
|
|
|
|
if have_data_entry(form_data, 'subfee'):
|
|
|
|
parsed_data['subfee'] = True
|
|
|
|
if have_data_entry(form_data, 'swap_type'):
|
|
|
|
page_data['swap_type'] = get_data_entry(form_data, 'swap_type')
|
|
|
|
parsed_data['swap_type'] = page_data['swap_type']
|
|
|
|
swap_type = swap_type_from_string(parsed_data['swap_type'])
|
|
|
|
elif parsed_data['coin_to'] in (Coins.XMR, Coins.PART_ANON):
|
|
|
|
parsed_data['swap_type'] = strSwapType(SwapTypes.XMR_SWAP)
|
|
|
|
swap_type = SwapTypes.XMR_SWAP
|
|
|
|
else:
|
|
|
|
parsed_data['swap_type'] = strSwapType(SwapTypes.SELLER_FIRST)
|
|
|
|
swap_type = SwapTypes.SELLER_FIRST
|
|
|
|
|
|
|
|
if swap_type == SwapTypes.XMR_SWAP:
|
|
|
|
page_data['swap_style'] = 'xmr'
|
|
|
|
else:
|
|
|
|
page_data['swap_style'] = 'atomic'
|
|
|
|
|
|
|
|
if 'swap_type' in parsed_data:
|
|
|
|
try:
|
|
|
|
swap_client.validateSwapType(coin_from, coin_to, swap_type)
|
|
|
|
except Exception as e:
|
|
|
|
errors.append(f'Invalid Swap type {e}')
|
|
|
|
|
|
|
|
if have_data_entry(form_data, 'step1'):
|
|
|
|
if len(errors) == 0 and have_data_entry(form_data, 'continue'):
|
|
|
|
page_data['step2'] = True
|
|
|
|
return parsed_data, errors
|
|
|
|
|
|
|
|
page_data['step2'] = True
|
|
|
|
|
|
|
|
if have_data_entry(form_data, 'fee_from_conf'):
|
|
|
|
page_data['fee_from_conf'] = int(get_data_entry(form_data, 'fee_from_conf'))
|
|
|
|
parsed_data['fee_from_conf'] = page_data['fee_from_conf']
|
|
|
|
|
|
|
|
if have_data_entry(form_data, 'fee_from_extra'):
|
|
|
|
page_data['fee_from_extra'] = int(get_data_entry(form_data, 'fee_from_extra'))
|
|
|
|
parsed_data['fee_from_extra'] = page_data['fee_from_extra']
|
|
|
|
|
|
|
|
if have_data_entry(form_data, 'fee_to_conf'):
|
|
|
|
page_data['fee_to_conf'] = int(get_data_entry(form_data, 'fee_to_conf'))
|
|
|
|
parsed_data['fee_to_conf'] = page_data['fee_to_conf']
|
|
|
|
|
|
|
|
if have_data_entry(form_data, 'fee_to_extra'):
|
|
|
|
page_data['fee_to_extra'] = int(get_data_entry(form_data, 'fee_to_extra'))
|
|
|
|
parsed_data['fee_to_extra'] = page_data['fee_to_extra']
|
|
|
|
|
|
|
|
if have_data_entry(form_data, 'check_offer'):
|
|
|
|
page_data['check_offer'] = True
|
|
|
|
if have_data_entry(form_data, 'submit_offer'):
|
|
|
|
page_data['submit_offer'] = True
|
|
|
|
|
|
|
|
if have_data_entry(form_data, 'lockhrs'):
|
|
|
|
page_data['lockhrs'] = int(get_data_entry(form_data, 'lockhrs'))
|
|
|
|
parsed_data['lock_seconds'] = page_data['lockhrs'] * 60 * 60
|
|
|
|
elif have_data_entry(form_data, 'lockmins'):
|
|
|
|
page_data['lockmins'] = int(get_data_entry(form_data, 'lockmins'))
|
|
|
|
parsed_data['lock_seconds'] = page_data['lockmins'] * 60
|
|
|
|
elif have_data_entry(form_data, 'lockseconds'):
|
|
|
|
parsed_data['lock_seconds'] = int(get_data_entry(form_data, 'lockseconds'))
|
|
|
|
|
|
|
|
if have_data_entry(form_data, 'validhrs'):
|
|
|
|
page_data['validhrs'] = int(get_data_entry(form_data, 'validhrs'))
|
|
|
|
parsed_data['valid_for_seconds'] = page_data['validhrs'] * 60 * 60
|
|
|
|
elif have_data_entry(form_data, 'valid_for_seconds'):
|
|
|
|
parsed_data['valid_for_seconds'] = int(get_data_entry(form_data, 'valid_for_seconds'))
|
|
|
|
|
|
|
|
try:
|
|
|
|
if len(errors) == 0 and page_data['swap_style'] == 'xmr':
|
|
|
|
reverse_bid: bool = ci_from.coin_type() in swap_client.scriptless_coins
|
|
|
|
ci_leader = ci_to if reverse_bid else ci_from
|
|
|
|
ci_follower = ci_from if reverse_bid else ci_to
|
|
|
|
|
|
|
|
if have_data_entry(form_data, 'fee_rate_from'):
|
|
|
|
page_data['from_fee_override'] = get_data_entry(form_data, 'fee_rate_from')
|
|
|
|
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']
|
|
|
|
|
|
|
|
lock_spend_tx_vsize = ci_leader.xmr_swap_alock_spend_tx_vsize()
|
|
|
|
lock_spend_tx_fee = ci_leader.make_int(ci_from.make_int(from_fee_override, r=1) * lock_spend_tx_vsize / 1000, r=1)
|
|
|
|
page_data['amt_from_lock_spend_tx_fee'] = ci_from.format_amount(lock_spend_tx_fee // ci_leader.COIN())
|
|
|
|
page_data['tla_from'] = ci_leader.ticker()
|
|
|
|
|
|
|
|
if ci_follower == Coins.XMR:
|
|
|
|
if have_data_entry(form_data, 'fee_rate_to'):
|
|
|
|
page_data['to_fee_override'] = get_data_entry(form_data, 'fee_rate_to')
|
|
|
|
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_follower.format_amount(ci_follower.make_int(to_fee_override, r=1))
|
|
|
|
parsed_data['to_fee_override'] = page_data['to_fee_override']
|
|
|
|
except Exception as e:
|
|
|
|
print('Error setting fee', str(e)) # Expected if missing fields
|
|
|
|
|
|
|
|
return parsed_data, errors
|
|
|
|
|
|
|
|
|
|
|
|
def postNewOfferFromParsed(swap_client, parsed_data):
|
|
|
|
swap_type = SwapTypes.SELLER_FIRST
|
|
|
|
|
|
|
|
if 'swap_type' in parsed_data:
|
|
|
|
str_swap_type = parsed_data['swap_type'].lower()
|
|
|
|
swap_type = swap_type_from_string(str_swap_type)
|
|
|
|
elif parsed_data['coin_to'] in (Coins.XMR, Coins.PART_ANON):
|
|
|
|
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 = TxLockTypes.SEQUENCE_LOCK_TIME
|
|
|
|
else:
|
|
|
|
lock_type = TxLockTypes.ABS_LOCK_TIME
|
|
|
|
|
|
|
|
extra_options = {}
|
|
|
|
|
|
|
|
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']
|
|
|
|
if 'valid_for_seconds' in parsed_data:
|
|
|
|
extra_options['valid_for_seconds'] = parsed_data['valid_for_seconds']
|
|
|
|
|
|
|
|
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']
|
|
|
|
|
|
|
|
if parsed_data.get('rate_var', None) is not None:
|
|
|
|
extra_options['rate_negotiable'] = parsed_data['rate_var']
|
|
|
|
|
|
|
|
if parsed_data.get('automation_strat_id', None) is not None:
|
|
|
|
extra_options['automation_id'] = parsed_data['automation_strat_id']
|
|
|
|
|
|
|
|
swap_value = parsed_data['amt_from']
|
|
|
|
if parsed_data.get('subfee', False):
|
|
|
|
ci_from = swap_client.ci(parsed_data['coin_from'])
|
|
|
|
pi = swap_client.pi(swap_type)
|
|
|
|
itx = pi.getFundedInitiateTxTemplate(ci_from, swap_value, True)
|
|
|
|
itx_decoded = ci_from.describeTx(itx.hex())
|
|
|
|
n = pi.findMockVout(ci_from, itx_decoded)
|
|
|
|
swap_value = ci_from.make_int(itx_decoded['vout'][n]['value'])
|
|
|
|
extra_options = {'prefunded_itx': itx}
|
|
|
|
|
|
|
|
offer_id = swap_client.postOffer(
|
|
|
|
parsed_data['coin_from'],
|
|
|
|
parsed_data['coin_to'],
|
|
|
|
swap_value,
|
|
|
|
parsed_data['rate'],
|
|
|
|
parsed_data['amt_bid_min'],
|
|
|
|
swap_type,
|
|
|
|
lock_type=lock_type,
|
|
|
|
lock_value=parsed_data['lock_seconds'],
|
|
|
|
addr_send_from=parsed_data['addr_from'],
|
|
|
|
extra_options=extra_options)
|
|
|
|
return offer_id
|
|
|
|
|
|
|
|
|
|
|
|
def postNewOffer(swap_client, form_data):
|
|
|
|
page_data = {}
|
|
|
|
parsed_data, errors = parseOfferFormData(swap_client, form_data, page_data, options={'add_min_bid_amt': True})
|
|
|
|
if len(errors) > 0:
|
|
|
|
raise ValueError('Parse errors: ' + ' '.join(errors))
|
|
|
|
return postNewOfferFromParsed(swap_client, parsed_data)
|
|
|
|
|
|
|
|
|
|
|
|
def offer_to_post_string(self, swap_client, offer_id):
|
|
|
|
|
|
|
|
offer, xmr_offer = swap_client.getXmrOffer(offer_id)
|
|
|
|
ensure(offer, 'Unknown offer ID')
|
|
|
|
|
|
|
|
ci_from = swap_client.ci(offer.coin_from)
|
|
|
|
ci_to = swap_client.ci(offer.coin_to)
|
|
|
|
offer_data = {
|
|
|
|
'formid': self.generate_form_id(),
|
|
|
|
'addr_to': offer.addr_to,
|
|
|
|
'addr_from': offer.addr_from,
|
|
|
|
'coin_from': offer.coin_from,
|
|
|
|
'coin_to': offer.coin_to,
|
|
|
|
# TODO store fee conf, or pass directly
|
|
|
|
# 'fee_from_conf'
|
|
|
|
# 'fee_to_conf'
|
|
|
|
'amt_from': ci_from.format_amount(offer.amount_from),
|
|
|
|
'amt_bid_min': ci_from.format_amount(offer.min_bid_amount),
|
|
|
|
'rate': ci_to.format_amount(offer.rate),
|
|
|
|
'amt_to': ci_to.format_amount((offer.amount_from * offer.rate) // ci_from.COIN()),
|
|
|
|
'validhrs': offer.time_valid // (60 * 60),
|
|
|
|
'swap_type': strSwapType(offer.swap_type),
|
|
|
|
}
|
|
|
|
|
|
|
|
if offer.amount_negotiable:
|
|
|
|
offer_data['amt_var'] = True
|
|
|
|
if offer.rate_negotiable:
|
|
|
|
offer_data['rate_var'] = True
|
|
|
|
|
|
|
|
if offer.lock_type == TxLockTypes.SEQUENCE_LOCK_TIME or offer.lock_type == TxLockTypes.ABS_LOCK_TIME:
|
|
|
|
if offer.lock_value > 60 * 60:
|
|
|
|
offer_data['lockhrs'] = offer.lock_value // (60 * 60)
|
|
|
|
else:
|
|
|
|
offer_data['lockhrs'] = offer.lock_value // 60
|
|
|
|
try:
|
|
|
|
strategy = swap_client.getLinkedStrategy(Concepts.OFFER, offer.offer_id)
|
|
|
|
offer_data['automation_strat_id'] = strategy[0]
|
|
|
|
except Exception:
|
|
|
|
pass # None found
|
|
|
|
|
|
|
|
return parse.urlencode(offer_data).encode()
|
|
|
|
|
|
|
|
|
|
|
|
def page_newoffer(self, url_split, post_string):
|
|
|
|
server = self.server
|
|
|
|
swap_client = self.server.swap_client
|
|
|
|
swap_client.checkSystemStatus()
|
|
|
|
summary = swap_client.getSummary()
|
|
|
|
|
|
|
|
messages = []
|
|
|
|
err_messages = []
|
|
|
|
page_data = {
|
|
|
|
# Set defaults
|
|
|
|
'addr_to': -1,
|
|
|
|
'fee_from_conf': 2,
|
|
|
|
'fee_to_conf': 2,
|
|
|
|
'validhrs': 1,
|
|
|
|
'lockhrs': 32,
|
|
|
|
'lockmins': 30, # used in debug mode
|
|
|
|
'debug_ui': swap_client.debug_ui,
|
|
|
|
'automation_strat_id': -1,
|
|
|
|
'amt_bid_min': format_amount(1000, 8),
|
|
|
|
'swap_type': strSwapType(SwapTypes.SELLER_FIRST),
|
|
|
|
}
|
|
|
|
|
|
|
|
post_data = parse.parse_qs(post_string)
|
|
|
|
if 'offer_from' in post_data:
|
|
|
|
offer_from_id_hex = post_data['offer_from'][0]
|
|
|
|
offer_from_id = decode_offer_id(offer_from_id_hex)
|
|
|
|
post_string = offer_to_post_string(self, swap_client, offer_from_id)
|
|
|
|
|
|
|
|
form_data = self.checkForm(post_string, 'newoffer', err_messages)
|
|
|
|
|
|
|
|
if form_data:
|
|
|
|
try:
|
|
|
|
parsed_data, errors = parseOfferFormData(swap_client, form_data, page_data)
|
|
|
|
for e in errors:
|
|
|
|
err_messages.append(str(e))
|
|
|
|
except Exception as e:
|
|
|
|
if swap_client.debug is True:
|
|
|
|
swap_client.log.error(traceback.format_exc())
|
|
|
|
err_messages.append(str(e))
|
|
|
|
|
|
|
|
if len(err_messages) == 0 and 'submit_offer' in page_data:
|
|
|
|
try:
|
|
|
|
offer_id = postNewOfferFromParsed(swap_client, parsed_data)
|
|
|
|
messages.append('<a href="/offer/' + offer_id.hex() + '">Sent Offer {}</a>'.format(offer_id.hex()))
|
|
|
|
page_data = {}
|
|
|
|
except Exception as e:
|
|
|
|
if swap_client.debug is True:
|
|
|
|
swap_client.log.error(traceback.format_exc())
|
|
|
|
err_messages.append(str(e))
|
|
|
|
|
|
|
|
if len(err_messages) == 0 and 'check_offer' in page_data:
|
|
|
|
template = server.env.get_template('offer_confirm.html')
|
|
|
|
elif 'step2' in page_data:
|
|
|
|
template = server.env.get_template('offer_new_2.html')
|
|
|
|
else:
|
|
|
|
template = server.env.get_template('offer_new_1.html')
|
|
|
|
|
|
|
|
if swap_client.debug_ui:
|
|
|
|
messages.append('Debug mode active.')
|
|
|
|
|
|
|
|
coins_from, coins_to = listAvailableCoins(swap_client, split_from=True)
|
|
|
|
|
|
|
|
automation_filters = {'type_ind': Concepts.OFFER, 'sort_by': 'label'}
|
|
|
|
automation_strategies = swap_client.listAutomationStrategies(automation_filters)
|
|
|
|
|
|
|
|
chart_api_key = swap_client.settings.get('chart_api_key', '')
|
|
|
|
if chart_api_key == '':
|
|
|
|
chart_api_key_enc = swap_client.settings.get('chart_api_key_enc', '')
|
|
|
|
chart_api_key = 'cd7600e7b5fdd99c6f900673ff0ee8f64d6d4219a4bb87191ad4a2e3fc65d7f4' if chart_api_key_enc == '' else bytes.fromhex(chart_api_key_enc).decode('utf-8')
|
|
|
|
|
|
|
|
return self.render_template(template, {
|
|
|
|
'messages': messages,
|
|
|
|
'err_messages': err_messages,
|
|
|
|
'coins_from': coins_from,
|
|
|
|
'coins': coins_to,
|
|
|
|
'addrs': swap_client.listSmsgAddresses('offer_send_from'),
|
|
|
|
'addrs_to': swap_client.listSmsgAddresses('offer_send_to'),
|
|
|
|
'data': page_data,
|
|
|
|
'automation_strategies': automation_strategies,
|
|
|
|
'summary': summary,
|
|
|
|
'swap_types': [(strSwapType(x), strSwapDesc(x)) for x in SwapTypes if strSwapType(x)],
|
|
|
|
'show_chart': swap_client.settings.get('show_chart', True),
|
|
|
|
'chart_api_key': chart_api_key,
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
def page_offer(self, url_split, post_string):
|
|
|
|
ensure(len(url_split) > 2, 'Offer ID not specified')
|
|
|
|
offer_id = decode_offer_id(url_split[2])
|
|
|
|
server = self.server
|
|
|
|
swap_client = server.swap_client
|
|
|
|
swap_client.checkSystemStatus()
|
|
|
|
summary = swap_client.getSummary()
|
|
|
|
offer, xmr_offer = swap_client.getXmrOffer(offer_id)
|
|
|
|
ensure(offer, 'Unknown offer ID')
|
|
|
|
|
|
|
|
extend_data = { # Defaults
|
|
|
|
'nb_validmins': 10,
|
|
|
|
}
|
|
|
|
messages = []
|
|
|
|
err_messages = []
|
|
|
|
if swap_client.debug_ui:
|
|
|
|
messages.append('Debug mode active.')
|
|
|
|
sent_bid_id = None
|
|
|
|
show_bid_form = None
|
|
|
|
show_edit_form = None
|
|
|
|
form_data = self.checkForm(post_string, 'offer', err_messages)
|
|
|
|
|
|
|
|
ci_from = swap_client.ci(Coins(offer.coin_from))
|
|
|
|
ci_to = swap_client.ci(Coins(offer.coin_to))
|
|
|
|
|
|
|
|
reverse_bid: bool = ci_from.coin_type() in swap_client.scriptless_coins
|
|
|
|
|
|
|
|
# Set defaults
|
|
|
|
debugind = -1
|
|
|
|
bid_amount = ci_from.format_amount(offer.amount_from)
|
|
|
|
bid_rate = ci_to.format_amount(offer.rate)
|
|
|
|
|
|
|
|
if form_data:
|
|
|
|
if b'archive_offer' in form_data:
|
|
|
|
try:
|
|
|
|
swap_client.archiveOffer(offer_id)
|
|
|
|
messages.append('Offer archived')
|
|
|
|
except Exception as ex:
|
|
|
|
err_messages.append('Archive offer failed: ' + str(ex))
|
|
|
|
if b'revoke_offer' in form_data:
|
|
|
|
try:
|
|
|
|
swap_client.revokeOffer(offer_id)
|
|
|
|
messages.append('Offer revoked')
|
|
|
|
except Exception as ex:
|
|
|
|
err_messages.append('Revoke offer failed: ' + str(ex))
|
|
|
|
elif b'repeat_offer' in form_data:
|
|
|
|
# Can't set the post data here as browsers will always resend the original post data when responding to redirects
|
|
|
|
self.send_response(302)
|
|
|
|
self.send_header('Location', '/newoffer?offer_from=' + offer_id.hex())
|
|
|
|
self.end_headers()
|
|
|
|
return bytes()
|
|
|
|
elif b'edit_offer' in form_data:
|
|
|
|
show_edit_form = True
|
|
|
|
automation_filters = {'type_ind': Concepts.OFFER, 'sort_by': 'label'}
|
|
|
|
extend_data['automation_strategies'] = swap_client.listAutomationStrategies(automation_filters)
|
|
|
|
elif b'edit_offer_submit' in form_data:
|
|
|
|
change_data = {}
|
|
|
|
change_data['automation_strat_id'] = int(get_data_entry_or(form_data, 'automation_strat_id', -1))
|
|
|
|
swap_client.editOffer(offer_id, change_data)
|
|
|
|
elif b'newbid' in form_data:
|
|
|
|
show_bid_form = True
|
|
|
|
elif b'sendbid' in form_data:
|
|
|
|
try:
|
|
|
|
addr_from = form_data[b'addr_from'][0].decode('utf-8')
|
|
|
|
extend_data['nb_addr_from'] = addr_from
|
|
|
|
if addr_from == '-1':
|
|
|
|
addr_from = None
|
|
|
|
|
|
|
|
minutes_valid = int(form_data[b'validmins'][0].decode('utf-8'))
|
|
|
|
extend_data['nb_validmins'] = minutes_valid
|
|
|
|
|
|
|
|
extra_options = {
|
|
|
|
'valid_for_seconds': minutes_valid * 60,
|
|
|
|
}
|
|
|
|
if have_data_entry(form_data, 'bid_rate'):
|
|
|
|
bid_rate = get_data_entry(form_data, 'bid_rate')
|
|
|
|
extra_options['bid_rate'] = ci_to.make_int(bid_rate, r=1)
|
|
|
|
|
|
|
|
if have_data_entry(form_data, 'bid_amount'):
|
|
|
|
bid_amount = get_data_entry(form_data, 'bid_amount')
|
|
|
|
amount_from = inputAmount(bid_amount, ci_from)
|
|
|
|
else:
|
|
|
|
amount_from = offer.amount_from
|
|
|
|
debugind = int(get_data_entry_or(form_data, 'debugind', -1))
|
|
|
|
|
|
|
|
sent_bid_id = swap_client.postBid(offer_id, amount_from, addr_send_from=addr_from, extra_options=extra_options).hex()
|
|
|
|
|
|
|
|
if debugind > -1:
|
|
|
|
swap_client.setBidDebugInd(bytes.fromhex(sent_bid_id), debugind)
|
|
|
|
except Exception as ex:
|
|
|
|
if self.server.swap_client.debug is True:
|
|
|
|
self.server.swap_client.log.error(traceback.format_exc())
|
|
|
|
err_messages.append('Send bid failed: ' + str(ex))
|
|
|
|
show_bid_form = True
|
|
|
|
|
|
|
|
now: int = swap_client.getTime()
|
|
|
|
data = {
|
|
|
|
'tla_from': ci_from.ticker(),
|
|
|
|
'tla_to': ci_to.ticker(),
|
|
|
|
'state': strOfferState(offer.state),
|
|
|
|
'coin_from': ci_from.coin_name(),
|
|
|
|
'coin_to': ci_to.coin_name(),
|
|
|
|
'coin_from_ind': int(ci_from.coin_type()),
|
|
|
|
'coin_to_ind': int(ci_to.coin_type()),
|
|
|
|
'amt_from': ci_from.format_amount(offer.amount_from),
|
|
|
|
'amt_to': ci_to.format_amount((offer.amount_from * offer.rate) // ci_from.COIN()),
|
|
|
|
'amt_bid_min': ci_from.format_amount(offer.min_bid_amount),
|
|
|
|
'rate': ci_to.format_amount(offer.rate),
|
|
|
|
'lock_type': getLockName(offer.lock_type),
|
|
|
|
'lock_value': offer.lock_value,
|
|
|
|
'addr_from': offer.addr_from,
|
|
|
|
'addr_to': 'Public' if offer.addr_to == swap_client.network_addr else offer.addr_to,
|
|
|
|
'created_at': offer.created_at,
|
|
|
|
'expired_at': offer.expire_at,
|
|
|
|
'sent': offer.was_sent,
|
|
|
|
'was_revoked': 'True' if offer.active_ind == 2 else 'False',
|
|
|
|
'show_bid_form': show_bid_form,
|
|
|
|
'show_edit_form': show_edit_form,
|
|
|
|
'amount_negotiable': offer.amount_negotiable,
|
|
|
|
'rate_negotiable': offer.rate_negotiable,
|
|
|
|
'bid_amount': bid_amount,
|
|
|
|
'bid_rate': bid_rate,
|
|
|
|
'debug_ui': swap_client.debug_ui,
|
|
|
|
'automation_strat_id': -1,
|
|
|
|
'is_expired': offer.expire_at <= now,
|
|
|
|
'active_ind': offer.active_ind,
|
|
|
|
'swap_type': strSwapDesc(offer.swap_type)
|
|
|
|
}
|
|
|
|
data.update(extend_data)
|
|
|
|
|
|
|
|
if offer.lock_type == TxLockTypes.SEQUENCE_LOCK_TIME or offer.lock_type == TxLockTypes.ABS_LOCK_TIME:
|
|
|
|
if offer.lock_value > 60 * 60:
|
|
|
|
data['lock_value_hr'] = ' ({} hours)'.format(offer.lock_value / (60 * 60))
|
|
|
|
else:
|
|
|
|
data['lock_value_hr'] = ' ({} minutes)'.format(offer.lock_value / 60)
|
|
|
|
|
|
|
|
addr_from_label, addr_to_label = swap_client.getAddressLabel([offer.addr_from, offer.addr_to])
|
|
|
|
if len(addr_from_label) > 0:
|
|
|
|
data['addr_from_label'] = '(' + addr_from_label + ')'
|
|
|
|
if len(addr_to_label) > 0:
|
|
|
|
data['addr_to_label'] = '(' + addr_to_label + ')'
|
|
|
|
|
|
|
|
if swap_client.debug_ui:
|
|
|
|
data['debug_ind'] = debugind
|
|
|
|
data['debug_options'] = [(int(t), t.name) for t in DebugTypes]
|
|
|
|
|
|
|
|
reverse_bid: bool = ci_from.coin_type() in swap_client.scriptless_coins
|
|
|
|
ci_leader = ci_to if reverse_bid else ci_from
|
|
|
|
ci_follower = ci_from if reverse_bid else ci_to
|
|
|
|
|
|
|
|
if xmr_offer:
|
|
|
|
int_fee_rate_now, fee_source = ci_leader.get_fee_rate()
|
|
|
|
|
|
|
|
data['xmr_type'] = True
|
|
|
|
data['a_fee_rate'] = ci_leader.format_amount(xmr_offer.a_fee_rate)
|
|
|
|
data['a_fee_rate_verify'] = ci_leader.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
|
|
|
|
|
|
|
|
lock_spend_tx_vsize = ci_leader.xmr_swap_alock_spend_tx_vsize()
|
|
|
|
lock_spend_tx_fee = ci_leader.make_int(xmr_offer.a_fee_rate * lock_spend_tx_vsize / 1000, r=1)
|
|
|
|
data['amt_from_lock_spend_tx_fee'] = ci_leader.format_amount(lock_spend_tx_fee // ci_leader.COIN())
|
|
|
|
|
|
|
|
if offer.was_sent:
|
|
|
|
try:
|
|
|
|
strategy = swap_client.getLinkedStrategy(Concepts.OFFER, offer_id)
|
|
|
|
data['automation_strat_id'] = strategy[0]
|
|
|
|
data['automation_strat_label'] = strategy[1]
|
|
|
|
except Exception:
|
|
|
|
pass # None found
|
|
|
|
|
|
|
|
bids = swap_client.listBids(offer_id=offer_id)
|
|
|
|
formatted_bids = []
|
|
|
|
amt_swapped = 0
|
|
|
|
for b in bids:
|
|
|
|
|
|
|
|
amount_from = b[4]
|
|
|
|
rate = b[10]
|
|
|
|
if reverse_bid:
|
|
|
|
amount_from, rate = reverseBidAmountAndRate(swap_client, offer, amount_from, rate)
|
|
|
|
|
|
|
|
amt_swapped += b[4]
|
|
|
|
formatted_bids.append((b[2].hex(), ci_from.format_amount(amount_from), strBidState(b[5]), ci_to.format_amount(rate), b[11]))
|
|
|
|
data['amt_swapped'] = ci_from.format_amount(amt_swapped)
|
|
|
|
|
|
|
|
template = server.env.get_template('offer.html')
|
|
|
|
return self.render_template(template, {
|
|
|
|
'offer_id': offer_id.hex(),
|
|
|
|
'sent_bid_id': sent_bid_id,
|
|
|
|
'messages': messages,
|
|
|
|
'err_messages': err_messages,
|
|
|
|
'data': data,
|
|
|
|
'bids': formatted_bids,
|
|
|
|
'addrs': None if show_bid_form is None else swap_client.listSmsgAddresses('bid'),
|
|
|
|
'summary': summary,
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
def page_offers(self, url_split, post_string, sent=False):
|
|
|
|
server = self.server
|
|
|
|
swap_client = server.swap_client
|
|
|
|
swap_client.checkSystemStatus()
|
|
|
|
summary = swap_client.getSummary()
|
|
|
|
|
|
|
|
filters = {
|
|
|
|
'coin_from': -1,
|
|
|
|
'coin_to': -1,
|
|
|
|
'page_no': 1,
|
|
|
|
'limit': PAGE_LIMIT,
|
|
|
|
'sort_by': 'created_at',
|
|
|
|
'sort_dir': 'desc',
|
|
|
|
'sent_from': 'any' if sent is False else 'only',
|
|
|
|
'active': 'any',
|
|
|
|
}
|
|
|
|
|
|
|
|
filter_prefix = 'page_offers_sent' if sent else 'page_offers'
|
|
|
|
messages = []
|
|
|
|
form_data = self.checkForm(post_string, 'offers', messages)
|
|
|
|
if form_data:
|
|
|
|
if have_data_entry(form_data, 'clearfilters'):
|
|
|
|
swap_client.clearFilters(filter_prefix)
|
|
|
|
else:
|
|
|
|
filters['coin_from'] = setCoinFilter(form_data, 'coin_from')
|
|
|
|
filters['coin_to'] = setCoinFilter(form_data, 'coin_to')
|
|
|
|
|
|
|
|
if have_data_entry(form_data, 'sort_by'):
|
|
|
|
sort_by = get_data_entry(form_data, 'sort_by')
|
|
|
|
ensure(sort_by in ['created_at', 'rate'], 'Invalid sort by')
|
|
|
|
filters['sort_by'] = sort_by
|
|
|
|
if have_data_entry(form_data, 'sort_dir'):
|
|
|
|
sort_dir = get_data_entry(form_data, 'sort_dir')
|
|
|
|
ensure(sort_dir in ['asc', 'desc'], 'Invalid sort dir')
|
|
|
|
filters['sort_dir'] = sort_dir
|
|
|
|
if have_data_entry(form_data, 'sent_from'):
|
|
|
|
sent_from = get_data_entry(form_data, 'sent_from')
|
|
|
|
ensure(sent_from in ['any', 'only'], 'Invalid sent filter')
|
|
|
|
filters['sent_from'] = sent_from
|
|
|
|
if have_data_entry(form_data, 'active'):
|
|
|
|
active_filter = get_data_entry(form_data, 'active')
|
|
|
|
ensure(active_filter in ['any', 'active', 'expired', 'revoked', 'archived'], 'Invalid active filter')
|
|
|
|
filters['active'] = active_filter
|
|
|
|
|
|
|
|
set_pagination_filters(form_data, filters)
|
|
|
|
if have_data_entry(form_data, 'applyfilters'):
|
|
|
|
swap_client.setFilters(filter_prefix, filters)
|
|
|
|
else:
|
|
|
|
saved_filters = swap_client.getFilters(filter_prefix)
|
|
|
|
if saved_filters:
|
|
|
|
filters.update(saved_filters)
|
|
|
|
|
|
|
|
if filters['sent_from'] == 'only':
|
|
|
|
sent = True
|
|
|
|
else:
|
|
|
|
sent = False
|
|
|
|
offers = swap_client.listOffers(sent, filters, with_bid_info=True)
|
|
|
|
|
|
|
|
now: int = swap_client.getTime()
|
|
|
|
formatted_offers = []
|
|
|
|
for row in offers:
|
|
|
|
o, completed_amount = row
|
|
|
|
ci_from = swap_client.ci(Coins(o.coin_from))
|
|
|
|
ci_to = swap_client.ci(Coins(o.coin_to))
|
|
|
|
is_expired = o.expire_at <= now
|
|
|
|
amount_negotiable = "Yes" if o.amount_negotiable else "No"
|
|
|
|
formatted_offers.append((
|
|
|
|
format_timestamp(o.created_at),
|
|
|
|
o.offer_id.hex(),
|
|
|
|
ci_from.coin_name(),
|
|
|
|
ci_to.coin_name(),
|
|
|
|
ci_from.format_amount(o.amount_from),
|
|
|
|
ci_to.format_amount((o.amount_from * o.rate) // ci_from.COIN()),
|
|
|
|
ci_to.format_amount(o.rate),
|
|
|
|
'Public' if o.addr_to == swap_client.network_addr else o.addr_to,
|
|
|
|
o.addr_from,
|
|
|
|
o.was_sent,
|
|
|
|
ci_from.format_amount(completed_amount),
|
|
|
|
is_expired,
|
|
|
|
o.active_ind,
|
|
|
|
strSwapDesc(o.swap_type),
|
|
|
|
amount_negotiable))
|
|
|
|
|
|
|
|
coins_from, coins_to = listAvailableCoins(swap_client, split_from=True)
|
|
|
|
|
|
|
|
chart_api_key = swap_client.settings.get('chart_api_key', '')
|
|
|
|
if chart_api_key == '':
|
|
|
|
chart_api_key_enc = swap_client.settings.get('chart_api_key_enc', '')
|
|
|
|
chart_api_key = 'cd7600e7b5fdd99c6f900673ff0ee8f64d6d4219a4bb87191ad4a2e3fc65d7f4' if chart_api_key_enc == '' else bytes.fromhex(chart_api_key_enc).decode('utf-8')
|
|
|
|
|
|
|
|
template = server.env.get_template('offers.html')
|
|
|
|
return self.render_template(template, {
|
|
|
|
'page_type': 'Your Offers' if sent else 'Network Order Book',
|
|
|
|
'page_button': 'hidden' if sent else '',
|
|
|
|
'page_type_description': 'Your entire offer history.' if sent else 'Consult available offers in the order book and initiate a coin swap.',
|
|
|
|
'messages': messages,
|
|
|
|
'show_chart': False if sent else swap_client.settings.get('show_chart', True),
|
|
|
|
'chart_api_key': chart_api_key,
|
|
|
|
'coins_from': coins_from,
|
|
|
|
'coins': coins_to,
|
|
|
|
'messages': messages,
|
|
|
|
'filters': filters,
|
|
|
|
'offers': formatted_offers,
|
|
|
|
'summary': summary,
|
|
|
|
'sent_offers': sent,
|
|
|
|
})
|