From 4405a130f5008e45a1816b2e97a8b1096830cec4 Mon Sep 17 00:00:00 2001 From: tecnovert Date: Mon, 5 Aug 2019 20:31:02 +0200 Subject: [PATCH] Started stutdown and explorer pages. Bid state can be manually edited. --- basicswap/basicswap.py | 216 ++++++++++++++++++----------- basicswap/db.py | 11 ++ basicswap/explorers.py | 55 ++++++-- basicswap/http_server.py | 118 +++++++++++++++- basicswap/templates/bid.html | 21 +++ basicswap/templates/explorers.html | 33 +++++ basicswap/templates/index.html | 5 +- basicswap/templates/rpc.html | 2 +- bin/basicswap_prepare.py | 2 + 9 files changed, 366 insertions(+), 97 deletions(-) create mode 100644 basicswap/templates/explorers.html diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index 21b1a81..c517474 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -368,22 +368,25 @@ class BasicSwap(): if self.chain == 'mainnet': self.coin_clients[Coins.PART]['explorers'].append(ExplorerInsight( - self, - 'https://explorer.particl.io/particl-insight-api/')) + self, Coins.PART, + 'https://explorer.particl.io/particl-insight-api')) self.coin_clients[Coins.LTC]['explorers'].append(ExplorerBitAps( - self, + self, Coins.LTC, 'https://api.bitaps.com/ltc/v1/blockchain')) self.coin_clients[Coins.LTC]['explorers'].append(ExplorerChainz( - self, + self, Coins.LTC, 'http://chainz.cryptoid.info/ltc/api.dws')) elif self.chain == 'testnet': self.coin_clients[Coins.PART]['explorers'].append(ExplorerInsight( - self, + self, Coins.PART, 'https://explorer-testnet.particl.io/particl-insight-api')) self.coin_clients[Coins.LTC]['explorers'].append(ExplorerBitAps( - self, + self, Coins.LTC, 'https://api.bitaps.com/ltc/testnet/v1/blockchain')) + # non-segwit + # https://testnet.litecore.io/insight-api + def prepareLogging(self): self.log = logging.getLogger(self.log_name) self.log.propagate = False @@ -593,6 +596,55 @@ class BasicSwap(): session.close() session.remove() + def activateBid(self, session, bid): + if bid.bid_id in self.swaps_in_progress: + self.log.debug('Bid %s is already in progress', bid.bid_id.hex()) + + self.log.debug('Loading active bid %s', bid.bid_id.hex()) + + offer = session.query(Offer).filter_by(offer_id=bid.offer_id).first() + assert(offer), 'Offer not found' + + bid.initiate_tx = session.query(SwapTx).filter(sa.and_(SwapTx.bid_id == bid.bid_id, SwapTx.tx_type == TxTypes.ITX)).first() + bid.participate_tx = session.query(SwapTx).filter(sa.and_(SwapTx.bid_id == bid.bid_id, SwapTx.tx_type == TxTypes.PTX)).first() + + self.swaps_in_progress[bid.bid_id] = (bid, offer) + + coin_from = Coins(offer.coin_from) + coin_to = Coins(offer.coin_to) + if bid.initiate_tx and bid.initiate_tx.txid: + self.addWatchedOutput(coin_from, bid.bid_id, bid.initiate_tx.txid.hex(), bid.initiate_tx.vout, BidStates.SWAP_INITIATED) + if bid.participate_tx and bid.participate_tx.txid: + self.addWatchedOutput(coin_to, bid.bid_id, bid.participate_tx.txid.hex(), bid.participate_tx.vout, BidStates.SWAP_PARTICIPATING) + + if self.coin_clients[coin_from]['last_height_checked'] < 1: + if bid.initiate_tx and bid.initiate_tx.chain_height: + self.coin_clients[coin_from]['last_height_checked'] = bid.initiate_tx.chain_height + if self.coin_clients[coin_to]['last_height_checked'] < 1: + if bid.participate_tx and bid.participate_tx.chain_height: + self.coin_clients[coin_to]['last_height_checked'] = bid.participate_tx.chain_height + + # TODO process addresspool if bid has previously been abandoned + + def deactivateBid(self, offer, bid): + # Remove from in progress + self.swaps_in_progress.pop(bid.bid_id, None) + + # Remove any watched outputs + self.removeWatchedOutput(Coins(offer.coin_from), bid.bid_id, None) + self.removeWatchedOutput(Coins(offer.coin_to), bid.bid_id, None) + + if bid.state == BidStates.BID_ABANDONED or bid.state == BidStates.SWAP_COMPLETED: + # Return unused addrs to pool + if bid.getITxState() != TxStates.TX_REDEEMED: + self.returnAddressToPool(bid_id, TxTypes.ITX_REDEEM) + if bid.getITxState() != TxStates.TX_REFUNDED: + self.returnAddressToPool(bid_id, TxTypes.ITX_REFUND) + if bid.getPTxState() != TxStates.TX_REDEEMED: + self.returnAddressToPool(bid_id, TxTypes.PTX_REDEEM) + if bid.getPTxState() != TxStates.TX_REFUNDED: + self.returnAddressToPool(bid_id, TxTypes.PTX_REFUND) + def loadFromDB(self): self.log.info('Loading data from db') self.mxDB.acquire() @@ -600,29 +652,7 @@ class BasicSwap(): session = scoped_session(self.session_factory) for bid in session.query(Bid): if bid.state and bid.state > BidStates.BID_RECEIVED and bid.state < BidStates.SWAP_COMPLETED: - self.log.debug('Loading active bid %s', bid.bid_id.hex()) - - offer = session.query(Offer).filter_by(offer_id=bid.offer_id).first() - assert(offer), 'Offer not found' - - bid.initiate_tx = session.query(SwapTx).filter(sa.and_(SwapTx.bid_id == bid.bid_id, SwapTx.tx_type == TxTypes.ITX)).first() - bid.participate_tx = session.query(SwapTx).filter(sa.and_(SwapTx.bid_id == bid.bid_id, SwapTx.tx_type == TxTypes.PTX)).first() - - self.swaps_in_progress[bid.bid_id] = (bid, offer) - - coin_from = Coins(offer.coin_from) - coin_to = Coins(offer.coin_to) - if bid.initiate_tx and bid.initiate_tx.txid: - self.addWatchedOutput(coin_from, bid.bid_id, bid.initiate_tx.txid.hex(), bid.initiate_tx.vout, BidStates.SWAP_INITIATED) - if bid.participate_tx and bid.participate_tx.txid: - self.addWatchedOutput(coin_to, bid.bid_id, bid.participate_tx.txid.hex(), bid.participate_tx.vout, BidStates.SWAP_PARTICIPATING) - - if self.coin_clients[coin_from]['last_height_checked'] < 1: - if bid.initiate_tx and bid.initiate_tx.chain_height: - self.coin_clients[coin_from]['last_height_checked'] = bid.initiate_tx.chain_height - if self.coin_clients[coin_to]['last_height_checked'] < 1: - if bid.participate_tx and bid.participate_tx.chain_height: - self.coin_clients[coin_to]['last_height_checked'] = bid.participate_tx.chain_height + self.activateBid(session, bid) finally: session.close() @@ -1139,44 +1169,49 @@ class BasicSwap(): pubkey_refund = self.getContractPubkey(bid_date, bid.contract_count) pkhash_refund = getKeyID(pubkey_refund) - if offer.lock_type < ABS_LOCK_BLOCKS: - sequence = getExpectedSequence(offer.lock_type, offer.lock_value, coin_from) - script = buildContractScript(sequence, secret_hash, bid.pkhash_buyer, pkhash_refund) + if bid.initiate_tx is not None: + self.log.warning('initiate txn %s already exists for bid %s', bid.initiate_tx.txid, bid_id.hex()) + txid = bid.initiate_tx.txid + script = bid.initiate_tx.script else: - if offer.lock_type == ABS_LOCK_BLOCKS: - lock_value = self.callcoinrpc(coin_from, 'getblockchaininfo')['blocks'] + offer.lock_value + if offer.lock_type < ABS_LOCK_BLOCKS: + sequence = getExpectedSequence(offer.lock_type, offer.lock_value, coin_from) + script = buildContractScript(sequence, secret_hash, bid.pkhash_buyer, pkhash_refund) else: - lock_value = int(time.time()) + offer.lock_value - self.log.debug('initiate %s lock_value %d %d', coin_from, offer.lock_value, lock_value) - script = buildContractScript(lock_value, secret_hash, bid.pkhash_buyer, pkhash_refund, OpCodes.OP_CHECKLOCKTIMEVERIFY) + if offer.lock_type == ABS_LOCK_BLOCKS: + lock_value = self.callcoinrpc(coin_from, 'getblockchaininfo')['blocks'] + offer.lock_value + else: + lock_value = int(time.time()) + offer.lock_value + self.log.debug('initiate %s lock_value %d %d', coin_from, offer.lock_value, lock_value) + script = buildContractScript(lock_value, secret_hash, bid.pkhash_buyer, pkhash_refund, OpCodes.OP_CHECKLOCKTIMEVERIFY) - p2sh = self.callcoinrpc(Coins.PART, 'decodescript', [script.hex()])['p2sh'] + p2sh = self.callcoinrpc(Coins.PART, 'decodescript', [script.hex()])['p2sh'] - bid.pkhash_seller = pkhash_refund + bid.pkhash_seller = pkhash_refund - txn = self.createInitiateTxn(coin_from, bid_id, bid, script) + txn = self.createInitiateTxn(coin_from, bid_id, bid, script) - # Store the signed refund txn in case wallet is locked when refund is possible - refund_txn = self.createRefundTxn(coin_from, txn, offer, bid, script) - bid.initiate_txn_refund = bytes.fromhex(refund_txn) + # Store the signed refund txn in case wallet is locked when refund is possible + refund_txn = self.createRefundTxn(coin_from, txn, offer, bid, script) + bid.initiate_txn_refund = bytes.fromhex(refund_txn) - txid = self.submitTxn(coin_from, txn) - self.log.debug('Submitted initiate txn %s to %s chain for bid %s', txid, chainparams[coin_from]['name'], bid_id.hex()) - bid.initiate_tx = SwapTx( - bid_id=bid_id, - tx_type=TxTypes.ITX, - txid=bytes.fromhex(txid), - script=script, - ) - bid.setITxState(TxStates.TX_SENT) + txid = self.submitTxn(coin_from, txn) + self.log.debug('Submitted initiate txn %s to %s chain for bid %s', txid, chainparams[coin_from]['name'], bid_id.hex()) + bid.initiate_tx = SwapTx( + bid_id=bid_id, + tx_type=TxTypes.ITX, + txid=bytes.fromhex(txid), + script=script, + ) + bid.setITxState(TxStates.TX_SENT) - # Check non-bip68 final - try: - txid = self.submitTxn(coin_from, bid.initiate_txn_refund.hex()) - self.log.error('Submit refund_txn unexpectedly worked: ' + txid) - except Exception as ex: - if 'non-BIP68-final' not in str(ex) and 'non-final' not in str(ex): - self.log.error('Submit refund_txn unexpected error' + str(ex)) + # Check non-bip68 final + try: + txid = self.submitTxn(coin_from, bid.initiate_txn_refund.hex()) + self.log.error('Submit refund_txn unexpectedly worked: ' + txid) + except Exception as ex: + if 'non-BIP68-final' not in str(ex) and 'non-final' not in str(ex): + self.log.error('Submit refund_txn unexpected error' + str(ex)) if txid is not None: msg_buf = BidAcceptMessage() @@ -1234,22 +1269,7 @@ class BasicSwap(): bid.setState(BidStates.BID_ABANDONED) session.commit() - # Remove from in progress - self.swaps_in_progress.pop(bid_id, None) - - # Remove any watched outputs - self.removeWatchedOutput(Coins(offer.coin_from), bid_id, None) - self.removeWatchedOutput(Coins(offer.coin_to), bid_id, None) - - # Return unused addrs to pool - if bid.getITxState() != TxStates.TX_REDEEMED: - self.returnAddressToPool(bid_id, TxTypes.ITX_REDEEM) - if bid.getITxState() != TxStates.TX_REFUNDED: - self.returnAddressToPool(bid_id, TxTypes.ITX_REFUND) - if bid.getPTxState() != TxStates.TX_REDEEMED: - self.returnAddressToPool(bid_id, TxTypes.PTX_REDEEM) - if bid.getPTxState() != TxStates.TX_REFUNDED: - self.returnAddressToPool(bid_id, TxTypes.PTX_REFUND) + self.deactivateBid(offer, bid) finally: session.close() session.remove() @@ -1612,13 +1632,16 @@ class BasicSwap(): # Seller first mode, buyer participates participate_script = self.deriveParticipateScript(bid_id, bid, offer) if bid.was_sent: - self.log.debug('Preparing participate txn for bid %s', bid_id.hex()) + if bid.participate_tx is not None: + self.log.warning('Participate txn %s already exists for bid %s', bid.participate_tx.txid, bid_id.hex()) + else: + self.log.debug('Preparing participate txn for bid %s', bid_id.hex()) - coin_to = Coins(offer.coin_to) - txn = self.createParticipateTxn(bid_id, bid, offer, participate_script) - txid = self.submitTxn(coin_to, txn) - self.log.debug('Submitted participate txn %s to %s chain for bid %s', txid, chainparams[coin_to]['name'], bid_id.hex()) - bid.setPTxState(TxStates.TX_SENT) + coin_to = Coins(offer.coin_to) + txn = self.createParticipateTxn(bid_id, bid, offer, participate_script) + txid = self.submitTxn(coin_to, txn) + self.log.debug('Submitted participate txn %s to %s chain for bid %s', txid, chainparams[coin_to]['name'], bid_id.hex()) + bid.setPTxState(TxStates.TX_SENT) else: bid.participate_tx = SwapTx( bid_id=bid_id, @@ -2326,6 +2349,37 @@ class BasicSwap(): finally: self.mxDB.release() + def manualBidUpdate(self, bid_id, data): + self.log.info('Manually updating bid %s', bid_id.hex()) + self.mxDB.acquire() + try: + bid, offer = self.getBidAndOffer(bid_id) + assert(bid), 'Bid not found {}'.format(bid_id.hex()) + assert(offer), 'Offer not found {}'.format(bid.offer_id.hex()) + + has_changed = False + if bid.state != data['bid_state']: + bid.setState(data['bid_state']) + self.log.debug('Set state to %s', strBidState(bid.state)) + has_changed = True + + if has_changed: + session = scoped_session(self.session_factory) + try: + self.saveBidInSession(session, bid) + session.commit() + if bid.state and bid.state > BidStates.BID_RECEIVED and bid.state < BidStates.SWAP_COMPLETED: + self.activateBid(session, bid) + else: + self.deactivateBid(offer, bid) + finally: + session.close() + session.remove() + else: + raise ValueError('No changes') + finally: + self.mxDB.release() + def getSummary(self, opts=None): num_watched_outputs = 0 for c, v in self.coin_clients.items(): diff --git a/basicswap/db.py b/basicswap/db.py index a5d7d0c..6f29519 100644 --- a/basicswap/db.py +++ b/basicswap/db.py @@ -180,3 +180,14 @@ class SmsgAddress(Base): addr_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) addr = sa.Column(sa.String) use_type = sa.Column(sa.Integer) + + +# TODO: Delay responding to automated events +class EventQueue(Base): + __tablename__ = 'eventqueue' + addr_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True) + created_at = sa.Column(sa.BigInteger) + trigger_at = sa.Column(sa.BigInteger) + linked_id = sa.Column(sa.LargeBinary) + event_type = sa.Column(sa.Integer) + diff --git a/basicswap/explorers.py b/basicswap/explorers.py index 6039799..df53f69 100644 --- a/basicswap/explorers.py +++ b/basicswap/explorers.py @@ -9,33 +9,72 @@ import json class Explorer(): - def __init__(self, swapclient, base_url): + def __init__(self, swapclient, coin_type, base_url): self.swapclient = swapclient + self.coin_type = coin_type self.base_url = base_url self.log = self.swapclient.log + self.coin_settings = self.swapclient.coin_clients[self.coin_type] + + def readURL(self, url): + self.log.debug('Explorer url: {}'.format(url)) + headers = {'User-Agent': 'Mozilla/5.0'} + req = urllib.request.Request(url, headers=headers) + return urllib.request.urlopen(req).read() class ExplorerInsight(Explorer): def getChainHeight(self): - return json.loads(urllib.request.urlopen(self.base_url + '/sync').read())['blockChainHeight'] + return json.loads(self.readURL(self.base_url + '/sync'))['blockChainHeight'] + + def getBlock(self, block_hash): + data = json.loads(self.readURL(self.base_url + '/block/{}'.format(block_hash))) + return data + + def getTransaction(self, txid): + data = json.loads(self.readURL(self.base_url + '/tx/{}'.format(txid))) + return data + + def getBalance(self, address): + data = json.loads(self.readURL(self.base_url + '/addr/{}/balance'.format(address))) + return data def lookupUnspentByAddress(self, address): - chain_height = self.getChainHeight() - self.log.debug('[rm] chain_height %d', chain_height) + data = json.loads(self.readURL(self.base_url + '/addr/{}/utxo'.format(address))) + rv = [] + for utxo in data: + rv.append({ + 'txid': utxo['txid'], + 'index': utxo['vout'], + 'height': utxo['height'], + 'n_conf': utxo['confirmations'], + }) + return rv class ExplorerBitAps(Explorer): def getChainHeight(self): - return json.loads(urllib.request.urlopen(self.base_url + '/block/last').read())['data']['block']['height'] + return json.loads(self.readURL(self.base_url + '/block/last'))['data']['block']['height'] + + def getBlock(self, block_hash): + data = json.loads(self.readURL(self.base_url + '/block/{}'.format(block_hash))) + return data + + def getTransaction(self, txid): + data = json.loads(self.readURL(self.base_url + '/transaction/{}'.format(txid))) + return data + + def getBalance(self, address): + data = json.loads(self.readURL(self.base_url + '/address/state/' + address)) + return data def lookupUnspentByAddress(self, address): - chain_height = self.getChainHeight() - self.log.debug('[rm] chain_height %d', chain_height) + return json.loads(self.readURL(self.base_url + '/address/transactions/' + address)) class ExplorerChainz(Explorer): def getChainHeight(self): - return int(urllib.request.urlopen(self.base_url + '?q=getblockcount').read()) + return int(self.readURL(self.base_url + '?q=getblockcount')) def lookupUnspentByAddress(self, address): chain_height = self.getChainHeight() diff --git a/basicswap/http_server.py b/basicswap/http_server.py index 4f9ab8d..0a0a0a3 100644 --- a/basicswap/http_server.py +++ b/basicswap/http_server.py @@ -20,6 +20,7 @@ from .util import ( COIN, format8, makeInt, + dumpj, ) from .chainparams import ( chainparams, @@ -60,6 +61,34 @@ def listAvailableCoins(swap_client): return coins +def extractDomain(url): + return url.split('://', 1)[1].split('/', 1)[0] + + +def listAvailableExplorers(swap_client): + explorers = [] + for c in Coins: + for i, e in enumerate(swap_client.coin_clients[c]['explorers']): + explorers.append(('{}_{}'.format(int(c), i), swap_client.coin_clients[c]['name'].capitalize() + ' - ' + extractDomain(e.base_url))) + return explorers + + +def listExplorerActions(swap_client): + actions = [('height', 'Chain Height'), + ('block', 'Get Block'), + ('tx', 'Get Transaction'), + ('balance', 'Address Balance'), + ('unspent', 'List Unspent')] + return actions + + +def listBidStates(): + rv = [] + for s in BidStates: + rv.append((int(s), strBidState(s))) + return rv + + def getTxIdHex(bid, tx_type, prefix): if tx_type == TxTypes.ITX: obj = bid.initiate_tx @@ -110,6 +139,12 @@ def html_content_start(title, h2=None, refresh=None): class HttpHandler(BaseHTTPRequestHandler): + def page_info(self, info_str): + content = html_content_start('BasicSwap Info') \ + + '

Info: ' + info_str + '

' \ + + '

home

' + return bytes(content, 'UTF-8') + def page_error(self, error_str): content = html_content_start('BasicSwap Error') \ + '

Error: ' + error_str + '

' \ @@ -156,6 +191,50 @@ class HttpHandler(BaseHTTPRequestHandler): def js_index(self, url_split): return bytes(json.dumps(self.server.swap_client.getSummary()), 'UTF-8') + def page_explorers(self, url_split, post_string): + swap_client = self.server.swap_client + + result = None + explorer = -1 + action = -1 + messages = [] + form_data = self.checkForm(post_string, 'explorers', messages) + if form_data: + + explorer = form_data[b'explorer'][0].decode('utf-8') + action = form_data[b'action'][0].decode('utf-8') + + args = '' if not b'args' in form_data else form_data[b'args'][0].decode('utf-8') + try: + c, e = explorer.split('_') + exp = swap_client.coin_clients[Coins(int(c))]['explorers'][int(e)] + if action == 'height': + result = str(exp.getChainHeight()) + elif action == 'block': + result = dumpj(exp.getBlock(args)) + elif action == 'tx': + result = dumpj(exp.getTransaction(args)) + elif action == 'balance': + result = dumpj(exp.getBalance(args)) + elif action == 'unspent': + result = dumpj(exp.lookupUnspentByAddress(args)) + else: + result = 'Unknown action' + except Exception as ex: + result = str(ex) + + template = env.get_template('explorers.html') + return bytes(template.render( + title=self.server.title, + h2=self.server.title, + explorers=listAvailableExplorers(swap_client), + explorer=explorer, + actions=listExplorerActions(swap_client), + action=action, + result=result, + form_id=os.urandom(8).hex(), + ), 'UTF-8') + def page_rpc(self, url_split, post_string): swap_client = self.server.swap_client @@ -400,7 +479,7 @@ class HttpHandler(BaseHTTPRequestHandler): sort_by = form_data[b'sort_by'][0].decode('utf-8') assert(sort_by in ['created_at', 'rate']), 'Invalid sort by' filters['sort_by'] = sort_by - if b'sort_dir' in form_data: + elif b'sort_dir' in form_data: sort_dir = form_data[b'sort_dir'][0].decode('utf-8') assert(sort_dir in ['asc', 'desc']), 'Invalid sort dir' filters['sort_dir'] = sort_dir @@ -409,7 +488,7 @@ class HttpHandler(BaseHTTPRequestHandler): filters['page_no'] = int(form_data[b'pageno'][0]) - 1 if filters['page_no'] < 1: filters['page_no'] = 1 - if form_data and b'pageforwards' in form_data: + elif form_data and b'pageforwards' in form_data: filters['page_no'] = int(form_data[b'pageno'][0]) + 1 if filters['page_no'] > 1: @@ -457,6 +536,7 @@ class HttpHandler(BaseHTTPRequestHandler): messages = [] show_txns = False + edit_bid = False form_data = self.checkForm(post_string, 'bid', messages) if form_data: if b'abandon_bid' in form_data: @@ -464,15 +544,26 @@ class HttpHandler(BaseHTTPRequestHandler): swap_client.abandonBid(bid_id) messages.append('Bid abandoned') except Exception as ex: - messages.append('Error ' + str(ex)) - if b'accept_bid' in form_data: + messages.append('Abandon failed ' + str(ex)) + elif b'accept_bid' in form_data: try: swap_client.acceptBid(bid_id) messages.append('Bid accepted') except Exception as ex: - messages.append('Error ' + str(ex)) - if b'show_txns' in form_data: + messages.append('Accept failed ' + str(ex)) + elif b'show_txns' in form_data: show_txns = True + elif b'edit_bid' in form_data: + edit_bid = True + elif b'edit_bid_submit' in form_data: + data = { + 'bid_state': int(form_data[b'new_state'][0]) + } + try: + swap_client.manualBidUpdate(bid_id, data) + messages.append('Bid edited') + except Exception as ex: + messages.append('Edit failed ' + str(ex)) bid, offer = swap_client.getBidAndOffer(bid_id) assert(bid), 'Unknown bid ID' @@ -533,6 +624,10 @@ class HttpHandler(BaseHTTPRequestHandler): 'show_txns': show_txns, } + if edit_bid: + data['bid_state_ind'] = int(bid.state) + data['bid_states'] = listBidStates() + if show_txns: data['initiate_tx_refund'] = 'None' if not bid.initiate_txn_refund else bid.initiate_txn_refund.hex() data['participate_tx_refund'] = 'None' if not bid.participate_txn_refund else bid.participate_txn_refund.hex() @@ -564,6 +659,7 @@ class HttpHandler(BaseHTTPRequestHandler): bid_id=bid_id.hex(), messages=messages, data=data, + edit_bid=edit_bid, form_id=os.urandom(8).hex(), old_states=old_states, ), 'UTF-8') @@ -594,6 +690,12 @@ class HttpHandler(BaseHTTPRequestHandler): watched_outputs=[(wo[1].hex(), getCoinName(wo[0]), wo[2], wo[3], int(wo[4])) for wo in watched_outputs], ), 'UTF-8') + def page_shutdown(self, url_split, post_string): + swap_client = self.server.swap_client + swap_client.stopRunning() + + return self.page_info('Shutting down') + def page_index(self, url_split): swap_client = self.server.swap_client summary = swap_client.getSummary() @@ -639,6 +741,8 @@ class HttpHandler(BaseHTTPRequestHandler): return self.page_wallets(url_split, post_string) if url_split[1] == 'rpc': return self.page_rpc(url_split, post_string) + if url_split[1] == 'explorers': + return self.page_explorers(url_split, post_string) if url_split[1] == 'offer': return self.page_offer(url_split, post_string) if url_split[1] == 'offers': @@ -657,6 +761,8 @@ class HttpHandler(BaseHTTPRequestHandler): return self.page_bids(url_split, post_string, sent=True) if url_split[1] == 'watched': return self.page_watched(url_split, post_string) + if url_split[1] == 'shutdown': + return self.page_shutdown(url_split, post_string) return self.page_index(url_split) except Exception as ex: traceback.print_exc() # TODO: Remove diff --git a/basicswap/templates/bid.html b/basicswap/templates/bid.html index 3dca527..ab6ac61 100644 --- a/basicswap/templates/bid.html +++ b/basicswap/templates/bid.html @@ -34,12 +34,33 @@ {% endif %} + +
+{% if edit_bid %} +

Edit Bid

+ + +
Change Bid State +
+ + +{% else %} {% if data.was_received == 'True' %}
{% endif %} +{% if data.show_txns %} + +{% else %} +{% endif %} + +{% endif %}
diff --git a/basicswap/templates/explorers.html b/basicswap/templates/explorers.html new file mode 100644 index 0000000..1ffbe14 --- /dev/null +++ b/basicswap/templates/explorers.html @@ -0,0 +1,33 @@ +{% include 'header.html' %} + +

Explorers

+{% for m in messages %} +

{{ m }}

+{% endfor %} + +
+

+
+
+
+ + +

+
+ +{% if result %} + +{% endif %} + +

home

+ \ No newline at end of file diff --git a/basicswap/templates/index.html b/basicswap/templates/index.html index 6b0ee92..57890b1 100644 --- a/basicswap/templates/index.html +++ b/basicswap/templates/index.html @@ -9,6 +9,7 @@ Version: {{ version }}

View Wallets
RPC Console
+Explorers

Swaps in progress: {{ summary.num_swapping }}
Network Offers: {{ summary.num_network_offers }}
@@ -18,7 +19,9 @@ Version: {{ version }} Watched Outputs: {{ summary.num_watched_outputs }}

-

New Offer

+

New Offer

+ +

Shutdown

diff --git a/basicswap/templates/rpc.html b/basicswap/templates/rpc.html index bc4e240..d1c41db 100644 --- a/basicswap/templates/rpc.html +++ b/basicswap/templates/rpc.html @@ -1,6 +1,6 @@ {% include 'header.html' %} -

New Offer

+

RPC Console

{% for m in messages %}

{{ m }}

{% endfor %} diff --git a/bin/basicswap_prepare.py b/bin/basicswap_prepare.py index 3c4de09..54010bb 100644 --- a/bin/basicswap_prepare.py +++ b/bin/basicswap_prepare.py @@ -485,6 +485,8 @@ def main(): 'network_key': '7sW2UEcHXvuqEjkpE5mD584zRaQYs6WXYohue4jLFZPTvMSxwvgs', 'network_pubkey': '035758c4a22d7dd59165db02a56156e790224361eb3191f02197addcb3bde903d2', 'chainclients': withchainclients, + 'auto_reply_delay_min': 5, # Min delay before sending a response message + 'auto_reply_delay_max': 50, # Max delay before sending a response message 'check_progress_seconds': 60, 'check_watched_seconds': 60, 'check_expired_seconds': 60