mirror of
https://github.com/basicswap/basicswap.git
synced 2024-12-23 03:49:25 +00:00
Dynamic fee selection.
Display xmr offer fees. Display bid events. html create offer uses correct coin amount scales.
This commit is contained in:
parent
2346858145
commit
3c4c2c528f
7 changed files with 93 additions and 23 deletions
|
@ -183,7 +183,7 @@ class EventTypes(IntEnum):
|
||||||
|
|
||||||
|
|
||||||
class EventLogTypes(IntEnum):
|
class EventLogTypes(IntEnum):
|
||||||
FAILED_TX_B_PUBLISH = auto()
|
FAILED_TX_B_LOCK_PUBLISH = auto()
|
||||||
LOCK_TX_A_PUBLISHED = auto()
|
LOCK_TX_A_PUBLISHED = auto()
|
||||||
LOCK_TX_B_PUBLISHED = auto()
|
LOCK_TX_B_PUBLISHED = auto()
|
||||||
FAILED_TX_B_SPEND = auto()
|
FAILED_TX_B_SPEND = auto()
|
||||||
|
@ -308,6 +308,19 @@ def getLockName(lock_type):
|
||||||
return 'time'
|
return 'time'
|
||||||
|
|
||||||
|
|
||||||
|
def describeEventEntry(event_type, event_msg):
|
||||||
|
if event_type == EventLogTypes.FAILED_TX_B_LOCK_PUBLISH:
|
||||||
|
return 'Failed to publish lock tx b'
|
||||||
|
if event_type == EventLogTypes.FAILED_TX_B_LOCK_PUBLISH:
|
||||||
|
return 'Failed to publish lock tx b'
|
||||||
|
if event_type == EventLogTypes.LOCK_TX_A_PUBLISHED:
|
||||||
|
return 'Lock tx a published'
|
||||||
|
if event_type == EventLogTypes.LOCK_TX_B_PUBLISHED:
|
||||||
|
return 'Lock tx b published'
|
||||||
|
if event_type == EventLogTypes.FAILED_TX_B_SPEND:
|
||||||
|
return 'Failed to publish lock tx b spend'
|
||||||
|
|
||||||
|
|
||||||
def getExpectedSequence(lockType, lockVal, coin_type):
|
def getExpectedSequence(lockType, lockVal, coin_type):
|
||||||
assert(lockVal >= 1), 'Bad lockVal'
|
assert(lockVal >= 1), 'Bad lockVal'
|
||||||
if lockType == SEQUENCE_LOCK_BLOCKS:
|
if lockType == SEQUENCE_LOCK_BLOCKS:
|
||||||
|
@ -995,8 +1008,13 @@ class BasicSwap(BaseApp):
|
||||||
# Delay before the follower can spend from the chain a lock refund tx
|
# Delay before the follower can spend from the chain a lock refund tx
|
||||||
xmr_offer.lock_time_2 = getExpectedSequence(lock_type, lock_value, coin_from)
|
xmr_offer.lock_time_2 = getExpectedSequence(lock_type, lock_value, coin_from)
|
||||||
|
|
||||||
# TODO: Dynamic fee selection
|
# TODO: max fee warning?
|
||||||
xmr_offer.a_fee_rate = make_int(0.00032595, self.ci(coin_from).exp())
|
chain_client_settings = self.getChainClientSettings(coin_from)
|
||||||
|
lock_tx_fee_premium = chain_client_settings.get('lock_tx_fee_premium', 0.0)
|
||||||
|
|
||||||
|
xmr_offer.a_fee_rate = make_int(self.getFeeRateForCoin(coin_from) + lock_tx_fee_premium, self.ci(coin_from).exp())
|
||||||
|
|
||||||
|
# Unused: TODO - Set priority?
|
||||||
# xmr_offer.b_fee_rate = make_int(0.0012595, self.ci(coin_to).exp())
|
# xmr_offer.b_fee_rate = make_int(0.0012595, self.ci(coin_to).exp())
|
||||||
xmr_offer.b_fee_rate = make_int(0.00002, self.ci(coin_to).exp()) # abs fee
|
xmr_offer.b_fee_rate = make_int(0.00002, self.ci(coin_to).exp()) # abs fee
|
||||||
|
|
||||||
|
@ -1210,9 +1228,9 @@ class BasicSwap(BaseApp):
|
||||||
return self.callcoinrpc(coin_type, 'getnetworkinfo')['relayfee']
|
return self.callcoinrpc(coin_type, 'getnetworkinfo')['relayfee']
|
||||||
|
|
||||||
def getFeeRateForCoin(self, coin_type):
|
def getFeeRateForCoin(self, coin_type):
|
||||||
# TODO: Per coin settings to override feerate
|
|
||||||
override_feerate = self.coin_clients[coin_type].get('override_feerate', None)
|
override_feerate = self.coin_clients[coin_type].get('override_feerate', None)
|
||||||
if override_feerate:
|
if override_feerate:
|
||||||
|
self.log.debug('Fee rate override used for %s: %f', str(coin_type), override_feerate)
|
||||||
return override_feerate
|
return override_feerate
|
||||||
|
|
||||||
return self.ci(coin_type).get_fee_rate()
|
return self.ci(coin_type).get_fee_rate()
|
||||||
|
@ -1559,6 +1577,23 @@ class BasicSwap(BaseApp):
|
||||||
session.remove()
|
session.remove()
|
||||||
self.mxDB.release()
|
self.mxDB.release()
|
||||||
|
|
||||||
|
def list_bid_events(self, bid_id):
|
||||||
|
self.mxDB.acquire()
|
||||||
|
events = []
|
||||||
|
try:
|
||||||
|
session = scoped_session(self.session_factory)
|
||||||
|
query_str = 'SELECT created_at, event_type, event_msg FROM eventlog ' + \
|
||||||
|
'WHERE linked_type = {} AND linked_id = x\'{}\' '.format(TableTypes.BID, bid_id.hex())
|
||||||
|
q = self.engine.execute(query_str)
|
||||||
|
|
||||||
|
for row in q:
|
||||||
|
events.append({'at': row[0], 'desc': describeEventEntry(row[1], row[2])})
|
||||||
|
return events
|
||||||
|
finally:
|
||||||
|
session.close()
|
||||||
|
session.remove()
|
||||||
|
self.mxDB.release()
|
||||||
|
|
||||||
def acceptBid(self, bid_id):
|
def acceptBid(self, bid_id):
|
||||||
self.log.info('Accepting bid %s', bid_id.hex())
|
self.log.info('Accepting bid %s', bid_id.hex())
|
||||||
|
|
||||||
|
@ -3822,6 +3857,8 @@ class BasicSwap(BaseApp):
|
||||||
|
|
||||||
bid.setState(BidStates.XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX)
|
bid.setState(BidStates.XMR_SWAP_HAVE_SCRIPT_COIN_SPEND_TX)
|
||||||
self.watchXmrSwap(bid, offer, xmr_swap)
|
self.watchXmrSwap(bid, offer, xmr_swap)
|
||||||
|
self.logBidEvent(bid, EventLogTypes.LOCK_TX_A_PUBLISHED, '', session)
|
||||||
|
|
||||||
self.saveBidInSession(bid_id, bid, session, xmr_swap)
|
self.saveBidInSession(bid_id, bid, session, xmr_swap)
|
||||||
|
|
||||||
def sendXmrBidCoinBLockTx(self, bid_id, session):
|
def sendXmrBidCoinBLockTx(self, bid_id, session):
|
||||||
|
@ -3852,7 +3889,7 @@ class BasicSwap(BaseApp):
|
||||||
b_lock_tx_id = ci_to.publishBLockTx(xmr_swap.pkbv, xmr_swap.pkbs, bid.amount_to, xmr_offer.b_fee_rate)
|
b_lock_tx_id = ci_to.publishBLockTx(xmr_swap.pkbv, xmr_swap.pkbs, bid.amount_to, xmr_offer.b_fee_rate)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
error_msg = 'publishBLockTx failed for bid {} with error {}'.format(bid_id.hex(), str(ex))
|
error_msg = 'publishBLockTx failed for bid {} with error {}'.format(bid_id.hex(), str(ex))
|
||||||
num_retries = self.countBidEvents(bid, EventLogTypes.FAILED_TX_B_PUBLISH, session)
|
num_retries = self.countBidEvents(bid, EventLogTypes.FAILED_TX_B_LOCK_PUBLISH, session)
|
||||||
if num_retries > 0:
|
if num_retries > 0:
|
||||||
error_msg += ', retry no. {}'.format(num_retries)
|
error_msg += ', retry no. {}'.format(num_retries)
|
||||||
self.log.error(error_msg)
|
self.log.error(error_msg)
|
||||||
|
@ -3866,7 +3903,7 @@ class BasicSwap(BaseApp):
|
||||||
self.setBidError(bid_id, bid, 'publishBLockTx failed: ' + str(ex), save_bid=False)
|
self.setBidError(bid_id, bid, 'publishBLockTx failed: ' + str(ex), save_bid=False)
|
||||||
self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer)
|
self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer)
|
||||||
|
|
||||||
self.logBidEvent(bid, EventLogTypes.FAILED_TX_B_PUBLISH, str_error, session)
|
self.logBidEvent(bid, EventLogTypes.FAILED_TX_B_LOCK_PUBLISH, str_error, session)
|
||||||
return
|
return
|
||||||
|
|
||||||
self.log.debug('Submitted lock txn %s to %s chain for bid %s', b_lock_tx_id.hex(), chainparams[coin_to]['name'], bid_id.hex())
|
self.log.debug('Submitted lock txn %s to %s chain for bid %s', b_lock_tx_id.hex(), chainparams[coin_to]['name'], bid_id.hex())
|
||||||
|
@ -3876,6 +3913,7 @@ class BasicSwap(BaseApp):
|
||||||
txid=b_lock_tx_id,
|
txid=b_lock_tx_id,
|
||||||
)
|
)
|
||||||
bid.xmr_b_lock_tx.setState(TxStates.TX_NONE)
|
bid.xmr_b_lock_tx.setState(TxStates.TX_NONE)
|
||||||
|
self.logBidEvent(bid, EventLogTypes.LOCK_TX_B_PUBLISHED, '', session)
|
||||||
|
|
||||||
self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer)
|
self.saveBidInSession(bid_id, bid, session, xmr_swap, save_in_progress=offer)
|
||||||
|
|
||||||
|
|
|
@ -59,7 +59,9 @@ class Offer(Base):
|
||||||
from_feerate = sa.Column(sa.BigInteger)
|
from_feerate = sa.Column(sa.BigInteger)
|
||||||
to_feerate = sa.Column(sa.BigInteger)
|
to_feerate = sa.Column(sa.BigInteger)
|
||||||
|
|
||||||
|
# Local fields
|
||||||
auto_accept_bids = sa.Column(sa.Boolean)
|
auto_accept_bids = sa.Column(sa.Boolean)
|
||||||
|
withdraw_to_addr = sa.Column(sa.String) # Address to spend lock tx to - address from wallet if empty TODO
|
||||||
|
|
||||||
state = sa.Column(sa.Integer)
|
state = sa.Column(sa.Integer)
|
||||||
states = sa.Column(sa.LargeBinary) # Packed states and times
|
states = sa.Column(sa.LargeBinary) # Packed states and times
|
||||||
|
@ -87,6 +89,7 @@ class Bid(Base):
|
||||||
expire_at = sa.Column(sa.BigInteger)
|
expire_at = sa.Column(sa.BigInteger)
|
||||||
bid_addr = sa.Column(sa.String)
|
bid_addr = sa.Column(sa.String)
|
||||||
proof_address = sa.Column(sa.String)
|
proof_address = sa.Column(sa.String)
|
||||||
|
withdraw_to_addr = sa.Column(sa.String) # Address to spend lock tx to - address from wallet if empty TODO
|
||||||
|
|
||||||
recovered_secret = sa.Column(sa.LargeBinary)
|
recovered_secret = sa.Column(sa.LargeBinary)
|
||||||
amount_to = sa.Column(sa.BigInteger) # amount * offer.rate
|
amount_to = sa.Column(sa.BigInteger) # amount * offer.rate
|
||||||
|
|
|
@ -17,6 +17,7 @@ from jinja2 import Environment, PackageLoader
|
||||||
from . import __version__
|
from . import __version__
|
||||||
from .util import (
|
from .util import (
|
||||||
dumpj,
|
dumpj,
|
||||||
|
make_int,
|
||||||
)
|
)
|
||||||
from .chainparams import (
|
from .chainparams import (
|
||||||
chainparams,
|
chainparams,
|
||||||
|
@ -300,6 +301,7 @@ class HttpHandler(BaseHTTPRequestHandler):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
coin_from = Coins(int(form_data[b'coin_from'][0]))
|
coin_from = Coins(int(form_data[b'coin_from'][0]))
|
||||||
|
ci_from = swap_client.ci(coin_from)
|
||||||
except Exception:
|
except Exception:
|
||||||
raise ValueError('Unknown Coin From')
|
raise ValueError('Unknown Coin From')
|
||||||
try:
|
try:
|
||||||
|
@ -308,11 +310,11 @@ class HttpHandler(BaseHTTPRequestHandler):
|
||||||
except Exception:
|
except Exception:
|
||||||
raise ValueError('Unknown Coin To')
|
raise ValueError('Unknown Coin To')
|
||||||
|
|
||||||
value_from = inputAmount(form_data[b'amt_from'][0].decode('utf-8'))
|
value_from = inputAmount(form_data[b'amt_from'][0].decode('utf-8'), ci_from)
|
||||||
value_to = inputAmount(form_data[b'amt_to'][0].decode('utf-8'))
|
value_to = inputAmount(form_data[b'amt_to'][0].decode('utf-8'), ci_to)
|
||||||
|
|
||||||
min_bid = int(value_from)
|
min_bid = int(value_from)
|
||||||
rate = int((value_to / value_from) * ci_to.COIN())
|
rate = int((value_to / value_from) * ci_from.COIN())
|
||||||
autoaccept = True if b'autoaccept' in form_data else False
|
autoaccept = True if b'autoaccept' in form_data else False
|
||||||
lock_seconds = int(form_data[b'lockhrs'][0]) * 60 * 60
|
lock_seconds = int(form_data[b'lockhrs'][0]) * 60 * 60
|
||||||
# TODO: More accurate rate
|
# TODO: More accurate rate
|
||||||
|
@ -357,7 +359,7 @@ class HttpHandler(BaseHTTPRequestHandler):
|
||||||
except Exception:
|
except Exception:
|
||||||
raise ValueError('Bad offer ID')
|
raise ValueError('Bad offer ID')
|
||||||
swap_client = self.server.swap_client
|
swap_client = self.server.swap_client
|
||||||
offer = swap_client.getOffer(offer_id)
|
offer, xmr_offer = swap_client.getXmrOffer(offer_id)
|
||||||
assert(offer), 'Unknown offer ID'
|
assert(offer), 'Unknown offer ID'
|
||||||
|
|
||||||
messages = []
|
messages = []
|
||||||
|
@ -374,16 +376,12 @@ class HttpHandler(BaseHTTPRequestHandler):
|
||||||
|
|
||||||
sent_bid_id = swap_client.postBid(offer_id, offer.amount_from, addr_send_from=addr_from).hex()
|
sent_bid_id = swap_client.postBid(offer_id, offer.amount_from, addr_send_from=addr_from).hex()
|
||||||
|
|
||||||
coin_from = Coins(offer.coin_from)
|
ci_from = swap_client.ci(Coins(offer.coin_from))
|
||||||
coin_to = Coins(offer.coin_to)
|
ci_to = swap_client.ci(Coins(offer.coin_to))
|
||||||
ci_from = swap_client.ci(coin_from)
|
|
||||||
ci_to = swap_client.ci(coin_to)
|
|
||||||
ticker_from = swap_client.getTicker(coin_from)
|
|
||||||
ticker_to = swap_client.getTicker(coin_to)
|
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'tla_from': swap_client.getTicker(coin_from),
|
'tla_from': ci_from.ticker(),
|
||||||
'tla_to': swap_client.getTicker(coin_to),
|
'tla_to': ci_to.ticker(),
|
||||||
'state': strOfferState(offer.state),
|
'state': strOfferState(offer.state),
|
||||||
'coin_from': ci_from.coin_name(),
|
'coin_from': ci_from.coin_name(),
|
||||||
'coin_to': ci_to.coin_name(),
|
'coin_to': ci_to.coin_name(),
|
||||||
|
@ -399,6 +397,13 @@ class HttpHandler(BaseHTTPRequestHandler):
|
||||||
'show_bid_form': show_bid_form,
|
'show_bid_form': show_bid_form,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if xmr_offer:
|
||||||
|
int_fee_rate_now = make_int(ci_from.get_fee_rate(), ci_from.exp())
|
||||||
|
data['xmr_type'] = True
|
||||||
|
data['a_fee_rate'] = ci_from.format_amount(xmr_offer.a_fee_rate)
|
||||||
|
data['a_fee_rate_verify'] = ci_from.format_amount(int_fee_rate_now)
|
||||||
|
data['a_fee_warn'] = xmr_offer.a_fee_rate < int_fee_rate_now
|
||||||
|
|
||||||
if offer.was_sent:
|
if offer.was_sent:
|
||||||
data['auto_accept'] = 'True' if offer.auto_accept_bids else 'False'
|
data['auto_accept'] = 'True' if offer.auto_accept_bids else 'False'
|
||||||
|
|
||||||
|
|
|
@ -68,5 +68,13 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<h4>Events</h4>
|
||||||
|
<table>
|
||||||
|
<tr><th>At</th><th>Event</th></tr>
|
||||||
|
{% for e in data.events %}
|
||||||
|
<tr><td>{{ e.at | formatts }}</td><td>{{ e.desc }}</td></tr>
|
||||||
|
{% endfor %}
|
||||||
|
</table>
|
||||||
|
|
||||||
<p><a href="/">home</a></p>
|
<p><a href="/">home</a></p>
|
||||||
</body></html>
|
</body></html>
|
||||||
|
|
|
@ -29,8 +29,15 @@
|
||||||
{% if data.sent == 'True' %}
|
{% if data.sent == 'True' %}
|
||||||
<tr><td>Auto Accept Bids</td><td>{{ data.auto_accept }}</td></tr>
|
<tr><td>Auto Accept Bids</td><td>{{ data.auto_accept }}</td></tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if data.xmr_type == true %}
|
||||||
|
<tr><td>Chain A offer fee rate</td><td>{{ data.a_fee_rate }}</td></tr>
|
||||||
|
<tr><td>Chain A local fee rate</td><td>{{ data.a_fee_rate_verify }} {% if data.a_fee_warn == true %} WARNING {% endif %}</td></tr>
|
||||||
|
{% endif %}
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<form method="post">
|
<form method="post">
|
||||||
{% if data.show_bid_form %}
|
{% if data.show_bid_form %}
|
||||||
<br/><h4>New Bid</h4>
|
<br/><h4>New Bid</h4>
|
||||||
|
|
|
@ -25,17 +25,17 @@ from .basicswap import (
|
||||||
PAGE_LIMIT = 50
|
PAGE_LIMIT = 50
|
||||||
|
|
||||||
|
|
||||||
def validateAmountString(amount):
|
def validateAmountString(amount, ci):
|
||||||
if type(amount) != str:
|
if type(amount) != str:
|
||||||
return
|
return
|
||||||
ar = amount.split('.')
|
ar = amount.split('.')
|
||||||
if len(ar) > 1 and len(ar[1]) > 8:
|
if len(ar) > 1 and len(ar[1]) > ci.exp():
|
||||||
raise ValueError('Too many decimal places in amount {}'.format(amount))
|
raise ValueError('Too many decimal places in amount {}'.format(amount))
|
||||||
|
|
||||||
|
|
||||||
def inputAmount(amount_str):
|
def inputAmount(amount_str, ci):
|
||||||
validateAmountString(amount_str)
|
validateAmountString(amount_str, ci)
|
||||||
return make_int(amount_str)
|
return make_int(amount_str, ci.exp())
|
||||||
|
|
||||||
|
|
||||||
def setCoinFilter(form_data, field_name):
|
def setCoinFilter(form_data, field_name):
|
||||||
|
@ -169,4 +169,7 @@ def describeBid(swap_client, bid, offer, edit_bid, show_txns):
|
||||||
data['initiate_tx_spend'] = getTxSpendHex(bid, TxTypes.ITX)
|
data['initiate_tx_spend'] = getTxSpendHex(bid, TxTypes.ITX)
|
||||||
data['participate_tx_spend'] = getTxSpendHex(bid, TxTypes.PTX)
|
data['participate_tx_spend'] = getTxSpendHex(bid, TxTypes.PTX)
|
||||||
|
|
||||||
|
if offer.swap_type == SwapTypes.XMR_SWAP:
|
||||||
|
data['events'] = swap_client.list_bid_events(bid.bid_id)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
|
@ -200,6 +200,9 @@ class Test(unittest.TestCase):
|
||||||
assert('100.00000000' == format_amount(amount_from, scale_from))
|
assert('100.00000000' == format_amount(amount_from, scale_from))
|
||||||
assert('10.000000000000' == format_amount(amount_to, scale_to))
|
assert('10.000000000000' == format_amount(amount_to, scale_to))
|
||||||
|
|
||||||
|
rate_check = int((amount_to / amount_from) * (10 ** scale_from))
|
||||||
|
assert(rate == rate_check)
|
||||||
|
|
||||||
scale_from = 12
|
scale_from = 12
|
||||||
scale_to = 8
|
scale_to = 8
|
||||||
amount_from = 1 * (10 ** scale_from)
|
amount_from = 1 * (10 ** scale_from)
|
||||||
|
@ -209,6 +212,9 @@ class Test(unittest.TestCase):
|
||||||
assert('1.000000000000' == format_amount(amount_from, scale_from))
|
assert('1.000000000000' == format_amount(amount_from, scale_from))
|
||||||
assert('12.00000000' == format_amount(amount_to, scale_to))
|
assert('12.00000000' == format_amount(amount_to, scale_to))
|
||||||
|
|
||||||
|
rate_check = int((amount_to / amount_from) * (10 ** scale_from))
|
||||||
|
assert(rate == rate_check)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
Loading…
Reference in a new issue