mirror of
https://github.com/basicswap/basicswap.git
synced 2025-01-03 17:29:26 +00:00
protocol: Enable private offers
Users can send private offers that will only be seen by one address. To send a private offer: 1. recipient creates a new address to receive offers on 2. recipient sends the pubkey for the newly created address to the offerer 3. offerer imports the recipient's pubkey 4. offerer sends a new offer to the recipients key instead of the public network Nodes will ignore offers sent on keys other than the network key or keys created for offer-receiving.
This commit is contained in:
parent
f63815b26b
commit
a40519737d
15 changed files with 217 additions and 38 deletions
|
@ -1,3 +1,3 @@
|
|||
name = "basicswap"
|
||||
|
||||
__version__ = "0.0.22"
|
||||
__version__ = "0.0.23"
|
||||
|
|
|
@ -99,6 +99,7 @@ from .basicswap_util import (
|
|||
SEQUENCE_LOCK_TIME,
|
||||
ABS_LOCK_BLOCKS,
|
||||
ABS_LOCK_TIME,
|
||||
AddressTypes,
|
||||
MessageTypes,
|
||||
SwapTypes,
|
||||
OfferStates,
|
||||
|
@ -119,6 +120,14 @@ from .basicswap_util import (
|
|||
isActiveBidState)
|
||||
|
||||
|
||||
def validOfferStateToReceiveBid(offer_state):
|
||||
if offer_state == OfferStates.OFFER_RECEIVED:
|
||||
return True
|
||||
if offer_state == OfferStates.OFFER_SENT:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def threadPollChainState(swap_client, coin_type):
|
||||
while not swap_client.delay_event.is_set():
|
||||
try:
|
||||
|
@ -562,7 +571,11 @@ class BasicSwap(BaseApp):
|
|||
session.execute('ALTER TABLE smsgaddresses ADD COLUMN active_ind INTEGER')
|
||||
session.execute('ALTER TABLE smsgaddresses ADD COLUMN created_at INTEGER')
|
||||
session.execute('ALTER TABLE smsgaddresses ADD COLUMN note VARCHAR')
|
||||
session.execute('ALTER TABLE smsgaddresses ADD COLUMN pubkey VARCHAR')
|
||||
session.execute('UPDATE smsgaddresses SET active_ind = 1, created_at = 1')
|
||||
|
||||
session.execute('ALTER TABLE offers ADD COLUMN addr_to VARCHAR')
|
||||
session.execute(f'UPDATE offers SET addr_to = "{self.network_addr}"')
|
||||
db_version += 1
|
||||
|
||||
if current_version != db_version:
|
||||
|
@ -859,6 +872,11 @@ class BasicSwap(BaseApp):
|
|||
if valid_for_seconds > 24 * 60 * 60:
|
||||
raise ValueError('Bid TTL too high')
|
||||
|
||||
def getOfferAddressTo(self, extra_options):
|
||||
if 'addr_send_to' in extra_options:
|
||||
return extra_options['addr_send_to']
|
||||
return self.network_addr
|
||||
|
||||
def postOffer(self, coin_from, coin_to, amount, rate, min_bid_amount, swap_type,
|
||||
lock_type=SEQUENCE_LOCK_TIME, lock_value=48 * 60 * 60, auto_accept_bids=False, addr_send_from=None, extra_options={}):
|
||||
# Offer to send offer.amount_from of coin_from in exchange for offer.amount_from * offer.rate of coin_to
|
||||
|
@ -882,6 +900,8 @@ class BasicSwap(BaseApp):
|
|||
self.validateOfferLockValue(coin_from_t, coin_to_t, lock_type, lock_value)
|
||||
self.validateOfferValidTime(swap_type, coin_from_t, coin_to_t, valid_for_seconds)
|
||||
|
||||
offer_addr_to = self.getOfferAddressTo(extra_options)
|
||||
|
||||
self.mxDB.acquire()
|
||||
session = None
|
||||
try:
|
||||
|
@ -948,7 +968,7 @@ class BasicSwap(BaseApp):
|
|||
self.callrpc('smsgaddlocaladdress', [offer_addr]) # Enable receiving smsg
|
||||
options = {'decodehex': True, 'ttl_is_seconds': True}
|
||||
msg_valid = max(self.SMSG_SECONDS_IN_HOUR * 1, valid_for_seconds)
|
||||
ro = self.callrpc('smsgsend', [offer_addr, self.network_addr, payload_hex, False, msg_valid, False, options])
|
||||
ro = self.callrpc('smsgsend', [offer_addr, offer_addr_to, payload_hex, False, msg_valid, False, options])
|
||||
msg_id = ro['msgid']
|
||||
|
||||
offer_id = bytes.fromhex(msg_id)
|
||||
|
@ -972,6 +992,7 @@ class BasicSwap(BaseApp):
|
|||
lock_value=msg_buf.lock_value,
|
||||
swap_type=msg_buf.swap_type,
|
||||
|
||||
addr_to=offer_addr_to,
|
||||
addr_from=offer_addr,
|
||||
created_at=offer_created_at,
|
||||
expire_at=offer_created_at + msg_buf.time_valid,
|
||||
|
@ -987,7 +1008,7 @@ class BasicSwap(BaseApp):
|
|||
session.add(offer)
|
||||
session.add(SentOffer(offer_id=offer_id))
|
||||
if addr_send_from is None:
|
||||
session.add(SmsgAddress(addr=offer_addr, use_type=MessageTypes.OFFER, active_ind=1, created_at=offer_created_at))
|
||||
session.add(SmsgAddress(addr=offer_addr, use_type=AddressTypes.OFFER, active_ind=1, created_at=offer_created_at))
|
||||
session.commit()
|
||||
|
||||
finally:
|
||||
|
@ -1542,7 +1563,7 @@ class BasicSwap(BaseApp):
|
|||
session = scoped_session(self.session_factory)
|
||||
self.saveBidInSession(bid_id, bid, session)
|
||||
if addr_send_from is None:
|
||||
session.add(SmsgAddress(addr=bid_addr, use_type=MessageTypes.BID, active_ind=1, created_at=now))
|
||||
session.add(SmsgAddress(addr=bid_addr, use_type=AddressTypes.BID, active_ind=1, created_at=now))
|
||||
session.commit()
|
||||
finally:
|
||||
session.close()
|
||||
|
@ -1913,7 +1934,7 @@ class BasicSwap(BaseApp):
|
|||
session = scoped_session(self.session_factory)
|
||||
self.saveBidInSession(xmr_swap.bid_id, bid, session, xmr_swap)
|
||||
if addr_send_from is None:
|
||||
session.add(SmsgAddress(addr=bid_addr, use_type=MessageTypes.BID, active_ind=1, created_at=bid_created_at))
|
||||
session.add(SmsgAddress(addr=bid_addr, use_type=AddressTypes.BID, active_ind=1, created_at=bid_created_at))
|
||||
session.commit()
|
||||
finally:
|
||||
session.close()
|
||||
|
@ -3376,8 +3397,6 @@ class BasicSwap(BaseApp):
|
|||
self.mxDB.release()
|
||||
|
||||
def processOffer(self, msg):
|
||||
assert(msg['to'] == self.network_addr), 'Offer received on wrong address'
|
||||
|
||||
offer_bytes = bytes.fromhex(msg['hex'][2:-2])
|
||||
offer_data = OfferMessage()
|
||||
offer_data.ParseFromString(offer_bytes)
|
||||
|
@ -3419,6 +3438,14 @@ class BasicSwap(BaseApp):
|
|||
|
||||
session = scoped_session(self.session_factory)
|
||||
try:
|
||||
# Offers must be received on the public network_addr or manually created addresses
|
||||
if msg['to'] != self.network_addr:
|
||||
# Double check active_ind, shouldn't be possible to receive message if not active
|
||||
query_str = 'SELECT COUNT(addr_id) FROM smsgaddresses WHERE addr = "{}" AND use_type = {} AND active_ind = 1'.format(msg['to'], AddressTypes.RECV_OFFER)
|
||||
rv = session.execute(query_str).first()
|
||||
if rv[0] < 1:
|
||||
raise ValueError('Offer received on incorrect address')
|
||||
|
||||
# Check for sent
|
||||
existing_offer = self.getOffer(offer_id)
|
||||
if existing_offer is None:
|
||||
|
@ -3436,6 +3463,7 @@ class BasicSwap(BaseApp):
|
|||
lock_value=offer_data.lock_value,
|
||||
swap_type=offer_data.swap_type,
|
||||
|
||||
addr_to=msg['to'],
|
||||
addr_from=msg['from'],
|
||||
created_at=msg['sent'],
|
||||
expire_at=msg['sent'] + offer_data.time_valid,
|
||||
|
@ -3808,7 +3836,8 @@ class BasicSwap(BaseApp):
|
|||
ci_from = self.ci(offer.coin_from)
|
||||
ci_to = self.ci(offer.coin_to)
|
||||
|
||||
assert(offer.state == OfferStates.OFFER_RECEIVED), 'Bad offer state'
|
||||
if not validOfferStateToReceiveBid(offer.state):
|
||||
raise ValueError('Bad offer state')
|
||||
assert(msg['to'] == offer.addr_from), 'Received on incorrect address'
|
||||
assert(now <= offer.expire_at), 'Offer expired'
|
||||
assert(bid_data.amount >= offer.min_bid_amount), 'Bid amount below minimum'
|
||||
|
@ -4307,9 +4336,8 @@ class BasicSwap(BaseApp):
|
|||
kbsf = self.getPathKey(coin_from, coin_to, bid.created_at, xmr_swap.contract_count, 2, for_ed25519)
|
||||
vkbs = ci_to.sumKeys(kbsl, kbsf)
|
||||
|
||||
address_to = ci_to.getMainWalletAddress()
|
||||
|
||||
try:
|
||||
address_to = ci_to.getMainWalletAddress()
|
||||
txid = ci_to.spendBLockTx(address_to, xmr_swap.vkbv, vkbs, bid.amount_to, xmr_offer.b_fee_rate, xmr_swap.b_restore_height)
|
||||
self.log.debug('Submitted lock B refund txn %s to %s chain for bid %s', txid.hex(), ci_to.coin_name(), bid_id.hex())
|
||||
self.logBidEvent(bid.bid_id, EventLogTypes.LOCK_TX_B_REFUND_TX_PUBLISHED, '', session)
|
||||
|
@ -5054,7 +5082,7 @@ class BasicSwap(BaseApp):
|
|||
try:
|
||||
session = scoped_session(self.session_factory)
|
||||
rv = []
|
||||
query_str = f'SELECT addr_id, addr, use_type, active_ind, created_at, note FROM smsgaddresses {filters} ORDER BY created_at'
|
||||
query_str = f'SELECT addr_id, addr, use_type, active_ind, created_at, note, pubkey FROM smsgaddresses {filters} ORDER BY created_at'
|
||||
|
||||
q = session.execute(query_str)
|
||||
for row in q:
|
||||
|
@ -5065,6 +5093,7 @@ class BasicSwap(BaseApp):
|
|||
'active_ind': row[3],
|
||||
'created_at': row[4],
|
||||
'note': row[5],
|
||||
'pubkey': row[6],
|
||||
})
|
||||
return rv
|
||||
finally:
|
||||
|
@ -5072,21 +5101,36 @@ class BasicSwap(BaseApp):
|
|||
session.remove()
|
||||
self.mxDB.release()
|
||||
|
||||
#listening_keys = self.callcoinrpc(Coins.PART, 'smsglocalkeys', [])
|
||||
return []
|
||||
|
||||
def addSMSGAddress(self, addressnote=None):
|
||||
def newSMSGAddress(self, addressnote=None):
|
||||
# TODO: smsg addresses should be generated from a unique chain
|
||||
self.mxDB.acquire()
|
||||
try:
|
||||
session = scoped_session(self.session_factory)
|
||||
now = int(time.time())
|
||||
new_addr = self.callrpc('getnewaddress')
|
||||
addr_info = self.callrpc('getaddressinfo', [new_addr])
|
||||
self.callrpc('smsgaddlocaladdress', [new_addr]) # Enable receiving smsgs
|
||||
|
||||
session.add(SmsgAddress(addr=new_addr, use_type=MessageTypes.OFFER, active_ind=1, created_at=now, note=addressnote))
|
||||
session.add(SmsgAddress(addr=new_addr, use_type=AddressTypes.RECV_OFFER, active_ind=1, created_at=now, note=addressnote, pubkey=addr_info['pubkey']))
|
||||
session.commit()
|
||||
return new_addr
|
||||
return new_addr, addr_info['pubkey']
|
||||
finally:
|
||||
session.close()
|
||||
session.remove()
|
||||
self.mxDB.release()
|
||||
|
||||
def addSMSGAddress(self, pubkey_hex, addressnote=None):
|
||||
self.mxDB.acquire()
|
||||
try:
|
||||
session = scoped_session(self.session_factory)
|
||||
now = int(time.time())
|
||||
ci = self.ci(Coins.PART)
|
||||
add_addr = ci.pubkey_to_address(bytes.fromhex(pubkey_hex))
|
||||
self.callrpc('smsgaddaddress', [add_addr, pubkey_hex])
|
||||
|
||||
session.add(SmsgAddress(addr=add_addr, use_type=AddressTypes.SEND_OFFER, active_ind=1, created_at=now, note=addressnote, pubkey=pubkey_hex))
|
||||
session.commit()
|
||||
return add_addr
|
||||
finally:
|
||||
session.close()
|
||||
session.remove()
|
||||
|
@ -5107,7 +5151,15 @@ class BasicSwap(BaseApp):
|
|||
self.mxDB.release()
|
||||
|
||||
def listSmsgAddresses(self, use_type_str):
|
||||
use_type = MessageTypes.OFFER if use_type_str == 'offer' else MessageTypes.BID
|
||||
if use_type_str == 'offer_send_from':
|
||||
use_type = AddressTypes.OFFER
|
||||
elif use_type_str == 'offer_send_to':
|
||||
use_type = AddressTypes.SEND_OFFER
|
||||
elif use_type_str == 'bid':
|
||||
use_type = AddressTypes.BID
|
||||
else:
|
||||
raise ValueError('Unknown address type')
|
||||
|
||||
self.mxDB.acquire()
|
||||
try:
|
||||
session = scoped_session(self.session_factory)
|
||||
|
|
|
@ -38,6 +38,13 @@ class MessageTypes(IntEnum):
|
|||
OFFER_REVOKE = auto()
|
||||
|
||||
|
||||
class AddressTypes(IntEnum):
|
||||
OFFER = auto()
|
||||
BID = auto()
|
||||
RECV_OFFER = auto()
|
||||
SEND_OFFER = auto()
|
||||
|
||||
|
||||
class SwapTypes(IntEnum):
|
||||
SELLER_FIRST = auto()
|
||||
BUYER_FIRST = auto()
|
||||
|
@ -239,11 +246,15 @@ def strTxType(tx_type):
|
|||
return 'Unknown'
|
||||
|
||||
|
||||
def strMessageType(msg_type):
|
||||
if msg_type == MessageTypes.OFFER:
|
||||
return 'Offers'
|
||||
if msg_type == MessageTypes.BID:
|
||||
return 'Bids'
|
||||
def strAddressType(addr_type):
|
||||
if addr_type == AddressTypes.OFFER:
|
||||
return 'Offer'
|
||||
if addr_type == AddressTypes.BID:
|
||||
return 'Bid'
|
||||
if addr_type == AddressTypes.RECV_OFFER:
|
||||
return 'Offer recv'
|
||||
if addr_type == AddressTypes.SEND_OFFER:
|
||||
return 'Offer send'
|
||||
return 'Unknown'
|
||||
|
||||
|
||||
|
|
|
@ -57,6 +57,7 @@ class Offer(Base):
|
|||
secret_hash = sa.Column(sa.LargeBinary)
|
||||
|
||||
addr_from = sa.Column(sa.String)
|
||||
addr_to = sa.Column(sa.String)
|
||||
created_at = sa.Column(sa.BigInteger)
|
||||
expire_at = sa.Column(sa.BigInteger)
|
||||
was_sent = sa.Column(sa.Boolean)
|
||||
|
@ -218,6 +219,7 @@ class SmsgAddress(Base):
|
|||
active_ind = sa.Column(sa.Integer)
|
||||
created_at = sa.Column(sa.BigInteger)
|
||||
addr = sa.Column(sa.String, unique=True)
|
||||
pubkey = sa.Column(sa.String)
|
||||
use_type = sa.Column(sa.Integer)
|
||||
note = sa.Column(sa.String)
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ from .basicswap_util import (
|
|||
strOfferState,
|
||||
strBidState,
|
||||
strTxState,
|
||||
strMessageType,
|
||||
strAddressType,
|
||||
getLockName,
|
||||
SEQUENCE_LOCK_TIME,
|
||||
ABS_LOCK_TIME,
|
||||
|
@ -59,6 +59,12 @@ env = Environment(loader=PackageLoader('basicswap', 'templates'))
|
|||
env.filters['formatts'] = format_timestamp
|
||||
|
||||
|
||||
def value_or_none(v):
|
||||
if v == -1 or v == '-1':
|
||||
return None
|
||||
return v
|
||||
|
||||
|
||||
def getCoinName(c):
|
||||
if c == Coins.PART_ANON:
|
||||
return chainparams[Coins.PART]['name'].capitalize() + 'Anon'
|
||||
|
@ -434,9 +440,15 @@ class HttpHandler(BaseHTTPRequestHandler):
|
|||
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'] = None if page_data['addr_from'] == '-1' else page_data['addr_from']
|
||||
parsed_data['addr_from'] = value_or_none(page_data['addr_from'])
|
||||
else:
|
||||
parsed_data['addr_from'] = None
|
||||
|
||||
|
@ -586,6 +598,9 @@ class HttpHandler(BaseHTTPRequestHandler):
|
|||
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']
|
||||
|
||||
offer_id = swap_client.postOffer(
|
||||
parsed_data['coin_from'],
|
||||
parsed_data['coin_to'],
|
||||
|
@ -613,6 +628,7 @@ class HttpHandler(BaseHTTPRequestHandler):
|
|||
messages = []
|
||||
page_data = {
|
||||
# Set defaults
|
||||
'addr_to': -1,
|
||||
'fee_from_conf': 2,
|
||||
'fee_to_conf': 2,
|
||||
'validhrs': 1,
|
||||
|
@ -649,7 +665,8 @@ class HttpHandler(BaseHTTPRequestHandler):
|
|||
h2=self.server.title,
|
||||
messages=messages,
|
||||
coins=listAvailableCoins(swap_client),
|
||||
addrs=swap_client.listSmsgAddresses('offer'),
|
||||
addrs=swap_client.listSmsgAddresses('offer_send_from'),
|
||||
addrs_to=swap_client.listSmsgAddresses('offer_send_to'),
|
||||
data=page_data,
|
||||
form_id=os.urandom(8).hex(),
|
||||
), 'UTF-8')
|
||||
|
@ -714,6 +731,7 @@ class HttpHandler(BaseHTTPRequestHandler):
|
|||
'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': 'True' if offer.was_sent else 'False',
|
||||
|
@ -800,7 +818,8 @@ class HttpHandler(BaseHTTPRequestHandler):
|
|||
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)))
|
||||
ci_to.format_amount(o.rate),
|
||||
'Public' if o.addr_to == swap_client.network_addr else o.addr_to))
|
||||
|
||||
template = env.get_template('offers.html')
|
||||
return bytes(template.render(
|
||||
|
@ -976,21 +995,34 @@ class HttpHandler(BaseHTTPRequestHandler):
|
|||
elif b'shownewaddr' in form_data:
|
||||
listaddresses = False
|
||||
page_data['new_address'] = True
|
||||
elif b'showaddaddr' in form_data:
|
||||
listaddresses = False
|
||||
page_data['new_send_address'] = True
|
||||
elif b'createnewaddr' in form_data:
|
||||
addressnote = '' if b'addressnote' not in form_data else form_data[b'addressnote'][0].decode('utf-8')
|
||||
if not validateTextInput(addressnote, 'Address note', messages, max_length=30):
|
||||
listaddresses = False
|
||||
page_data['new_address'] = True
|
||||
else:
|
||||
new_addr = swap_client.addSMSGAddress(addressnote=addressnote)
|
||||
messages.append(f'Created address {new_addr}')
|
||||
new_addr, pubkey = swap_client.newSMSGAddress(addressnote=addressnote)
|
||||
messages.append(f'Created address {new_addr}, pubkey {pubkey}')
|
||||
elif b'createnewsendaddr' in form_data:
|
||||
pubkey_hex = form_data[b'addresspubkey'][0].decode('utf-8')
|
||||
addressnote = '' if b'addressnote' not in form_data else form_data[b'addressnote'][0].decode('utf-8')
|
||||
if not validateTextInput(addressnote, 'Address note', messages, max_length=30) or \
|
||||
not validateTextInput(pubkey_hex, 'Pubkey', messages, max_length=66):
|
||||
listaddresses = False
|
||||
page_data['new_send_address'] = True
|
||||
else:
|
||||
new_addr = swap_client.addSMSGAddress(pubkey_hex, addressnote=addressnote)
|
||||
messages.append(f'Added address {new_addr}')
|
||||
|
||||
if listaddresses is True:
|
||||
smsgaddresses = swap_client.listAllSMSGAddresses()
|
||||
network_addr = swap_client.network_addr
|
||||
|
||||
for addr in smsgaddresses:
|
||||
addr['type'] = strMessageType(addr['type'])
|
||||
addr['type'] = strAddressType(addr['type'])
|
||||
|
||||
template = env.get_template('smsgaddresses.html')
|
||||
return bytes(template.render(
|
||||
|
|
|
@ -13,6 +13,7 @@ from io import BytesIO
|
|||
from basicswap.contrib.test_framework import segwit_addr
|
||||
|
||||
from .util import (
|
||||
b58encode,
|
||||
decodeScriptNum,
|
||||
getCompactSizeLen,
|
||||
SerialiseNumCompact,
|
||||
|
@ -251,6 +252,13 @@ class BTCInterface(CoinInterface):
|
|||
pkh = hash160(pk)
|
||||
return segwit_addr.encode(bech32_prefix, version, pkh)
|
||||
|
||||
def pubkey_to_address(self, pk):
|
||||
assert(len(pk) == 33)
|
||||
prefix = chainparams[self.coin_type()][self._network]['pubkey_address']
|
||||
data = bytes((prefix,)) + hash160(pk)
|
||||
checksum = hashlib.sha256(hashlib.sha256(data).digest()).digest()
|
||||
return b58encode(data + checksum[0:4])
|
||||
|
||||
def getNewSecretKey(self):
|
||||
return getSecretInt()
|
||||
|
||||
|
|
|
@ -129,6 +129,8 @@ def js_offers(self, url_split, post_string, is_json, sent=False):
|
|||
ci_from = self.server.swap_client.ci(o.coin_from)
|
||||
ci_to = self.server.swap_client.ci(o.coin_to)
|
||||
rv.append({
|
||||
'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,
|
||||
|
@ -136,7 +138,7 @@ def js_offers(self, url_split, post_string, is_json, sent=False):
|
|||
'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)
|
||||
'rate': ci_to.format_amount(o.rate),
|
||||
})
|
||||
|
||||
return bytes(json.dumps(rv), 'UTF-8')
|
||||
|
@ -259,8 +261,13 @@ def js_smsgaddresses(self, url_split, post_string, is_json):
|
|||
post_data = urllib.parse.parse_qs(post_string)
|
||||
if url_split[3] == 'new':
|
||||
addressnote = get_data_entry_or(post_data, 'addressnote', '')
|
||||
new_addr = swap_client.addSMSGAddress(addressnote)
|
||||
return bytes(json.dumps({'new_address': new_addr}), 'UTF-8')
|
||||
new_addr, pubkey = swap_client.newSMSGAddress(addressnote)
|
||||
return bytes(json.dumps({'new_address': new_addr, 'pubkey': pubkey}), 'UTF-8')
|
||||
if url_split[3] == '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 url_split[3] == 'edit':
|
||||
address = get_data_entry(post_data, 'address')
|
||||
activeind = int(get_data_entry(post_data, 'active_ind'))
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
<tr><td>Rate</td><td>{{ data.rate }} {{ data.amt_from }}/{{ data.tla_from }}</td></tr>
|
||||
<tr><td>Script Lock Type</td><td>{{ data.lock_type }}</td></tr>
|
||||
<tr><td>Script Lock Value</td><td>{{ data.lock_value }}</td></tr>
|
||||
<tr><td>Address To</td><td>{{ data.addr_to }}</td></tr>
|
||||
<tr><td>Address From</td><td>{{ data.addr_from }}</td></tr>
|
||||
<tr><td>Created At</td><td>{{ data.created_at | formatts }}</td></tr>
|
||||
<tr><td>Expired At</td><td>{{ data.expired_at | formatts }}</td></tr>
|
||||
|
|
|
@ -8,6 +8,12 @@
|
|||
<form method="post">
|
||||
|
||||
<table>
|
||||
<tr><td>Send To</td><td><select name="addr_to_" disabled>
|
||||
<option{% if data.addr_to=="-1" %} selected{% endif %} value="-1">-- Public Network --</option>
|
||||
{% for a in addrs_to %}
|
||||
<option{% if data.addr_to==a %} selected{% endif %} value="{{ a }}">{{ a }}</option>
|
||||
{% endfor %}
|
||||
</select></td></tr>
|
||||
<tr><td>Send From Address</td><td><select name="addr_from_" disabled>
|
||||
{% for a in addrs %}
|
||||
<option{% if data.addr_from==a %} selected{% endif %} value="{{ a }}">{{ a }}</option>
|
||||
|
@ -62,6 +68,7 @@
|
|||
<input name="submit_offer" type="submit" value="Confirm Offer">
|
||||
<input name="step2" type="submit" value="Back">
|
||||
<input type="hidden" name="formid" value="{{ form_id }}">
|
||||
<input type="hidden" name="addr_to" value="{{ data.addr_to }}">
|
||||
<input type="hidden" name="addr_from" value="{{ data.addr_from }}">
|
||||
<input type="hidden" name="coin_from" value="{{ data.coin_from }}">
|
||||
<input type="hidden" name="fee_from_extra" value="{{ data.fee_from_extra }}">
|
||||
|
|
|
@ -8,6 +8,12 @@
|
|||
<form method="post">
|
||||
|
||||
<table>
|
||||
<tr><td>Send To</td><td><select name="addr_to">
|
||||
<option{% if data.addr_to=="-1" %} selected{% endif %} value="-1">-- Public Network --</option>
|
||||
{% for a in addrs_to %}
|
||||
<option{% if data.addr_to==a %} selected{% endif %} value="{{ a }}">{{ a }}</option>
|
||||
{% endfor %}
|
||||
</select></td></tr>
|
||||
<tr><td>Send From Address</td><td><select name="addr_from">
|
||||
{% for a in addrs %}
|
||||
<option{% if data.addr_from==a %} selected{% endif %} value="{{ a }}">{{ a }}</option>
|
||||
|
|
|
@ -8,6 +8,12 @@
|
|||
<form method="post">
|
||||
|
||||
<table>
|
||||
<tr><td>Send To</td><td><select name="addr_to_" disabled>
|
||||
<option{% if data.addr_to=="-1" %} selected{% endif %} value="-1">-- Public Network --</option>
|
||||
{% for a in addrs_to %}
|
||||
<option{% if data.addr_to==a %} selected{% endif %} value="{{ a }}">{{ a }}</option>
|
||||
{% endfor %}
|
||||
</select></td></tr>
|
||||
<tr><td>Send From Address</td><td><select name="addr_from_" disabled>
|
||||
{% for a in addrs %}
|
||||
<option{% if data.addr_from==a %} selected{% endif %} value="{{ a }}">{{ a }}</option>
|
||||
|
@ -59,6 +65,7 @@
|
|||
<input name="check_offer" type="submit" value="Continue">
|
||||
<input name="step1" type="submit" value="Back">
|
||||
<input type="hidden" name="formid" value="{{ form_id }}">
|
||||
<input type="hidden" name="addr_to" value="{{ data.addr_to }}">
|
||||
<input type="hidden" name="addr_from" value="{{ data.addr_from }}">
|
||||
<input type="hidden" name="coin_from" value="{{ data.coin_from }}">
|
||||
<input type="hidden" name="coin_to" value="{{ data.coin_to }}">
|
||||
|
|
|
@ -45,9 +45,9 @@
|
|||
|
||||
|
||||
<table>
|
||||
<tr><th>At</th><th>Offer ID</th><th>Coin From</th><th>Coin To</th><th>Amount From</th><th>Amount To</th><th>Rate</th></tr>
|
||||
<tr><th>At</th><th>Recipient</th><th>Offer ID</th><th>Coin From</th><th>Coin To</th><th>Amount From</th><th>Amount To</th><th>Rate</th></tr>
|
||||
{% for o in offers %}
|
||||
<tr><td>{{ o[0] }}</td><td><a href=/offer/{{ o[1] }}>{{ o[1] }}</a></td><td>{{ o[2] }}</td><td>{{ o[3] }}</td><td>{{ o[4] }}</td><td>{{ o[5] }}</td><td>{{ o[6] }}</td></tr>
|
||||
<tr><td>{{ o[0] }}</td><td>{{ o[7] }}</td><td><a href=/offer/{{ o[1] }}>{{ o[1] }}</a></td><td>{{ o[2] }}</td><td>{{ o[3] }}</td><td>{{ o[4] }}</td><td>{{ o[5] }}</td><td>{{ o[6] }}</td></tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
<br/><h4>Edit Address {{ data.addr_data.addr }}</h4>
|
||||
<table>
|
||||
<tr><td>Pubkey</td><td>{{ data.addr_data.pubkey }}</td></tr>
|
||||
<tr><td>Active</td><td><select name="active_ind">
|
||||
<option value="1"{% if data.addr_data.active_ind==1 %} selected{% endif %}>True</option>
|
||||
<option value="0"{% if data.addr_data.active_ind==0 %} selected{% endif %}>False</option>
|
||||
|
@ -23,12 +24,20 @@
|
|||
<tr><td><input type="submit" name="saveaddr" value="Save Address"><input type="submit" name="cancel" value="Cancel"></td></tr>
|
||||
</table>
|
||||
{% elif data.new_address %}
|
||||
<br/><h4>New Address</h4>
|
||||
<br/><h4>New Receiving Address</h4>
|
||||
<table>
|
||||
<tr><td>Note</td><td><input name="addressnote" type="text" value="" maxlength="30"></td></tr>
|
||||
|
||||
<tr><td><input type="submit" name="createnewaddr" value="Create Address"><input type="submit" name="cancel" value="Cancel"></td></tr>
|
||||
</table>
|
||||
{% elif data.new_send_address %}
|
||||
<br/><h4>Add Sending Address</h4>
|
||||
<table>
|
||||
<tr><td>Pubkey</td><td><input name="addresspubkey" type="text" value="" maxlength="66"></td></tr>
|
||||
<tr><td>Note</td><td><input name="addressnote" type="text" value="" maxlength="30"></td></tr>
|
||||
|
||||
<tr><td><input type="submit" name="createnewsendaddr" value="Add Address"><input type="submit" name="cancel" value="Cancel"></td></tr>
|
||||
</table>
|
||||
{% else %}
|
||||
<table>
|
||||
<tr><th>Address</th><th>Type</th><th>Active</th><th>Created At</th><th>Note</th><th>Action</th></tr>
|
||||
|
@ -39,6 +48,7 @@
|
|||
</table>
|
||||
|
||||
<input type="submit" name="shownewaddr" value="New Address">
|
||||
<input type="submit" name="showaddaddr" value="Add Sending Address">
|
||||
{% endif %}
|
||||
<input type="hidden" name="formid" value="{{ form_id }}">
|
||||
</form>
|
||||
|
|
|
@ -21,7 +21,7 @@ from coincurve.ecdsaotves import (
|
|||
from coincurve.keys import (
|
||||
PrivateKey)
|
||||
|
||||
from basicswap.ecc_util import i2b
|
||||
from basicswap.ecc_util import i2b, h2b
|
||||
from basicswap.interface_btc import BTCInterface
|
||||
from basicswap.interface_xmr import XMRInterface
|
||||
|
||||
|
@ -206,6 +206,13 @@ class Test(unittest.TestCase):
|
|||
assert(len(sig) == 64)
|
||||
ci.verifyCompact(pk, 'test signing message', sig)
|
||||
|
||||
def test_pubkey_to_address(self):
|
||||
coin_settings = {'rpcport': 0, 'rpcauth': 'none', 'blocks_confirmed': 1, 'conf_target': 1}
|
||||
ci = BTCInterface(coin_settings, 'regtest')
|
||||
pk = h2b('02c26a344e7d21bcc6f291532679559f2fd234c881271ff98714855edc753763a6')
|
||||
addr = ci.pubkey_to_address(pk)
|
||||
assert(addr == 'mj6SdSxmWRmdDqR5R3FfZmRiLmQfQAsLE8')
|
||||
|
||||
def test_dleag(self):
|
||||
coin_settings = {'rpcport': 0, 'walletrpcport': 0, 'walletrpcauth': 'none', 'blocks_confirmed': 1, 'conf_target': 1}
|
||||
ci = XMRInterface(coin_settings, 'regtest')
|
||||
|
|
|
@ -466,8 +466,9 @@ class Test(unittest.TestCase):
|
|||
end_xmr = float(js_0_end['6']['balance']) + float(js_0_end['6']['unconfirmed'])
|
||||
assert(end_xmr > 10.9 and end_xmr < 11.0)
|
||||
|
||||
|
||||
def test_011_smsgaddresses(self):
|
||||
logging.info('---------- Test address management and private offers')
|
||||
swap_clients = self.swap_clients
|
||||
js_1 = json.loads(urlopen('http://127.0.0.1:1801/json/smsgaddresses').read())
|
||||
|
||||
post_json = {
|
||||
|
@ -475,6 +476,7 @@ class Test(unittest.TestCase):
|
|||
}
|
||||
json_rv = json.loads(post_json_req('http://127.0.0.1:1801/json/smsgaddresses/new', post_json))
|
||||
new_address = json_rv['new_address']
|
||||
new_address_pk = json_rv['pubkey']
|
||||
|
||||
js_2 = json.loads(urlopen('http://127.0.0.1:1801/json/smsgaddresses').read())
|
||||
assert(len(js_2) == len(js_1) + 1)
|
||||
|
@ -534,6 +536,33 @@ class Test(unittest.TestCase):
|
|||
found = True
|
||||
assert(found is True)
|
||||
|
||||
post_json = {
|
||||
'addresspubkey': new_address_pk,
|
||||
'addressnote': 'testing_add_addr',
|
||||
}
|
||||
json_rv = json.loads(post_json_req('http://127.0.0.1:1800/json/smsgaddresses/add', post_json))
|
||||
assert(json_rv['added_address'] == new_address)
|
||||
|
||||
post_json = {
|
||||
'addr_to': new_address,
|
||||
'addr_from': -1,
|
||||
'coin_from': 1,
|
||||
'coin_to': 6,
|
||||
'amt_from': 1,
|
||||
'amt_to': 1,
|
||||
'lockhrs': 24,
|
||||
'autoaccept': True}
|
||||
rv = json.loads(post_json_req('http://127.0.0.1:1800/json/offers/new', post_json))
|
||||
offer_id_hex = rv['offer_id']
|
||||
|
||||
wait_for_offer(test_delay_event, swap_clients[1], bytes.fromhex(offer_id_hex))
|
||||
|
||||
rv = json.loads(urlopen(f'http://127.0.0.1:1801/json/offers/{offer_id_hex}').read())
|
||||
assert(rv[0]['addr_to'] == new_address)
|
||||
|
||||
rv = json.loads(urlopen(f'http://127.0.0.1:1800/json/offers/{offer_id_hex}').read())
|
||||
assert(rv[0]['addr_to'] == new_address)
|
||||
|
||||
def test_02_leader_recover_a_lock_tx(self):
|
||||
logging.info('---------- Test PART to XMR leader recovers coin a lock tx')
|
||||
swap_clients = self.swap_clients
|
||||
|
|
Loading…
Reference in a new issue