diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py
index a9334fc..98e5d94 100644
--- a/basicswap/basicswap.py
+++ b/basicswap/basicswap.py
@@ -558,6 +558,12 @@ class BasicSwap(BaseApp):
elif current_version == 9:
session.execute('ALTER TABLE wallets ADD COLUMN wallet_data VARCHAR')
db_version += 1
+ elif current_version == 10:
+ 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('UPDATE smsgaddresses SET active_ind = 1, created_at = 1')
+ db_version += 1
if current_version != db_version:
self.db_version = db_version
@@ -981,7 +987,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))
+ session.add(SmsgAddress(addr=offer_addr, use_type=MessageTypes.OFFER, active_ind=1, created_at=offer_created_at))
session.commit()
finally:
@@ -1536,7 +1542,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))
+ session.add(SmsgAddress(addr=bid_addr, use_type=MessageTypes.BID, active_ind=1, created_at=now))
session.commit()
finally:
session.close()
@@ -1907,7 +1913,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))
+ session.add(SmsgAddress(addr=bid_addr, use_type=MessageTypes.BID, active_ind=1, created_at=bid_created_at))
session.commit()
finally:
session.close()
@@ -5040,13 +5046,73 @@ class BasicSwap(BaseApp):
finally:
self.mxDB.release()
+ def listAllSMSGAddresses(self, addr_id=None):
+ filters = ''
+ if addr_id is not None:
+ filters += f' WHERE addr_id = {addr_id} '
+ self.mxDB.acquire()
+ 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'
+
+ q = session.execute(query_str)
+ for row in q:
+ rv.append({
+ 'id': row[0],
+ 'addr': row[1],
+ 'type': row[2],
+ 'active_ind': row[3],
+ 'created_at': row[4],
+ 'note': row[5],
+ })
+ return rv
+ finally:
+ session.close()
+ session.remove()
+ self.mxDB.release()
+
+ #listening_keys = self.callcoinrpc(Coins.PART, 'smsglocalkeys', [])
+ return []
+
+ def addSMSGAddress(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')
+ 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.commit()
+ return new_addr
+ finally:
+ session.close()
+ session.remove()
+ self.mxDB.release()
+
+ def editSMSGAddress(self, address, active_ind, addressnote):
+ self.mxDB.acquire()
+ try:
+ session = scoped_session(self.session_factory)
+ mode = '-' if active_ind == 0 else '+'
+ self.callrpc('smsglocalkeys', ['recv', mode, address])
+
+ session.execute('UPDATE smsgaddresses SET active_ind = {}, note = "{}" WHERE addr = "{}"'.format(active_ind, addressnote, address))
+ session.commit()
+ finally:
+ session.close()
+ session.remove()
+ self.mxDB.release()
+
def listSmsgAddresses(self, use_type_str):
use_type = MessageTypes.OFFER if use_type_str == 'offer' else MessageTypes.BID
self.mxDB.acquire()
try:
session = scoped_session(self.session_factory)
rv = []
- q = session.execute('SELECT addr FROM smsgaddresses WHERE use_type = {} ORDER BY addr_id DESC'.format(use_type))
+ q = session.execute('SELECT addr FROM smsgaddresses WHERE use_type = {} AND active_ind = 1 ORDER BY addr_id DESC'.format(use_type))
for row in q:
rv.append(row[0])
return rv
diff --git a/basicswap/basicswap_util.py b/basicswap/basicswap_util.py
index bb82fa8..f366329 100644
--- a/basicswap/basicswap_util.py
+++ b/basicswap/basicswap_util.py
@@ -239,6 +239,14 @@ def strTxType(tx_type):
return 'Unknown'
+def strMessageType(msg_type):
+ if msg_type == MessageTypes.OFFER:
+ return 'Offers'
+ if msg_type == MessageTypes.BID:
+ return 'Bids'
+ return 'Unknown'
+
+
def getLockName(lock_type):
if lock_type == SEQUENCE_LOCK_BLOCKS:
return 'Sequence lock, blocks'
diff --git a/basicswap/db.py b/basicswap/db.py
index aa3edd6..d7d7909 100644
--- a/basicswap/db.py
+++ b/basicswap/db.py
@@ -12,7 +12,7 @@ from enum import IntEnum, auto
from sqlalchemy.ext.declarative import declarative_base
-CURRENT_DB_VERSION = 10
+CURRENT_DB_VERSION = 11
Base = declarative_base()
@@ -215,8 +215,11 @@ class SmsgAddress(Base):
__tablename__ = 'smsgaddresses'
addr_id = sa.Column(sa.Integer, primary_key=True, autoincrement=True)
- addr = sa.Column(sa.String)
+ active_ind = sa.Column(sa.Integer)
+ created_at = sa.Column(sa.BigInteger)
+ addr = sa.Column(sa.String, unique=True)
use_type = sa.Column(sa.Integer)
+ note = sa.Column(sa.String)
class EventQueue(Base):
diff --git a/basicswap/http_server.py b/basicswap/http_server.py
index cd37f9b..dbac543 100644
--- a/basicswap/http_server.py
+++ b/basicswap/http_server.py
@@ -27,6 +27,7 @@ from .basicswap_util import (
strOfferState,
strBidState,
strTxState,
+ strMessageType,
getLockName,
SEQUENCE_LOCK_TIME,
ABS_LOCK_TIME,
@@ -40,6 +41,7 @@ from .js_server import (
js_sentbids,
js_network,
js_revokeoffer,
+ js_smsgaddresses,
js_index,
)
from .ui import (
@@ -77,6 +79,16 @@ def listAvailableCoins(swap_client):
return coins
+def validateTextInput(text, name, messages, max_length=None):
+ if max_length is not None and len(text) > max_length:
+ messages.append(f'Error: {name} is too long')
+ return False
+ if len(text) > 0 and text.isalnum() is False:
+ messages.append(f'Error: {name} must consist of only letters and digits')
+ return False
+ return True
+
+
def extractDomain(url):
return url.split('://', 1)[1].split('/', 1)[0]
@@ -930,6 +942,67 @@ 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_smsgaddresses(self, url_split, post_string):
+ swap_client = self.server.swap_client
+
+ page_data = {}
+ messages = []
+ smsgaddresses = []
+
+ listaddresses = True
+ form_data = self.checkForm(post_string, 'smsgaddresses', messages)
+ if form_data:
+ edit_address_id = None
+ for key in form_data:
+ if key.startswith(b'editaddr_'):
+ edit_address_id = int(key.split(b'_')[1])
+ break
+ if edit_address_id is not None:
+ listaddresses = False
+ page_data['edit_address'] = edit_address_id
+ page_data['addr_data'] = swap_client.listAllSMSGAddresses(addr_id=edit_address_id)[0]
+ elif b'saveaddr' in form_data:
+ edit_address_id = int(form_data[b'edit_address_id'][0].decode('utf-8'))
+ edit_addr = form_data[b'edit_address'][0].decode('utf-8')
+ active_ind = int(form_data[b'active_ind'][0].decode('utf-8'))
+ assert(active_ind == 0 or active_ind == 1), 'Invalid sort by'
+ 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['edit_address'] = edit_address_id
+ else:
+ swap_client.editSMSGAddress(edit_addr, active_ind=active_ind, addressnote=addressnote)
+ messages.append(f'Edited address {edit_addr}')
+ elif b'shownewaddr' in form_data:
+ listaddresses = False
+ page_data['new_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}')
+
+ if listaddresses is True:
+ smsgaddresses = swap_client.listAllSMSGAddresses()
+ network_addr = swap_client.network_addr
+
+ for addr in smsgaddresses:
+ addr['type'] = strMessageType(addr['type'])
+
+ template = env.get_template('smsgaddresses.html')
+ return bytes(template.render(
+ title=self.server.title,
+ h2=self.server.title,
+ messages=messages,
+ data=page_data,
+ form_id=os.urandom(8).hex(),
+ smsgaddresses=smsgaddresses,
+ network_addr=network_addr,
+ ), 'UTF-8')
+
def page_shutdown(self, url_split, post_string):
swap_client = self.server.swap_client
swap_client.stopRunning()
@@ -976,6 +1049,7 @@ class HttpHandler(BaseHTTPRequestHandler):
'sentbids': js_sentbids,
'network': js_network,
'revokeoffer': js_revokeoffer,
+ 'smsgaddresses': js_smsgaddresses,
}.get(url_split[2], js_index)
return func(self, url_split, post_string, is_json)
except Exception as ex:
@@ -1035,6 +1109,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] == 'smsgaddresses':
+ return self.page_smsgaddresses(url_split, post_string)
if url_split[1] == 'shutdown':
return self.page_shutdown(url_split, post_string)
return self.page_index(url_split)
diff --git a/basicswap/js_server.py b/basicswap/js_server.py
index 21f4e03..f879788 100644
--- a/basicswap/js_server.py
+++ b/basicswap/js_server.py
@@ -245,3 +245,27 @@ def js_revokeoffer(self, url_split, post_string, is_json):
def js_index(self, url_split, post_string, is_json):
return bytes(json.dumps(self.server.swap_client.getSummary()), 'UTF-8')
+
+
+def js_smsgaddresses(self, url_split, post_string, is_json):
+ swap_client = self.server.swap_client
+ if len(url_split) > 3:
+ if post_string == '':
+ raise ValueError('No post data')
+ if is_json:
+ post_data = json.loads(post_string)
+ post_data['is_json'] = True
+ else:
+ 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')
+ elif url_split[3] == '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')
+
+ return bytes(json.dumps(swap_client.listAllSMSGAddresses()), 'UTF-8')
diff --git a/basicswap/templates/index.html b/basicswap/templates/index.html
index 22a11cb..1604053 100644
--- a/basicswap/templates/index.html
+++ b/basicswap/templates/index.html
@@ -11,6 +11,7 @@ Version: {{ version }}
Settings
RPC Console
Explorers
+SMSG Addresses
Swaps in progress: {{ summary.num_swapping }}
Network Offers: {{ summary.num_network_offers }}
diff --git a/basicswap/templates/smsgaddresses.html b/basicswap/templates/smsgaddresses.html
new file mode 100644
index 0000000..7e8efde
--- /dev/null
+++ b/basicswap/templates/smsgaddresses.html
@@ -0,0 +1,47 @@
+{% include 'header.html' %}
+
+
{{ m }}
+{% endfor %} + + + + +