|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
|
|
|
# Copyright (c) 2020-2024 tecnovert
|
|
|
|
# Distributed under the MIT software license, see the accompanying
|
|
|
|
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
|
|
|
|
import json
|
|
|
|
import random
|
|
|
|
import urllib.parse
|
|
|
|
|
|
|
|
from .util import (
|
|
|
|
ensure,
|
|
|
|
toBool,
|
|
|
|
)
|
|
|
|
from .basicswap_util import (
|
|
|
|
strBidState,
|
|
|
|
SwapTypes,
|
|
|
|
NotificationTypes as NT,
|
|
|
|
)
|
|
|
|
from .chainparams import (
|
|
|
|
Coins,
|
|
|
|
chainparams,
|
|
|
|
)
|
|
|
|
from .ui.util import (
|
|
|
|
PAGE_LIMIT,
|
|
|
|
getCoinName,
|
|
|
|
getCoinType,
|
|
|
|
inputAmount,
|
|
|
|
describeBid,
|
|
|
|
setCoinFilter,
|
|
|
|
get_data_entry,
|
|
|
|
get_data_entry_or,
|
|
|
|
have_data_entry,
|
|
|
|
tickerToCoinId,
|
|
|
|
listOldBidStates,
|
|
|
|
checkAddressesOwned,
|
|
|
|
)
|
|
|
|
from .ui.page_offers import postNewOffer
|
|
|
|
from .protocols.xmr_swap_1 import recoverNoScriptTxnWithKey, getChainBSplitKey
|
|
|
|
|
|
|
|
|
|
|
|
def getFormData(post_string: str, is_json: bool):
|
|
|
|
if post_string == '':
|
|
|
|
raise ValueError('No post data')
|
|
|
|
if is_json:
|
|
|
|
form_data = json.loads(post_string)
|
|
|
|
form_data['is_json'] = True
|
|
|
|
else:
|
|
|
|
form_data = urllib.parse.parse_qs(post_string)
|
|
|
|
return form_data
|
|
|
|
|
|
|
|
|
|
|
|
def withdraw_coin(swap_client, coin_type, post_string, is_json):
|
|
|
|
post_data = getFormData(post_string, is_json)
|
|
|
|
address = get_data_entry(post_data, 'address')
|
|
|
|
|
|
|
|
if coin_type in (Coins.XMR, Coins.WOW):
|
|
|
|
value = None
|
|
|
|
sweepall = get_data_entry(post_data, 'sweepall')
|
|
|
|
if not isinstance(sweepall, bool):
|
|
|
|
sweepall = toBool(sweepall)
|
|
|
|
if not sweepall:
|
|
|
|
value = get_data_entry(post_data, 'value')
|
|
|
|
else:
|
|
|
|
value = get_data_entry(post_data, 'value')
|
|
|
|
subfee = get_data_entry(post_data, 'subfee')
|
|
|
|
if not isinstance(subfee, bool):
|
|
|
|
subfee = toBool(subfee)
|
|
|
|
|
|
|
|
if coin_type == Coins.PART:
|
|
|
|
type_from = get_data_entry_or(post_data, 'type_from', 'plain')
|
|
|
|
type_to = get_data_entry_or(post_data, 'type_to', 'plain')
|
|
|
|
txid_hex = swap_client.withdrawParticl(type_from, type_to, value, address, subfee)
|
|
|
|
elif coin_type == Coins.LTC:
|
|
|
|
type_from = get_data_entry_or(post_data, 'type_from', 'plain')
|
|
|
|
txid_hex = swap_client.withdrawLTC(type_from, value, address, subfee)
|
|
|
|
elif coin_type in (Coins.XMR, Coins.WOW):
|
|
|
|
txid_hex = swap_client.withdrawCoin(coin_type, value, address, sweepall)
|
|
|
|
else:
|
|
|
|
txid_hex = swap_client.withdrawCoin(coin_type, value, address, subfee)
|
|
|
|
|
|
|
|
return {'txid': txid_hex}
|
|
|
|
|
|
|
|
|
|
|
|
def js_error(self, error_str) -> bytes:
|
|
|
|
error_str_json = json.dumps({'error': error_str})
|
|
|
|
return bytes(error_str_json, 'UTF-8')
|
|
|
|
|
|
|
|
|
|
|
|
def js_coins(self, url_split, post_string, is_json) -> bytes:
|
|
|
|
swap_client = self.server.swap_client
|
|
|
|
|
|
|
|
coins = []
|
|
|
|
for coin in Coins:
|
|
|
|
cc = swap_client.coin_clients[coin]
|
|
|
|
coin_chainparams = chainparams[cc['coin']]
|
|
|
|
coin_active: bool = False if cc['connection_type'] == 'none' else True
|
|
|
|
if coin == Coins.LTC_MWEB:
|
|
|
|
coin_active = False
|
|
|
|
entry = {
|
|
|
|
'id': int(coin),
|
|
|
|
'ticker': coin_chainparams['ticker'],
|
|
|
|
'name': getCoinName(coin),
|
|
|
|
'active': coin_active,
|
|
|
|
'decimal_places': coin_chainparams['decimal_places'],
|
|
|
|
}
|
|
|
|
if coin == Coins.PART_ANON:
|
|
|
|
entry['variant'] = 'Anon'
|
|
|
|
elif coin == Coins.PART_BLIND:
|
|
|
|
entry['variant'] = 'Blind'
|
|
|
|
elif coin == Coins.LTC_MWEB:
|
|
|
|
entry['variant'] = 'MWEB'
|
|
|
|
coins.append(entry)
|
|
|
|
|
|
|
|
return bytes(json.dumps(coins), 'UTF-8')
|
|
|
|
|
|
|
|
|
|
|
|
def js_wallets(self, url_split, post_string, is_json):
|
|
|
|
swap_client = self.server.swap_client
|
|
|
|
swap_client.checkSystemStatus()
|
|
|
|
if len(url_split) > 3:
|
|
|
|
ticker_str = url_split[3]
|
|
|
|
coin_type = tickerToCoinId(ticker_str)
|
|
|
|
|
|
|
|
if len(url_split) > 4:
|
|
|
|
cmd = url_split[4]
|
|
|
|
if cmd == 'withdraw':
|
|
|
|
return bytes(json.dumps(withdraw_coin(swap_client, coin_type, post_string, is_json)), 'UTF-8')
|
|
|
|
elif cmd == 'nextdepositaddr':
|
|
|
|
return bytes(json.dumps(swap_client.cacheNewAddressForCoin(coin_type)), 'UTF-8')
|
|
|
|
elif cmd == 'createutxo':
|
|
|
|
post_data = getFormData(post_string, is_json)
|
|
|
|
ci = swap_client.ci(coin_type)
|
|
|
|
value = ci.make_int(get_data_entry(post_data, 'value'))
|
|
|
|
txid_hex, new_addr = ci.createUTXO(value)
|
|
|
|
return bytes(json.dumps({'txid': txid_hex, 'address': new_addr}), 'UTF-8')
|
|
|
|
elif cmd == 'reseed':
|
|
|
|
swap_client.reseedWallet(coin_type)
|
|
|
|
return bytes(json.dumps({'reseeded': True}), 'UTF-8')
|
|
|
|
elif cmd == 'newstealthaddress':
|
|
|
|
if coin_type != Coins.PART:
|
|
|
|
raise ValueError('Invalid coin for command')
|
|
|
|
return bytes(json.dumps(swap_client.ci(coin_type).getNewStealthAddress()), 'UTF-8')
|
|
|
|
elif cmd == 'newmwebaddress':
|
|
|
|
if coin_type not in (Coins.LTC, Coins.LTC_MWEB):
|
|
|
|
raise ValueError('Invalid coin for command')
|
|
|
|
return bytes(json.dumps(swap_client.ci(coin_type).getNewMwebAddress()), 'UTF-8')
|
|
|
|
|
|
|
|
raise ValueError('Unknown command')
|
|
|
|
|
|
|
|
if coin_type == Coins.LTC_MWEB:
|
|
|
|
coin_type = Coins.LTC
|
|
|
|
rv = swap_client.getWalletInfo(coin_type)
|
|
|
|
rv.update(swap_client.getBlockchainInfo(coin_type))
|
|
|
|
ci = swap_client.ci(coin_type)
|
|
|
|
checkAddressesOwned(swap_client, ci, rv)
|
|
|
|
return bytes(json.dumps(rv), 'UTF-8')
|
|
|
|
|
|
|
|
return bytes(json.dumps(swap_client.getWalletsInfo({'ticker_key': True})), 'UTF-8')
|
|
|
|
|
|
|
|
|
|
|
|
def js_offers(self, url_split, post_string, is_json, sent=False) -> bytes:
|
|
|
|
swap_client = self.server.swap_client
|
|
|
|
swap_client.checkSystemStatus()
|
|
|
|
offer_id = None
|
|
|
|
if len(url_split) > 3:
|
|
|
|
if url_split[3] == 'new':
|
|
|
|
form_data = getFormData(post_string, is_json)
|
|
|
|
offer_id = postNewOffer(swap_client, form_data)
|
|
|
|
rv = {'offer_id': offer_id.hex()}
|
|
|
|
return bytes(json.dumps(rv), 'UTF-8')
|
|
|
|
offer_id = bytes.fromhex(url_split[3])
|
|
|
|
|
|
|
|
with_extra_info = False
|
|
|
|
filters = {
|
|
|
|
'coin_from': -1,
|
|
|
|
'coin_to': -1,
|
|
|
|
'page_no': 1,
|
|
|
|
'limit': PAGE_LIMIT,
|
|
|
|
'sort_by': 'created_at',
|
|
|
|
'sort_dir': 'desc',
|
|
|
|
}
|
|
|
|
|
|
|
|
if offer_id:
|
|
|
|
filters['offer_id'] = offer_id
|
|
|
|
|
|
|
|
if post_string != '':
|
|
|
|
post_data = getFormData(post_string, is_json)
|
|
|
|
filters['coin_from'] = setCoinFilter(post_data, 'coin_from')
|
|
|
|
filters['coin_to'] = setCoinFilter(post_data, 'coin_to')
|
|
|
|
|
|
|
|
if have_data_entry(post_data, 'sort_by'):
|
|
|
|
sort_by = get_data_entry(post_data, 'sort_by')
|
|
|
|
assert (sort_by in ['created_at', 'rate']), 'Invalid sort by'
|
|
|
|
filters['sort_by'] = sort_by
|
|
|
|
if have_data_entry(post_data, 'sort_dir'):
|
|
|
|
sort_dir = get_data_entry(post_data, 'sort_dir')
|
|
|
|
assert (sort_dir in ['asc', 'desc']), 'Invalid sort dir'
|
|
|
|
filters['sort_dir'] = sort_dir
|
|
|
|
|
|
|
|
if have_data_entry(post_data, 'offset'):
|
|
|
|
filters['offset'] = int(get_data_entry(post_data, 'offset'))
|
|
|
|
if have_data_entry(post_data, 'limit'):
|
|
|
|
filters['limit'] = int(get_data_entry(post_data, 'limit'))
|
|
|
|
assert (filters['limit'] > 0 and filters['limit'] <= PAGE_LIMIT), 'Invalid limit'
|
|
|
|
if have_data_entry(post_data, 'active'):
|
|
|
|
filters['active'] = get_data_entry(post_data, 'active')
|
|
|
|
if have_data_entry(post_data, 'include_sent'):
|
|
|
|
filters['include_sent'] = toBool(get_data_entry(post_data, 'include_sent'))
|
|
|
|
|
|
|
|
if have_data_entry(post_data, 'with_extra_info'):
|
|
|
|
with_extra_info = toBool(get_data_entry(post_data, 'with_extra_info'))
|
|
|
|
|
|
|
|
offers = swap_client.listOffers(sent, filters)
|
|
|
|
rv = []
|
|
|
|
for o in offers:
|
|
|
|
ci_from = swap_client.ci(o.coin_from)
|
|
|
|
ci_to = swap_client.ci(o.coin_to)
|
|
|
|
offer_data = {
|
|
|
|
'swap_type': o.swap_type,
|
|
|
|
'addr_from': o.addr_from,
|
|
|
|
'addr_to': o.addr_to,
|
|
|
|
'offer_id': o.offer_id.hex(),
|
|
|
|
'created_at': o.created_at,
|
|
|
|
'expire_at': o.expire_at,
|
|
|
|
'coin_from': ci_from.coin_name(),
|
|
|
|
'coin_to': ci_to.coin_name(),
|
|
|
|
'amount_from': ci_from.format_amount(o.amount_from),
|
|
|
|
'amount_to': ci_to.format_amount((o.amount_from * o.rate) // ci_from.COIN()),
|
|
|
|
'rate': ci_to.format_amount(o.rate),
|
|
|
|
'min_bid_amount': ci_from.format_amount(o.min_bid_amount),
|
|
|
|
}
|
|
|
|
if with_extra_info:
|
|
|
|
offer_data['amount_negotiable'] = o.amount_negotiable
|
|
|
|
offer_data['rate_negotiable'] = o.rate_negotiable
|
|
|
|
|
|
|
|
if o.swap_type == SwapTypes.XMR_SWAP:
|
|
|
|
_, xmr_offer = swap_client.getXmrOffer(o.offer_id)
|
|
|
|
offer_data['lock_time_1'] = xmr_offer.lock_time_1
|
|
|
|
offer_data['lock_time_2'] = xmr_offer.lock_time_2
|
|
|
|
|
|
|
|
offer_data['feerate_from'] = xmr_offer.a_fee_rate
|
|
|
|
offer_data['feerate_to'] = xmr_offer.b_fee_rate
|
|
|
|
else:
|
|
|
|
offer_data['feerate_from'] = o.from_feerate
|
|
|
|
offer_data['feerate_to'] = o.to_feerate
|
|
|
|
|
|
|
|
rv.append(offer_data)
|
|
|
|
return bytes(json.dumps(rv), 'UTF-8')
|
|
|
|
|
|
|
|
|
|
|
|
def js_sentoffers(self, url_split, post_string, is_json) -> bytes:
|
|
|
|
return js_offers(self, url_split, post_string, is_json, True)
|
|
|
|
|
|
|
|
|
|
|
|
def parseBidFilters(post_data):
|
|
|
|
offer_id = None
|
|
|
|
filters = {}
|
|
|
|
|
|
|
|
if have_data_entry(post_data, 'offer_id'):
|
|
|
|
offer_id = bytes.fromhex(get_data_entry(post_data, 'offer_id'))
|
|
|
|
assert (len(offer_id) == 28)
|
|
|
|
|
|
|
|
if have_data_entry(post_data, 'sort_by'):
|
|
|
|
sort_by = get_data_entry(post_data, 'sort_by')
|
|
|
|
assert (sort_by in ['created_at', ]), 'Invalid sort by'
|
|
|
|
filters['sort_by'] = sort_by
|
|
|
|
if have_data_entry(post_data, 'sort_dir'):
|
|
|
|
sort_dir = get_data_entry(post_data, 'sort_dir')
|
|
|
|
assert (sort_dir in ['asc', 'desc']), 'Invalid sort dir'
|
|
|
|
filters['sort_dir'] = sort_dir
|
|
|
|
|
|
|
|
if have_data_entry(post_data, 'offset'):
|
|
|
|
filters['offset'] = int(get_data_entry(post_data, 'offset'))
|
|
|
|
if have_data_entry(post_data, 'limit'):
|
|
|
|
filters['limit'] = int(get_data_entry(post_data, 'limit'))
|
|
|
|
assert (filters['limit'] > 0 and filters['limit'] <= PAGE_LIMIT), 'Invalid limit'
|
|
|
|
|
|
|
|
if have_data_entry(post_data, 'with_available_or_active'):
|
|
|
|
filters['with_available_or_active'] = toBool(get_data_entry(post_data, 'with_available_or_active'))
|
|
|
|
elif have_data_entry(post_data, 'with_expired'):
|
|
|
|
filters['with_expired'] = toBool(get_data_entry(post_data, 'with_expired'))
|
|
|
|
|
|
|
|
if have_data_entry(post_data, 'with_extra_info'):
|
|
|
|
filters['with_extra_info'] = toBool(get_data_entry(post_data, 'with_extra_info'))
|
|
|
|
|
|
|
|
return offer_id, filters
|
|
|
|
|
|
|
|
|
|
|
|
def formatBids(swap_client, bids, filters) -> bytes:
|
|
|
|
with_extra_info = filters.get('with_extra_info', False)
|
|
|
|
rv = []
|
|
|
|
for b in bids:
|
|
|
|
bid_data = {
|
|
|
|
'bid_id': b[2].hex(),
|
|
|
|
'offer_id': b[3].hex(),
|
|
|
|
'created_at': b[0],
|
|
|
|
'expire_at': b[1],
|
|
|
|
'coin_from': b[9],
|
|
|
|
'amount_from': swap_client.ci(b[9]).format_amount(b[4]),
|
|
|
|
'bid_rate': swap_client.ci(b[14]).format_amount(b[10]),
|
|
|
|
'bid_state': strBidState(b[5])
|
|
|
|
}
|
|
|
|
if with_extra_info:
|
|
|
|
bid_data['addr_from'] = b[11]
|
|
|
|
rv.append(bid_data)
|
|
|
|
return bytes(json.dumps(rv), 'UTF-8')
|
|
|
|
|
|
|
|
|
|
|
|
def js_bids(self, url_split, post_string: str, is_json: bool) -> bytes:
|
|
|
|
swap_client = self.server.swap_client
|
|
|
|
swap_client.checkSystemStatus()
|
|
|
|
if len(url_split) > 3:
|
|
|
|
if url_split[3] == 'new':
|
|
|
|
post_data = getFormData(post_string, is_json)
|
|
|
|
|
|
|
|
offer_id = bytes.fromhex(get_data_entry(post_data, 'offer_id'))
|
|
|
|
assert (len(offer_id) == 28)
|
|
|
|
|
|
|
|
offer = swap_client.getOffer(offer_id)
|
|
|
|
assert (offer), 'Offer not found.'
|
|
|
|
|
|
|
|
ci_from = swap_client.ci(offer.coin_from)
|
|
|
|
ci_to = swap_client.ci(offer.coin_to)
|
|
|
|
amount_from = inputAmount(get_data_entry(post_data, 'amount_from'), ci_from)
|
|
|
|
|
|
|
|
addr_from = None
|
|
|
|
if have_data_entry(post_data, 'addr_from'):
|
|
|
|
addr_from = get_data_entry(post_data, 'addr_from')
|
|
|
|
if addr_from == '-1':
|
|
|
|
addr_from = None
|
|
|
|
|
|
|
|
if have_data_entry(post_data, 'validmins'):
|
|
|
|
valid_for_seconds = int(get_data_entry(post_data, 'validmins')) * 60
|
|
|
|
elif have_data_entry(post_data, 'valid_for_seconds'):
|
|
|
|
valid_for_seconds = int(get_data_entry(post_data, 'valid_for_seconds'))
|
|
|
|
else:
|
|
|
|
valid_for_seconds = 10 * 60
|
|
|
|
|
|
|
|
extra_options = {
|
|
|
|
'valid_for_seconds': valid_for_seconds,
|
|
|
|
}
|
|
|
|
|
|
|
|
if have_data_entry(post_data, 'amount_to'):
|
|
|
|
extra_options['amount_to'] = inputAmount(get_data_entry(post_data, 'amount_to'), ci_to)
|
|
|
|
elif have_data_entry(post_data, 'bid_rate'):
|
|
|
|
extra_options['bid_rate'] = ci_to.make_int(get_data_entry(post_data, 'bid_rate'), r=1)
|
|
|
|
if have_data_entry(post_data, 'bid_amount'):
|
|
|
|
amount_from = inputAmount(get_data_entry(post_data, 'bid_amount'), ci_from)
|
|
|
|
|
|
|
|
if offer.swap_type == SwapTypes.XMR_SWAP:
|
|
|
|
bid_id = swap_client.postXmrBid(offer_id, amount_from, addr_send_from=addr_from, extra_options=extra_options)
|
|
|
|
else:
|
|
|
|
bid_id = swap_client.postBid(offer_id, amount_from, addr_send_from=addr_from, extra_options=extra_options)
|
|
|
|
|
|
|
|
if have_data_entry(post_data, 'debugind'):
|
|
|
|
swap_client.setBidDebugInd(bid_id, int(get_data_entry(post_data, 'debugind')))
|
|
|
|
|
|
|
|
rv = {'bid_id': bid_id.hex()}
|
|
|
|
return bytes(json.dumps(rv), 'UTF-8')
|
|
|
|
|
|
|
|
bid_id = bytes.fromhex(url_split[3])
|
|
|
|
assert (len(bid_id) == 28)
|
|
|
|
|
|
|
|
show_txns = False
|
|
|
|
if post_string != '':
|
|
|
|
post_data = getFormData(post_string, is_json)
|
|
|
|
if have_data_entry(post_data, 'accept'):
|
|
|
|
swap_client.acceptBid(bid_id)
|
|
|
|
elif have_data_entry(post_data, 'abandon'):
|
|
|
|
swap_client.abandonBid(bid_id)
|
|
|
|
elif have_data_entry(post_data, 'debugind'):
|
|
|
|
swap_client.setBidDebugInd(bid_id, int(get_data_entry(post_data, 'debugind')))
|
|
|
|
|
|
|
|
if have_data_entry(post_data, 'show_extra'):
|
|
|
|
show_txns = True
|
|
|
|
|
|
|
|
bid, xmr_swap, offer, xmr_offer, events = swap_client.getXmrBidAndOffer(bid_id)
|
|
|
|
assert (bid), 'Unknown bid ID'
|
|
|
|
|
|
|
|
if post_string != '':
|
|
|
|
if have_data_entry(post_data, 'chainbkeysplit'):
|
|
|
|
return bytes(json.dumps({'splitkey': getChainBSplitKey(swap_client, bid, xmr_swap, offer)}), 'UTF-8')
|
|
|
|
elif have_data_entry(post_data, 'spendchainblocktx'):
|
|
|
|
remote_key = get_data_entry(post_data, 'remote_key')
|
|
|
|
return bytes(json.dumps({'txid': recoverNoScriptTxnWithKey(swap_client, bid_id, remote_key).hex()}), 'UTF-8')
|
|
|
|
|
|
|
|
if len(url_split) > 4 and url_split[4] == 'states':
|
|
|
|
old_states = listOldBidStates(bid)
|
|
|
|
return bytes(json.dumps(old_states), 'UTF-8')
|
|
|
|
|
|
|
|
edit_bid = False
|
|
|
|
data = describeBid(swap_client, bid, xmr_swap, offer, xmr_offer, events, edit_bid, show_txns, for_api=True)
|
|
|
|
return bytes(json.dumps(data), 'UTF-8')
|
|
|
|
|
|
|
|
post_data = {} if post_string == '' else getFormData(post_string, is_json)
|
|
|
|
offer_id, filters = parseBidFilters(post_data)
|
|
|
|
|
|
|
|
bids = swap_client.listBids(offer_id=offer_id, filters=filters)
|
|
|
|
return formatBids(swap_client, bids, filters)
|
|
|
|
|
|
|
|
|
|
|
|
def js_sentbids(self, url_split, post_string, is_json) -> bytes:
|
|
|
|
swap_client = self.server.swap_client
|
|
|
|
swap_client.checkSystemStatus()
|
|
|
|
post_data = getFormData(post_string, is_json)
|
|
|
|
offer_id, filters = parseBidFilters(post_data)
|
|
|
|
|
|
|
|
bids = swap_client.listBids(sent=True, offer_id=offer_id, filters=filters)
|
|
|
|
return formatBids(swap_client, bids, filters)
|
|
|
|
|
|
|
|
|
|
|
|
def js_network(self, url_split, post_string, is_json) -> bytes:
|
|
|
|
swap_client = self.server.swap_client
|
|
|
|
swap_client.checkSystemStatus()
|
|
|
|
return bytes(json.dumps(swap_client.get_network_info()), 'UTF-8')
|
|
|
|
|
|
|
|
|
|
|
|
def js_revokeoffer(self, url_split, post_string, is_json) -> bytes:
|
|
|
|
swap_client = self.server.swap_client
|
|
|
|
swap_client.checkSystemStatus()
|
|
|
|
offer_id = bytes.fromhex(url_split[3])
|
|
|
|
assert (len(offer_id) == 28)
|
|
|
|
swap_client.revokeOffer(offer_id)
|
|
|
|
return bytes(json.dumps({'revoked_offer': offer_id.hex()}), 'UTF-8')
|
|
|
|
|
|
|
|
|
|
|
|
def js_smsgaddresses(self, url_split, post_string, is_json) -> bytes:
|
|
|
|
swap_client = self.server.swap_client
|
|
|
|
swap_client.checkSystemStatus()
|
|
|
|
post_data = {} if post_string == '' else getFormData(post_string, is_json)
|
|
|
|
|
|
|
|
if len(url_split) > 3:
|
|
|
|
mode: str = url_split[3]
|
|
|
|
if mode == 'new':
|
|
|
|
addressnote = get_data_entry_or(post_data, 'addressnote', '')
|
|
|
|
new_addr, pubkey = swap_client.newSMSGAddress(addressnote=addressnote)
|
|
|
|
return bytes(json.dumps({'new_address': new_addr, 'pubkey': pubkey}), 'UTF-8')
|
|
|
|
if mode == 'add':
|
|
|
|
addressnote = get_data_entry_or(post_data, 'addressnote', '')
|
|
|
|
pubkey_hex = get_data_entry(post_data, 'addresspubkey')
|
|
|
|
added_address = swap_client.addSMSGAddress(pubkey_hex, addressnote)
|
|
|
|
return bytes(json.dumps({'added_address': added_address, 'pubkey': pubkey_hex}), 'UTF-8')
|
|
|
|
elif mode == 'edit':
|
|
|
|
address = get_data_entry(post_data, 'address')
|
|
|
|
activeind = int(get_data_entry(post_data, 'active_ind'))
|
|
|
|
addressnote = get_data_entry_or(post_data, 'addressnote', '')
|
|
|
|
new_addr = swap_client.editSMSGAddress(address, activeind, addressnote)
|
|
|
|
return bytes(json.dumps({'edited_address': address}), 'UTF-8')
|
|
|
|
elif mode == 'disableall':
|
|
|
|
rv = swap_client.disableAllSMSGAddresses()
|
|
|
|
return bytes(json.dumps(rv), 'UTF-8')
|
|
|
|
|
|
|
|
filters = {
|
|
|
|
'exclude_inactive': post_data.get('exclude_inactive', True),
|
|
|
|
}
|
|
|
|
return bytes(json.dumps(swap_client.listAllSMSGAddresses(filters)), 'UTF-8')
|
|
|
|
|
|
|
|
|
|
|
|
def js_rates(self, url_split, post_string, is_json) -> bytes:
|
|
|
|
post_data = getFormData(post_string, is_json)
|
|
|
|
|
|
|
|
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_rates_list(self, url_split, query_string, is_json) -> bytes:
|
|
|
|
get_data = urllib.parse.parse_qs(query_string)
|
|
|
|
|
|
|
|
sc = self.server.swap_client
|
|
|
|
coin_from = getCoinType(get_data['from'][0])
|
|
|
|
coin_to = getCoinType(get_data['to'][0])
|
|
|
|
return bytes(json.dumps(sc.lookupRates(coin_from, coin_to, True)), 'UTF-8')
|
|
|
|
|
|
|
|
|
|
|
|
def js_rate(self, url_split, post_string, is_json) -> bytes:
|
|
|
|
post_data = getFormData(post_string, is_json)
|
|
|
|
|
|
|
|
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: int = inputAmount(get_data_entry(post_data, 'amt_from'), ci_from)
|
|
|
|
amt_to: int = inputAmount(get_data_entry(post_data, 'amt_to'), ci_to)
|
|
|
|
|
|
|
|
rate: int = 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) -> bytes:
|
|
|
|
swap_client = self.server.swap_client
|
|
|
|
swap_client.checkSystemStatus()
|
|
|
|
return bytes(json.dumps(swap_client.getSummary()), 'UTF-8')
|
|
|
|
|
|
|
|
|
|
|
|
def js_generatenotification(self, url_split, post_string, is_json) -> bytes:
|
|
|
|
swap_client = self.server.swap_client
|
|
|
|
|
|
|
|
if not swap_client.debug:
|
|
|
|
raise ValueError('Debug mode not active.')
|
|
|
|
|
|
|
|
r = random.randint(0, 3)
|
|
|
|
if r == 0:
|
|
|
|
swap_client.notify(NT.OFFER_RECEIVED, {'offer_id': random.randbytes(28).hex()})
|
|
|
|
elif r == 1:
|
|
|
|
swap_client.notify(NT.BID_RECEIVED, {'type': 'atomic', 'bid_id': random.randbytes(28).hex(), 'offer_id': random.randbytes(28).hex()})
|
|
|
|
elif r == 2:
|
|
|
|
swap_client.notify(NT.BID_ACCEPTED, {'bid_id': random.randbytes(28).hex()})
|
|
|
|
elif r == 3:
|
|
|
|
swap_client.notify(NT.BID_RECEIVED, {'type': 'ads', 'bid_id': random.randbytes(28).hex(), 'offer_id': random.randbytes(28).hex()})
|
|
|
|
|
|
|
|
return bytes(json.dumps({'type': r}), 'UTF-8')
|
|
|
|
|
|
|
|
|
|
|
|
def js_notifications(self, url_split, post_string, is_json) -> bytes:
|
|
|
|
swap_client = self.server.swap_client
|
|
|
|
swap_client.checkSystemStatus()
|
|
|
|
|
|
|
|
return bytes(json.dumps(swap_client.getNotifications()), 'UTF-8')
|
|
|
|
|
|
|
|
|
|
|
|
def js_identities(self, url_split, post_string: str, is_json: bool) -> bytes:
|
|
|
|
swap_client = self.server.swap_client
|
|
|
|
swap_client.checkSystemStatus()
|
|
|
|
|
|
|
|
filters = {
|
|
|
|
'page_no': 1,
|
|
|
|
'limit': PAGE_LIMIT,
|
|
|
|
'sort_by': 'created_at',
|
|
|
|
'sort_dir': 'desc',
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(url_split) > 3:
|
|
|
|
address = url_split[3]
|
|
|
|
filters['address'] = address
|
|
|
|
|
|
|
|
if post_string != '':
|
|
|
|
post_data = getFormData(post_string, is_json)
|
|
|
|
|
|
|
|
if have_data_entry(post_data, 'sort_by'):
|
|
|
|
sort_by = get_data_entry(post_data, 'sort_by')
|
|
|
|
assert (sort_by in ['created_at', 'rate']), 'Invalid sort by'
|
|
|
|
filters['sort_by'] = sort_by
|
|
|
|
if have_data_entry(post_data, 'sort_dir'):
|
|
|
|
sort_dir = get_data_entry(post_data, 'sort_dir')
|
|
|
|
assert (sort_dir in ['asc', 'desc']), 'Invalid sort dir'
|
|
|
|
filters['sort_dir'] = sort_dir
|
|
|
|
|
|
|
|
if have_data_entry(post_data, 'offset'):
|
|
|
|
filters['offset'] = int(get_data_entry(post_data, 'offset'))
|
|
|
|
if have_data_entry(post_data, 'limit'):
|
|
|
|
filters['limit'] = int(get_data_entry(post_data, 'limit'))
|
|
|
|
assert (filters['limit'] > 0 and filters['limit'] <= PAGE_LIMIT), 'Invalid limit'
|
|
|
|
|
|
|
|
set_data = {}
|
|
|
|
if have_data_entry(post_data, 'set_label'):
|
|
|
|
set_data['label'] = get_data_entry(post_data, 'set_label')
|
|
|
|
if have_data_entry(post_data, 'set_automation_override'):
|
|
|
|
set_data['automation_override'] = get_data_entry(post_data, 'set_automation_override')
|
|
|
|
if have_data_entry(post_data, 'set_visibility_override'):
|
|
|
|
set_data['visibility_override'] = get_data_entry(post_data, 'set_visibility_override')
|
|
|
|
if have_data_entry(post_data, 'set_note'):
|
|
|
|
set_data['note'] = get_data_entry(post_data, 'set_note')
|
|
|
|
|
|
|
|
if set_data:
|
|
|
|
ensure('address' in filters, 'Must provide an address to modify data')
|
|
|
|
swap_client.setIdentityData(filters, set_data)
|
|
|
|
|
|
|
|
return bytes(json.dumps(swap_client.listIdentities(filters)), 'UTF-8')
|
|
|
|
|
|
|
|
|
|
|
|
def js_automationstrategies(self, url_split, post_string: str, is_json: bool) -> bytes:
|
|
|
|
swap_client = self.server.swap_client
|
|
|
|
swap_client.checkSystemStatus()
|
|
|
|
|
|
|
|
filters = {
|
|
|
|
'page_no': 1,
|
|
|
|
'limit': PAGE_LIMIT,
|
|
|
|
'sort_by': 'created_at',
|
|
|
|
'sort_dir': 'desc',
|
|
|
|
}
|
|
|
|
|
|
|
|
if post_string != '':
|
|
|
|
post_data = getFormData(post_string, is_json)
|
|
|
|
|
|
|
|
if have_data_entry(post_data, 'sort_by'):
|
|
|
|
sort_by = get_data_entry(post_data, 'sort_by')
|
|
|
|
assert (sort_by in ['created_at', 'rate']), 'Invalid sort by'
|
|
|
|
filters['sort_by'] = sort_by
|
|
|
|
if have_data_entry(post_data, 'sort_dir'):
|
|
|
|
sort_dir = get_data_entry(post_data, 'sort_dir')
|
|
|
|
assert (sort_dir in ['asc', 'desc']), 'Invalid sort dir'
|
|
|
|
filters['sort_dir'] = sort_dir
|
|
|
|
|
|
|
|
if have_data_entry(post_data, 'offset'):
|
|
|
|
filters['offset'] = int(get_data_entry(post_data, 'offset'))
|
|
|
|
if have_data_entry(post_data, 'limit'):
|
|
|
|
filters['limit'] = int(get_data_entry(post_data, 'limit'))
|
|
|
|
assert (filters['limit'] > 0 and filters['limit'] <= PAGE_LIMIT), 'Invalid limit'
|
|
|
|
|
|
|
|
if len(url_split) > 3:
|
|
|
|
strat_id = int(url_split[3])
|
|
|
|
strat_data = swap_client.getAutomationStrategy(strat_id)
|
|
|
|
rv = {
|
|
|
|
'record_id': strat_data.record_id,
|
|
|
|
'label': strat_data.label,
|
|
|
|
'type_ind': strat_data.type_ind,
|
|
|
|
'only_known_identities': strat_data.only_known_identities,
|
|
|
|
'num_concurrent': strat_data.num_concurrent,
|
|
|
|
'data': json.loads(strat_data.data.decode('utf-8')),
|
|
|
|
'note': '' if strat_data.note is None else strat_data.note,
|
|
|
|
}
|
|
|
|
return bytes(json.dumps(rv), 'UTF-8')
|
|
|
|
|
|
|
|
rv = []
|
|
|
|
strats = swap_client.listAutomationStrategies(filters)
|
|
|
|
for row in strats:
|
|
|
|
rv.append((row[0], row[1], row[2]))
|
|
|
|
return bytes(json.dumps(rv), 'UTF-8')
|
|
|
|
|
|
|
|
|
|
|
|
def js_validateamount(self, url_split, post_string: str, is_json: bool) -> bytes:
|
|
|
|
swap_client = self.server.swap_client
|
|
|
|
swap_client.checkSystemStatus()
|
|
|
|
|
|
|
|
post_data = getFormData(post_string, is_json)
|
|
|
|
|
|
|
|
ticker_str = post_data['coin']
|
|
|
|
amount = post_data['amount']
|
|
|
|
round_method = post_data.get('method', 'none')
|
|
|
|
|
|
|
|
valid_round_methods = ('roundoff', 'rounddown', 'none')
|
|
|
|
if round_method not in valid_round_methods:
|
|
|
|
raise ValueError(f'Unknown rounding method, must be one of {valid_round_methods}')
|
|
|
|
|
|
|
|
coin_type = tickerToCoinId(ticker_str)
|
|
|
|
ci = swap_client.ci(coin_type)
|
|
|
|
|
|
|
|
r = 0
|
|
|
|
if round_method == 'roundoff':
|
|
|
|
r = 1
|
|
|
|
elif round_method == 'rounddown':
|
|
|
|
r = -1
|
|
|
|
|
|
|
|
output_amount = ci.format_amount(amount, conv_int=True, r=r)
|
|
|
|
return bytes(json.dumps(output_amount), 'UTF-8')
|
|
|
|
|
|
|
|
|
|
|
|
def js_vacuumdb(self, url_split, post_string, is_json) -> bytes:
|
|
|
|
swap_client = self.server.swap_client
|
|
|
|
swap_client.checkSystemStatus()
|
|
|
|
swap_client.vacuumDB()
|
|
|
|
|
|
|
|
return bytes(json.dumps({'completed': True}), 'UTF-8')
|
|
|
|
|
|
|
|
|
|
|
|
def js_getcoinseed(self, url_split, post_string, is_json) -> bytes:
|
|
|
|
swap_client = self.server.swap_client
|
|
|
|
swap_client.checkSystemStatus()
|
|
|
|
post_data = getFormData(post_string, is_json)
|
|
|
|
|
|
|
|
coin = getCoinType(get_data_entry(post_data, 'coin'))
|
|
|
|
if coin in (Coins.PART, Coins.PART_ANON, Coins.PART_BLIND):
|
|
|
|
raise ValueError('Particl wallet seed is set from the Basicswap mnemonic.')
|
|
|
|
|
|
|
|
ci = swap_client.ci(coin)
|
|
|
|
if coin in (Coins.XMR, Coins.WOW):
|
|
|
|
key_view = swap_client.getWalletKey(coin, 1, for_ed25519=True)
|
|
|
|
key_spend = swap_client.getWalletKey(coin, 2, for_ed25519=True)
|
|
|
|
address = ci.getAddressFromKeys(key_view, key_spend)
|
|
|
|
return bytes(json.dumps({'coin': ci.ticker(), 'key_view': ci.encodeKey(key_view), 'key_spend': ci.encodeKey(key_spend), 'address': address}), 'UTF-8')
|
|
|
|
|
|
|
|
seed_key = swap_client.getWalletKey(coin, 1)
|
|
|
|
if coin == Coins.DASH:
|
|
|
|
return bytes(json.dumps({'coin': ci.ticker(), 'seed': seed_key.hex(), 'mnemonic': ci.entropyToMnemonic(seed_key)}), 'UTF-8')
|
|
|
|
seed_id = ci.getSeedHash(seed_key)
|
|
|
|
return bytes(json.dumps({'coin': ci.ticker(), 'seed': seed_key.hex(), 'seed_id': seed_id.hex()}), 'UTF-8')
|
|
|
|
|
|
|
|
|
|
|
|
def js_setpassword(self, url_split, post_string, is_json) -> bytes:
|
|
|
|
# Set or change wallet passwords
|
|
|
|
# Only works with currently enabled coins
|
|
|
|
# Will fail if any coin does not unlock on the old password
|
|
|
|
swap_client = self.server.swap_client
|
|
|
|
post_data = getFormData(post_string, is_json)
|
|
|
|
|
|
|
|
old_password = get_data_entry(post_data, 'oldpassword')
|
|
|
|
new_password = get_data_entry(post_data, 'newpassword')
|
|
|
|
|
|
|
|
if have_data_entry(post_data, 'coin'):
|
|
|
|
# Set password for one coin
|
|
|
|
coin = getCoinType(get_data_entry(post_data, 'coin'))
|
|
|
|
if coin in (Coins.PART_ANON, Coins.PART_BLIND, Coins.LTC_MWEB):
|
|
|
|
raise ValueError('Invalid coin.')
|
|
|
|
swap_client.changeWalletPasswords(old_password, new_password, coin)
|
|
|
|
return bytes(json.dumps({'success': True}), 'UTF-8')
|
|
|
|
|
|
|
|
# Set password for all coins
|
|
|
|
swap_client.changeWalletPasswords(old_password, new_password)
|
|
|
|
return bytes(json.dumps({'success': True}), 'UTF-8')
|
|
|
|
|
|
|
|
|
|
|
|
def js_unlock(self, url_split, post_string, is_json) -> bytes:
|
|
|
|
swap_client = self.server.swap_client
|
|
|
|
post_data = getFormData(post_string, is_json)
|
|
|
|
|
|
|
|
password = get_data_entry(post_data, 'password')
|
|
|
|
|
|
|
|
if have_data_entry(post_data, 'coin'):
|
|
|
|
coin = getCoinType(str(get_data_entry(post_data, 'coin')))
|
|
|
|
if coin in (Coins.PART_ANON, Coins.PART_BLIND):
|
|
|
|
raise ValueError('Invalid coin.')
|
|
|
|
swap_client.unlockWallets(password, coin)
|
|
|
|
return bytes(json.dumps({'success': True}), 'UTF-8')
|
|
|
|
|
|
|
|
swap_client.unlockWallets(password)
|
|
|
|
return bytes(json.dumps({'success': True}), 'UTF-8')
|
|
|
|
|
|
|
|
|
|
|
|
def js_lock(self, url_split, post_string, is_json) -> bytes:
|
|
|
|
swap_client = self.server.swap_client
|
|
|
|
post_data = {} if post_string == '' else getFormData(post_string, is_json)
|
|
|
|
|
|
|
|
if have_data_entry(post_data, 'coin'):
|
|
|
|
coin = getCoinType(get_data_entry(post_data, 'coin'))
|
|
|
|
if coin in (Coins.PART_ANON, Coins.PART_BLIND):
|
|
|
|
raise ValueError('Invalid coin.')
|
|
|
|
swap_client.lockWallets(coin)
|
|
|
|
return bytes(json.dumps({'success': True}), 'UTF-8')
|
|
|
|
|
|
|
|
swap_client.lockWallets()
|
|
|
|
return bytes(json.dumps({'success': True}), 'UTF-8')
|
|
|
|
|
|
|
|
|
|
|
|
def js_404(self, url_split, post_string, is_json) -> bytes:
|
|
|
|
return bytes(json.dumps({'Error': 'path unknown'}), 'UTF-8')
|
|
|
|
|
|
|
|
|
|
|
|
def js_help(self, url_split, post_string, is_json) -> bytes:
|
|
|
|
# TODO: Add details and examples
|
|
|
|
commands = []
|
|
|
|
for k in pages:
|
|
|
|
commands.append(k)
|
|
|
|
return bytes(json.dumps({'commands': commands}), 'UTF-8')
|
|
|
|
|
|
|
|
|
|
|
|
pages = {
|
|
|
|
'coins': js_coins,
|
|
|
|
'wallets': js_wallets,
|
|
|
|
'offers': js_offers,
|
|
|
|
'sentoffers': js_sentoffers,
|
|
|
|
'bids': js_bids,
|
|
|
|
'sentbids': js_sentbids,
|
|
|
|
'network': js_network,
|
|
|
|
'revokeoffer': js_revokeoffer,
|
|
|
|
'smsgaddresses': js_smsgaddresses,
|
|
|
|
'rate': js_rate,
|
|
|
|
'rates': js_rates,
|
|
|
|
'rateslist': js_rates_list,
|
|
|
|
'generatenotification': js_generatenotification,
|
|
|
|
'notifications': js_notifications,
|
|
|
|
'identities': js_identities,
|
|
|
|
'automationstrategies': js_automationstrategies,
|
|
|
|
'validateamount': js_validateamount,
|
|
|
|
'vacuumdb': js_vacuumdb,
|
|
|
|
'getcoinseed': js_getcoinseed,
|
|
|
|
'setpassword': js_setpassword,
|
|
|
|
'unlock': js_unlock,
|
|
|
|
'lock': js_lock,
|
|
|
|
'help': js_help,
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
def js_url_to_function(url_split):
|
|
|
|
if len(url_split) > 2:
|
|
|
|
return pages.get(url_split[2], js_404)
|
|
|
|
return js_index
|