diff --git a/.travis.yml b/.travis.yml
index 03f8b87..e98ca0d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -39,7 +39,7 @@ jobs:
- travis_retry pip install codespell==1.15.0
before_script:
script:
- - PYTHONWARNINGS="ignore" flake8 --ignore=E501,F841 --exclude=key.py,messages_pb2.py
- - codespell --check-filenames --disable-colors --quiet-level=7 -S .git
+ - PYTHONWARNINGS="ignore" flake8 --ignore=E501,F841 --exclude=key.py,messages_pb2.py,.eggs
+ - codespell --check-filenames --disable-colors --quiet-level=7 -S .git,.eggs
after_success:
- echo "End lint"
diff --git a/README.md b/README.md
index 921ca81..e61885f 100644
--- a/README.md
+++ b/README.md
@@ -24,6 +24,7 @@ Features still required (of many):
- Ability to swap coin-types without running nodes for all coin-types
- More swap protocols
- Method to load mnemonic into Particl.
+ - COIN must be defined per coin.
## Seller first protocol:
diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py
index a635241..657b9f4 100644
--- a/basicswap/basicswap.py
+++ b/basicswap/basicswap.py
@@ -351,6 +351,7 @@ class Bid(Base):
initiate_spend_txid = sa.Column(sa.LargeBinary)
initiate_spend_n = sa.Column(sa.Integer)
+ initiate_txn_height = sa.Column(sa.Integer)
initiate_txn_state = sa.Column(sa.Integer)
initiate_txn_states = sa.Column(sa.LargeBinary) # Packed states and times
@@ -364,6 +365,7 @@ class Bid(Base):
participate_spend_txid = sa.Column(sa.LargeBinary)
participate_spend_n = sa.Column(sa.Integer)
+ participate_txn_height = sa.Column(sa.Integer)
participate_txn_state = sa.Column(sa.Integer)
participate_txn_states = sa.Column(sa.LargeBinary) # Packed states and times
@@ -371,6 +373,8 @@ class Bid(Base):
state_time = sa.Column(sa.BigInteger) # timestamp of last state change
states = sa.Column(sa.LargeBinary) # Packed states and times
+ state_note = sa.Column(sa.String)
+
def setITXState(self, new_state):
self.initiate_txn_state = new_state
if self.initiate_txn_states is None:
@@ -1438,6 +1442,8 @@ class BasicSwap():
self.coin_clients[coin_type]['last_height_checked'] = tx_height
self.log.debug('Rewind checking of %s chain to height %d', chain_name, tx_height)
+ return tx_height
+
def addParticipateTxn(self, bid_id, bid, coin_type, txid_hex, vout, tx_height):
bid.participate_txid = bytes.fromhex(txid_hex)
bid.participate_txn_n = vout
@@ -1447,7 +1453,7 @@ class BasicSwap():
self.log.debug('Watching %s chain for spend of output %s %d', chain_name, txid_hex, vout)
# TODO: Check connection type
- self.setLastHeightChecked(coin_type, tx_height)
+ bid.participate_txn_height = self.setLastHeightChecked(coin_type, tx_height)
self.log.debug('Adding watched output %s bid %s tx %s type %s', coin_type, bid_id.hex(), txid_hex, BidStates.SWAP_PARTICIPATING)
self.coin_clients[coin_type]['watched_outputs'].append((bid_id, txid_hex, vout, BidStates.SWAP_PARTICIPATING))
@@ -1556,12 +1562,12 @@ class BasicSwap():
if bid.initiate_txn_n is None:
bid.initiate_txn_n = index
# Start checking for spends of initiate_txn before fully confirmed
- self.setLastHeightChecked(coin_from, tx_height)
+ bid.initiate_txn_height = self.setLastHeightChecked(coin_from, tx_height)
self.log.debug('Adding watched output %s bid %s tx %s type %s', coin_from, bid_id.hex(), initiate_txnid_hex, BidStates.SWAP_INITIATED)
self.coin_clients[coin_from]['watched_outputs'].append((bid_id, initiate_txnid_hex, bid.initiate_txn_n, BidStates.SWAP_INITIATED))
if bid.initiate_txn_state is None or bid.initiate_txn_state < TxStates.TX_SENT:
bid.setITXState(TxStates.TX_SENT)
- save_bid = True
+ save_bid = True
if bid.initiate_txn_conf >= self.coin_clients[coin_from]['blocks_confirmed']:
self.initiateTxnConfirmed(bid_id, bid, offer)
@@ -2112,7 +2118,7 @@ class BasicSwap():
'deposit_address': self.getCachedAddressForCoin(coin),
'name': chainparams[coin]['name'].capitalize(),
'blocks': blockchaininfo['blocks'],
- 'balance': walletinfo.get('total_balance', walletinfo['balance']),
+ 'balance': format8(walletinfo.get('total_balance', walletinfo['balance']) * COIN),
'synced': '{0:.2f}'.format(round(blockchaininfo['verificationprogress'], 2)),
}
return rv
diff --git a/basicswap/config.py b/basicswap/config.py
index 78b88af..00b569f 100644
--- a/basicswap/config.py
+++ b/basicswap/config.py
@@ -9,16 +9,16 @@ import os
DATADIRS = os.path.expanduser(os.getenv('DATADIRS', '/tmp/basicswap'))
PARTICL_BINDIR = os.path.expanduser(os.getenv('PARTICL_BINDIR', ''))
-PARTICLD = os.getenv('PARTICLD', 'particld')
-PARTICL_CLI = os.getenv('PARTICL_CLI', 'particl-cli')
-PARTICL_TX = os.getenv('PARTICL_TX', 'particl-tx')
+PARTICLD = os.getenv('PARTICLD', 'particld' + ('.exe' if os.name == 'nt' else ''))
+PARTICL_CLI = os.getenv('PARTICL_CLI', 'particl-cli' + ('.exe' if os.name == 'nt' else ''))
+PARTICL_TX = os.getenv('PARTICL_TX', 'particl-tx' + ('.exe' if os.name == 'nt' else ''))
BITCOIN_BINDIR = os.path.expanduser(os.getenv('BITCOIN_BINDIR', ''))
-BITCOIND = os.getenv('BITCOIND', 'bitcoind')
-BITCOIN_CLI = os.getenv('BITCOIN_CLI', 'bitcoin-cli')
-BITCOIN_TX = os.getenv('BITCOIN_TX', 'bitcoin-tx')
+BITCOIND = os.getenv('BITCOIND', 'bitcoind' + ('.exe' if os.name == 'nt' else ''))
+BITCOIN_CLI = os.getenv('BITCOIN_CLI', 'bitcoin-cli' + ('.exe' if os.name == 'nt' else ''))
+BITCOIN_TX = os.getenv('BITCOIN_TX', 'bitcoin-tx' + ('.exe' if os.name == 'nt' else ''))
LITECOIN_BINDIR = os.path.expanduser(os.getenv('LITECOIN_BINDIR', ''))
-LITECOIND = os.getenv('LITECOIND', 'litecoind')
-LITECOIN_CLI = os.getenv('LITECOIN_CLI', 'litecoin-cli')
-LITECOIN_TX = os.getenv('LITECOIN_TX', 'litecoin-tx')
+LITECOIND = os.getenv('LITECOIND', 'litecoind' + ('.exe' if os.name == 'nt' else ''))
+LITECOIN_CLI = os.getenv('LITECOIN_CLI', 'litecoin-cli' + ('.exe' if os.name == 'nt' else ''))
+LITECOIN_TX = os.getenv('LITECOIN_TX', 'litecoin-tx' + ('.exe' if os.name == 'nt' else ''))
diff --git a/basicswap/http_server.py b/basicswap/http_server.py
index aa4fba6..d73f567 100644
--- a/basicswap/http_server.py
+++ b/basicswap/http_server.py
@@ -130,7 +130,7 @@ class HttpHandler(BaseHTTPRequestHandler):
cid = str(int(k))
content += '
' + w['name'] + '
' \
+ '' \
- + '| Balance: | ' + str(w['balance']) + ' |
' \
+ + '| Balance: | ' + w['balance'] + ' |
' \
+ '| Blocks: | ' + str(w['blocks']) + ' |
' \
+ '| Synced: | ' + str(w['synced']) + ' |
' \
+ ' | ' + str(w['deposit_address']) + ' |
' \
diff --git a/bin/basicswap-prepare.py b/bin/basicswap-prepare.py
new file mode 100644
index 0000000..ed0fb6e
--- /dev/null
+++ b/bin/basicswap-prepare.py
@@ -0,0 +1,117 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2019 tecnovert
+# Distributed under the MIT software license, see the accompanying
+# file LICENSE.txt or http://www.opensource.org/licenses/mit-license.php.
+
+"""
+Particl Atomic Swap - Proof of Concept
+
+sudo pip install python-gnupg
+
+"""
+
+import sys
+import os
+import subprocess
+import time
+import json
+import hashlib
+import mmap
+import tarfile
+import urllib.request
+import urllib.parse
+import logging
+
+import gnupg
+
+
+logger = logging.getLogger()
+logger.level = logging.DEBUG
+
+
+def printVersion():
+ from basicswap import __version__
+ logger.info('Basicswap version:', __version__)
+
+
+def printHelp():
+ logger.info('Usage: basicswap-prepare ')
+ logger.info('\n--help, -h Print help.')
+ logger.info('\n--version, -v Print version.')
+ logger.info('\n--datadir=PATH Path to basicswap data directory, default:~/.basicswap.')
+ logger.info('\n--mainnet Run in mainnet mode.')
+ logger.info('\n--testnet Run in testnet mode.')
+ logger.info('\n--regtest Run in regtest mode.')
+ logger.info('\n--particl_mnemonic= Recovery phrase to use for the Particl wallet, default is randomly generated.')
+
+
+def main():
+ print('main')
+ data_dir = None
+ chain = 'mainnet'
+ particl_wallet_mnemonic = None
+
+ for v in sys.argv[1:]:
+ if len(v) < 2 or v[0] != '-':
+ logger.warning('Unknown argument', v)
+ continue
+
+ s = v.split('=')
+ name = s[0].strip()
+
+ for i in range(2):
+ if name[0] == '-':
+ name = name[1:]
+
+ if name == 'v' or name == 'version':
+ printVersion()
+ return 0
+ if name == 'h' or name == 'help':
+ printHelp()
+ return 0
+ if name == 'mainnet':
+ continue
+ if name == 'testnet':
+ chain = 'testnet'
+ continue
+ if name == 'regtest':
+ chain = 'regtest'
+ continue
+
+ if len(s) == 2:
+ if name == 'datadir':
+ data_dir = os.path.expanduser(s[1])
+ continue
+ if name == 'particl_mnemonic':
+ particl_wallet_mnemonic = s[1]
+ continue
+
+ logger.warning('Unknown argument', v)
+
+ if data_dir is None:
+ default_datadir = '~/.basicswap'
+ data_dir = os.path.join(os.path.expanduser(default_datadir))
+ logger.info('Using datadir: %s', data_dir)
+ logger.info('Chain: %s', chain)
+
+ if not os.path.exists(data_dir):
+ os.makedirs(data_dir)
+
+ config_path = os.path.join(data_dir, 'basicswap.json')
+ if os.path.exists(config_path):
+ sys.stderr.write('Error: {} exists, exiting.'.format(config_path))
+ exit(1)
+
+ settings = {
+ 'debug': True,
+ }
+
+ with open(config_path, 'w') as fp:
+ json.dump(settings, fp, indent=4)
+
+ logger.info('Done.')
+
+if __name__ == '__main__':
+ main()
diff --git a/bin/basicswap-run.py b/bin/basicswap-run.py
deleted file mode 120000
index 0bf5b17..0000000
--- a/bin/basicswap-run.py
+++ /dev/null
@@ -1 +0,0 @@
-basicswap_run.py
\ No newline at end of file
diff --git a/bin/basicswap-run.py b/bin/basicswap-run.py
new file mode 100644
index 0000000..cccf364
--- /dev/null
+++ b/bin/basicswap-run.py
@@ -0,0 +1,181 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2019 tecnovert
+# Distributed under the MIT software license, see the accompanying
+# file LICENSE.txt or http://www.opensource.org/licenses/mit-license.php.
+
+"""
+Particl Atomic Swap - Proof of Concept
+
+Dependencies:
+ $ pacman -S python-pyzmq python-plyvel protobuf
+
+"""
+
+import sys
+import os
+import time
+import json
+import traceback
+import signal
+import subprocess
+import logging
+
+import basicswap.config as cfg
+from basicswap import __version__
+from basicswap.basicswap import BasicSwap
+from basicswap.http_server import HttpThread
+
+
+logger = logging.getLogger()
+logger.level = logging.DEBUG
+logger.addHandler(logging.StreamHandler(sys.stdout))
+
+ALLOW_CORS = False
+swap_client = None
+
+
+def signal_handler(sig, frame):
+ logger.info('Signal %d detected, ending program.' % (sig))
+ if swap_client is not None:
+ swap_client.stopRunning()
+
+
+def startDaemon(node_dir, bin_dir, daemon_bin):
+ daemon_bin = os.path.join(bin_dir, daemon_bin)
+
+ args = [daemon_bin, '-datadir=' + node_dir]
+ logger.info('Starting node ' + daemon_bin + ' ' + '-datadir=' + node_dir)
+ return subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+
+def runClient(fp, dataDir, chain):
+ global swap_client
+ settings_path = os.path.join(dataDir, 'basicswap.json')
+
+ if not os.path.exists(settings_path):
+ raise ValueError('Settings file not found: ' + str(settings_path))
+
+ with open(settings_path) as fs:
+ settings = json.load(fs)
+
+ daemons = []
+
+ for c, v in settings['chainclients'].items():
+ if v['manage_daemon'] is True:
+ logger.info('Starting {} daemon'.format(c.capitalize()))
+ if c == 'particl':
+ daemons.append(startDaemon(v['datadir'], cfg.PARTICL_BINDIR, cfg.PARTICLD))
+ logger.info('Started {} {}'.format(cfg.PARTICLD, daemons[-1].pid))
+ elif c == 'bitcoin':
+ daemons.append(startDaemon(v['datadir'], cfg.BITCOIN_BINDIR, cfg.BITCOIND))
+ logger.info('Started {} {}'.format(cfg.BITCOIND, daemons[-1].pid))
+ elif c == 'litecoin':
+ daemons.append(startDaemon(v['datadir'], cfg.LITECOIN_BINDIR, cfg.LITECOIND))
+ logger.info('Started {} {}'.format(cfg.LITECOIND, daemons[-1].pid))
+ else:
+ logger.warning('Unknown chain', c)
+
+ swap_client = BasicSwap(fp, dataDir, settings, chain)
+
+ signal.signal(signal.SIGINT, signal_handler)
+ signal.signal(signal.SIGTERM, signal_handler)
+ swap_client.start()
+
+ threads = []
+ if 'htmlhost' in settings:
+ swap_client.log.info('Starting server at %s:%d.' % (settings['htmlhost'], settings['htmlport']))
+ allow_cors = settings['allowcors'] if 'allowcors' in settings else ALLOW_CORS
+ tS1 = HttpThread(fp, settings['htmlhost'], settings['htmlport'], allow_cors, swap_client)
+ threads.append(tS1)
+ tS1.start()
+
+ try:
+ logger.info('Exit with Ctrl + c.')
+ while swap_client.is_running:
+ time.sleep(0.5)
+ swap_client.update()
+ except Exception:
+ traceback.print_exc()
+
+ swap_client.log.info('Stopping threads.')
+ for t in threads:
+ t.stop()
+ t.join()
+
+ for d in daemons:
+ logger.info('Terminating {}'.format(d.pid))
+ d.terminate()
+ d.wait(timeout=120)
+ if d.stdout:
+ d.stdout.close()
+ if d.stderr:
+ d.stderr.close()
+ if d.stdin:
+ d.stdin.close()
+
+
+def printVersion():
+ logger.info('Basicswap version:', __version__)
+
+
+def printHelp():
+ logger.info('basicswap-run.py --datadir=path -testnet')
+
+
+def main():
+ data_dir = None
+ chain = 'mainnet'
+
+ for v in sys.argv[1:]:
+ if len(v) < 2 or v[0] != '-':
+ logger.warning('Unknown argument', v)
+ continue
+
+ s = v.split('=')
+ name = s[0].strip()
+
+ for i in range(2):
+ if name[0] == '-':
+ name = name[1:]
+
+ if name == 'v' or name == 'version':
+ printVersion()
+ return 0
+ if name == 'h' or name == 'help':
+ printHelp()
+ return 0
+ if name == 'testnet':
+ chain = 'testnet'
+ continue
+ if name == 'regtest':
+ chain = 'regtest'
+ continue
+
+ if len(s) == 2:
+ if name == 'datadir':
+ data_dir = os.path.expanduser(s[1])
+ continue
+
+ logger.warning('Unknown argument', v)
+
+ if data_dir is None:
+ default_datadir = '~/.basicswap'
+ data_dir = os.path.join(os.path.expanduser(default_datadir))
+ logger.info('Using datadir:', data_dir)
+ logger.info('Chain:', chain)
+
+ if not os.path.exists(data_dir):
+ os.makedirs(data_dir)
+
+ with open(os.path.join(data_dir, 'basicswap.log'), 'a') as fp:
+ logger.info(os.path.basename(sys.argv[0]) + ', version: ' + __version__ + '\n\n')
+ runClient(fp, data_dir, chain)
+
+ logger.info('Done.')
+ return swap_client.fail_code if swap_client is not None else 0
+
+
+if __name__ == '__main__':
+ main()
diff --git a/bin/basicswap_run.py b/bin/basicswap_run.py
deleted file mode 100644
index b79a670..0000000
--- a/bin/basicswap_run.py
+++ /dev/null
@@ -1,182 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-# Copyright (c) 2019 tecnovert
-# Distributed under the MIT software license, see the accompanying
-# file LICENSE.txt or http://www.opensource.org/licenses/mit-license.php.
-
-"""
-Particl Atomic Swap - Proof of Concept
-
-Dependencies:
- $ pacman -S python-pyzmq python-plyvel protobuf
-
-"""
-
-import sys
-import os
-import time
-import json
-import traceback
-import signal
-import subprocess
-import logging
-
-import basicswap.config as cfg
-from basicswap import __version__
-from basicswap.basicswap import BasicSwap
-from basicswap.http_server import HttpThread
-
-
-logger = logging.getLogger()
-logger.level = logging.DEBUG
-logger.addHandler(logging.StreamHandler(sys.stdout))
-
-ALLOW_CORS = False
-swap_client = None
-
-
-def signal_handler(sig, frame):
- logger.info('Signal %d detected, ending program.' % (sig))
- if swap_client is not None:
- swap_client.stopRunning()
-
-
-def startDaemon(node_dir, bin_dir, daemon_bin):
- daemon_bin = os.path.join(bin_dir, daemon_bin)
-
- args = [daemon_bin, '-datadir=' + node_dir]
- logger.info('Starting node ' + daemon_bin + ' ' + '-datadir=' + node_dir)
- return subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-
-
-def runClient(fp, dataDir, chain):
- global swap_client
- settings_path = os.path.join(dataDir, 'basicswap.json')
-
- if not os.path.exists(settings_path):
- raise ValueError('Settings file not found: ' + str(settings_path))
-
- with open(settings_path) as fs:
- settings = json.load(fs)
-
- daemons = []
-
- for c, v in settings['chainclients'].items():
- if v['manage_daemon'] is True:
- logger.info('Starting {} daemon'.format(c.capitalize()))
- if c == 'particl':
- daemons.append(startDaemon(v['datadir'], cfg.PARTICL_BINDIR, cfg.PARTICLD))
- logger.info('Started {} {}'.format(cfg.PARTICLD, daemons[-1].pid))
- elif c == 'bitcoin':
- daemons.append(startDaemon(v['datadir'], cfg.BITCOIN_BINDIR, cfg.BITCOIND))
- logger.info('Started {} {}'.format(cfg.BITCOIND, daemons[-1].pid))
- elif c == 'litecoin':
- daemons.append(startDaemon(v['datadir'], cfg.LITECOIN_BINDIR, cfg.LITECOIND))
- logger.info('Started {} {}'.format(cfg.LITECOIND, daemons[-1].pid))
- else:
- logger.warning('Unknown chain', c)
-
- swap_client = BasicSwap(fp, dataDir, settings, chain)
-
- signal.signal(signal.SIGINT, signal_handler)
- signal.signal(signal.SIGTERM, signal_handler)
- swap_client.start()
-
- threads = []
- if 'htmlhost' in settings:
- swap_client.log.info('Starting server at %s:%d.' % (settings['htmlhost'], settings['htmlport']))
- allow_cors = settings['allowcors'] if 'allowcors' in settings else ALLOW_CORS
- tS1 = HttpThread(fp, settings['htmlhost'], settings['htmlport'], allow_cors, swap_client)
- threads.append(tS1)
- tS1.start()
-
- try:
- logger.info('Exit with Ctrl + c.')
- while swap_client.is_running:
- time.sleep(0.5)
- swap_client.update()
- except Exception:
- traceback.print_exc()
-
- swap_client.log.info('Stopping threads.')
- for t in threads:
- t.stop()
- t.join()
-
- for d in daemons:
- logger.info('Terminating {}'.format(d.pid))
- d.terminate()
- d.wait(timeout=120)
- if d.stdout:
- d.stdout.close()
- if d.stderr:
- d.stderr.close()
- if d.stdin:
- d.stdin.close()
-
-
-def printVersion():
- logger.info('Basicswap version:', __version__)
-
-
-def printHelp():
- logger.info('basicswap-run.py --datadir=path -testnet')
-
-
-def main():
- data_dir = None
- chain = 'mainnet'
-
- for v in sys.argv[1:]:
- if len(v) < 2 or v[0] != '-':
- logger.warning('Unknown argument', v)
- continue
-
- s = v.split('=')
- name = s[0].strip()
-
- for i in range(2):
- if name[0] == '-':
- name = name[1:]
-
- if name == 'v' or name == 'version':
- printVersion()
- return 0
- if name == 'h' or name == 'help':
- printHelp()
- return 0
- if name == 'testnet':
- chain = 'testnet'
- continue
- if name == 'regtest':
- chain = 'regtest'
- continue
-
- if len(s) == 2:
- if name == 'datadir':
- data_dir = os.path.expanduser(s[1])
- continue
-
- logger.warning('Unknown argument', v)
-
- if data_dir is None:
- data_dir = os.path.join(os.path.expanduser(os.path.join(cfg.DATADIRS)), 'particl', ('' if chain == 'mainnet' else chain), 'basicswap')
-
- print('data_dir:', data_dir)
- if chain != 'mainnet':
- logger.info('chain:', chain)
-
- if not os.path.exists(data_dir):
- os.makedirs(data_dir)
-
- with open(os.path.join(data_dir, 'basicswap.log'), 'a') as fp:
- logger.info(os.path.basename(sys.argv[0]) + ', version: ' + __version__ + '\n\n')
- runClient(fp, data_dir, chain)
-
- logger.info('Done.')
- return swap_client.fail_code if swap_client is not None else 0
-
-
-if __name__ == '__main__':
- main()
diff --git a/bin/start_docker.bat b/bin/start_docker.bat
deleted file mode 100644
index e69de29..0000000
diff --git a/setup.py b/setup.py
index 2d87b73..28ce73f 100644
--- a/setup.py
+++ b/setup.py
@@ -26,6 +26,7 @@ setuptools.setup(
"pyzmq",
"protobuf",
"sqlalchemy",
+ "python-gnupg",
],
entry_points={
"console_scripts": [
diff --git a/tests/__init__.py b/tests/__init__.py
index 84e7855..48ce776 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1,11 +1,14 @@
import unittest
-import tests.test_run
import tests.test_other
+import tests.test_prepare
+import tests.test_run
def test_suite():
loader = unittest.TestLoader()
- suite = loader.loadTestsFromModule(tests.test_run)
suite.addTests(loader.loadTestsFromModule(tests.test_other))
+ suite.addTests(loader.loadTestsFromModule(tests.test_prepare))
+ suite = loader.loadTestsFromModule(tests.test_run)
+
return suite
diff --git a/tests/test_prepare.py b/tests/test_prepare.py
new file mode 100644
index 0000000..9c59f31
--- /dev/null
+++ b/tests/test_prepare.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# Copyright (c) 2019 tecnovert
+# Distributed under the MIT software license, see the accompanying
+# file LICENSE.txt or http://www.opensource.org/licenses/mit-license.php.
+
+import os
+import sys
+import unittest
+from unittest.mock import patch
+from io import StringIO
+import logging
+import shutil
+import importlib
+
+prepareSystem = importlib.import_module('bin.basicswap-prepare')
+test_path = os.path.expanduser('~/test_basicswap')
+
+logger = logging.getLogger()
+logger.level = logging.DEBUG
+
+
+class Test(unittest.TestCase):
+ @classmethod
+ def tearDownClass(self):
+ try:
+ shutil.rmtree(test_path)
+ except Exception as e:
+ logger.warning('tearDownClass %s', str(e))
+
+ def test_no_overwrite(self):
+ testargs = ['basicswap-prepare', '-datadir=' + test_path]
+ with patch.object(sys, 'argv', testargs):
+ prepareSystem.main()
+
+ self.assertTrue(os.path.exists(os.path.join(test_path, 'basicswap.json')))
+
+ testargs = ['basicswap-prepare', '-datadir=' + test_path]
+ with patch('sys.stderr', new=StringIO()) as fake_stderr:
+ with patch.object(sys, 'argv', testargs):
+ with self.assertRaises(SystemExit) as cm:
+ prepareSystem.main()
+
+ self.assertEqual(cm.exception.code, 1)
+ logger.info('fake_stderr.getvalue() %s', fake_stderr.getvalue())
+ self.assertTrue('exists, exiting' in fake_stderr.getvalue())
+
+
+if __name__ == '__main__':
+ unittest.main()