diff --git a/.travis.yml b/.travis.yml
index 930b182..a433ea0 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -48,7 +48,7 @@ jobs:
       before_script:
       script:
         - PYTHONWARNINGS="ignore" flake8 --ignore=E501,F841,W503 --exclude=segwit_addr.py,key.py,messages_pb2.py,.eggs
-        - codespell --check-filenames --disable-colors --quiet-level=7 -S .git,.eggs,gitianpubkeys
+        - codespell --check-filenames --disable-colors --quiet-level=7 --ignore-words=tests/lint/spelling.ignore-words.txt -S .git,.eggs,gitianpubkeys,*.pyc
       after_success:
         - echo "End lint"
     - stage: test
diff --git a/basicswap/base.py b/basicswap/base.py
new file mode 100644
index 0000000..198b572
--- /dev/null
+++ b/basicswap/base.py
@@ -0,0 +1,115 @@
+# -*- 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 threading
+import logging
+import subprocess
+
+import basicswap.config as cfg
+
+from .chainparams import (
+    chainparams,
+    Coins,
+)
+from .util import (
+    callrpc,
+)
+
+
+class BaseApp:
+    def __init__(self, fp, data_dir, settings, chain, log_name='BasicSwap'):
+        self.log_name = log_name
+        self.fp = fp
+        self.is_running = True
+        self.fail_code = 0
+
+        self.data_dir = data_dir
+        self.chain = chain
+        self.settings = settings
+        self.coin_clients = {}
+        self.mxDB = threading.RLock()
+        self.debug = self.settings.get('debug', cfg.DEBUG)
+
+        self.prepareLogging()
+        self.log.info('Network: {}'.format(self.chain))
+
+    def stopRunning(self, with_code=0):
+        self.fail_code = with_code
+        self.is_running = False
+
+    def prepareLogging(self):
+        self.log = logging.getLogger(self.log_name)
+        self.log.propagate = False
+
+        formatter = logging.Formatter('%(asctime)s %(levelname)s : %(message)s')
+        stream_stdout = logging.StreamHandler()
+        if self.log_name != 'BasicSwap':
+            stream_stdout.setFormatter(logging.Formatter('%(asctime)s %(name)s %(levelname)s : %(message)s'))
+        else:
+            stream_stdout.setFormatter(formatter)
+        stream_fp = logging.StreamHandler(self.fp)
+        stream_fp.setFormatter(formatter)
+
+        self.log.setLevel(logging.DEBUG if self.debug else logging.INFO)
+        self.log.addHandler(stream_fp)
+        self.log.addHandler(stream_stdout)
+
+    def getChainClientSettings(self, coin):
+        try:
+            return self.settings['chainclients'][chainparams[coin]['name']]
+        except Exception:
+            return {}
+
+    def setDaemonPID(self, name, pid):
+        if isinstance(name, Coins):
+            self.coin_clients[name]['pid'] = pid
+            return
+        for c, v in self.coin_clients.items():
+            if v['name'] == name:
+                v['pid'] = pid
+
+    def getChainDatadirPath(self, coin):
+        datadir = self.coin_clients[coin]['datadir']
+        testnet_name = '' if self.chain == 'mainnet' else chainparams[coin][self.chain].get('name', self.chain)
+        return os.path.join(datadir, testnet_name)
+
+    def getTicker(self, coin_type):
+        ticker = chainparams[coin_type]['ticker']
+        if self.chain == 'testnet':
+            ticker = 't' + ticker
+        if self.chain == 'regtest':
+            ticker = 'rt' + ticker
+        return ticker
+
+    def callrpc(self, method, params=[], wallet=None):
+        return callrpc(self.coin_clients[Coins.PART]['rpcport'], self.coin_clients[Coins.PART]['rpcauth'], method, params, wallet)
+
+    def callcoinrpc(self, coin, method, params=[], wallet=None):
+        return callrpc(self.coin_clients[coin]['rpcport'], self.coin_clients[coin]['rpcauth'], method, params, wallet)
+
+    def calltx(self, cmd):
+        bindir = self.coin_clients[Coins.PART]['bindir']
+        command_tx = os.path.join(bindir, cfg.PARTICL_TX)
+        chainname = '' if self.chain == 'mainnet' else (' -' + self.chain)
+        args = command_tx + chainname + ' ' + cmd
+        p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
+        out = p.communicate()
+        if len(out[1]) > 0:
+            raise ValueError('TX error ' + str(out[1]))
+        return out[0].decode('utf-8').strip()
+
+    def callcoincli(self, coin_type, params, wallet=None, timeout=None):
+        bindir = self.coin_clients[coin_type]['bindir']
+        datadir = self.coin_clients[coin_type]['datadir']
+        command_cli = os.path.join(bindir, chainparams[coin_type]['name'] + '-cli' + ('.exe' if os.name == 'nt' else ''))
+        chainname = '' if self.chain == 'mainnet' else (' -' + self.chain)
+        args = command_cli + chainname + ' ' + '-datadir=' + datadir + ' ' + params
+        p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
+        out = p.communicate(timeout=timeout)
+        if len(out[1]) > 0:
+            raise ValueError('CLI error ' + str(out[1]))
+        return out[0].decode('utf-8').strip()
diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py
index cc33bc4..d8e9714 100644
--- a/basicswap/basicswap.py
+++ b/basicswap/basicswap.py
@@ -9,11 +9,8 @@ import re
 import time
 import datetime as dt
 import zmq
-import threading
 import traceback
 import hashlib
-import subprocess
-import logging
 import sqlalchemy as sa
 import shutil
 import json
@@ -25,7 +22,6 @@ from enum import IntEnum, auto
 from . import __version__
 from .util import (
     COIN,
-    callrpc,
     pubkeyToAddress,
     format8,
     encodeAddress,
@@ -63,9 +59,7 @@ from .db import (
 from .explorers import ExplorerInsight, ExplorerBitAps, ExplorerChainz
 import basicswap.config as cfg
 import basicswap.segwit_addr as segwit_addr
-
-
-DEBUG = True
+from .base import BaseApp
 
 
 MIN_OFFER_VALID_TIME = 60 * 10
@@ -303,19 +297,9 @@ def replaceAddrPrefix(addr, coin_type, chain_name, addr_type='pubkey_address'):
     return encodeAddress(bytes((chainparams[coin_type][chain_name][addr_type],)) + decodeAddress(addr)[1:])
 
 
-class BasicSwap():
+class BasicSwap(BaseApp):
     def __init__(self, fp, data_dir, settings, chain, log_name='BasicSwap'):
-        self.log_name = log_name
-        self.fp = fp
-        self.is_running = True
-        self.fail_code = 0
-
-        self.data_dir = data_dir
-        self.chain = chain
-        self.settings = settings
-        self.coin_clients = {}
-        self.mxDB = threading.RLock()
-        self.debug = self.settings.get('debug', DEBUG)
+        super().__init__(fp, data_dir, settings, chain, log_name)
 
         self.check_progress_seconds = self.settings.get('check_progress_seconds', 60)
         self.check_watched_seconds = self.settings.get('check_watched_seconds', 60)
@@ -339,9 +323,6 @@ class BasicSwap():
             self.SMSG_SECONDS_IN_DAY = 86400
             self.SMSG_SECONDS_IN_HOUR = 60 * 60
 
-        self.prepareLogging()
-        self.log.info('Network: {}'.format(self.chain))
-
         # Encode key to match network
         wif_prefix = chainparams[Coins.PART][self.chain]['key_prefix']
         self.network_key = toWIF(wif_prefix, decodeWif(self.settings['network_key']))
@@ -407,29 +388,6 @@ class BasicSwap():
 
         random.seed(secrets.randbits(128))
 
-    def prepareLogging(self):
-        self.log = logging.getLogger(self.log_name)
-        self.log.propagate = False
-
-        formatter = logging.Formatter('%(asctime)s %(levelname)s : %(message)s')
-        stream_stdout = logging.StreamHandler()
-        if self.log_name != 'BasicSwap':
-            stream_stdout.setFormatter(logging.Formatter('%(asctime)s %(name)s %(levelname)s : %(message)s'))
-        else:
-            stream_stdout.setFormatter(formatter)
-        stream_fp = logging.StreamHandler(self.fp)
-        stream_fp.setFormatter(formatter)
-
-        self.log.setLevel(logging.DEBUG if self.debug else logging.INFO)
-        self.log.addHandler(stream_fp)
-        self.log.addHandler(stream_stdout)
-
-    def getChainClientSettings(self, coin):
-        try:
-            return self.settings['chainclients'][chainparams[coin]['name']]
-        except Exception:
-            return {}
-
     def setCoinConnectParams(self, coin):
         # Set anything that does not require the daemon to be running
         chain_client_settings = self.getChainClientSettings(coin)
@@ -476,19 +434,6 @@ class BasicSwap():
             'chain_lookups': chain_client_settings.get('chain_lookups', 'local'),
         }
 
-    def setDaemonPID(self, name, pid):
-        if isinstance(name, Coins):
-            self.coin_clients[name]['pid'] = pid
-            return
-        for c, v in self.coin_clients.items():
-            if v['name'] == name:
-                v['pid'] = pid
-
-    def getChainDatadirPath(self, coin):
-        datadir = self.coin_clients[coin]['datadir']
-        testnet_name = '' if self.chain == 'mainnet' else chainparams[coin][self.chain].get('name', self.chain)
-        return os.path.join(datadir, testnet_name)
-
     def setCoinRunParams(self, coin):
         cc = self.coin_clients[coin]
         if cc['connection_type'] == 'rpc' and cc['rpcauth'] is None:
@@ -573,10 +518,6 @@ class BasicSwap():
             if self.coin_clients[c]['connection_type'] == 'rpc' and chain_client_settings['manage_daemon'] is True:
                 self.stopDaemon(c)
 
-    def stopRunning(self, with_code=0):
-        self.fail_code = with_code
-        self.is_running = False
-
     def upgradeDatabase(self, db_version):
         if db_version >= CURRENT_DB_VERSION:
             return
@@ -930,14 +871,6 @@ class BasicSwap():
             except Exception:
                 return self.callcoinrpc(coin_type, 'getnetworkinfo')['relayfee']
 
-    def getTicker(self, coin_type):
-        ticker = chainparams[coin_type]['ticker']
-        if self.chain == 'testnet':
-            ticker = 't' + ticker
-        if self.chain == 'regtest':
-            ticker = 'rt' + ticker
-        return ticker
-
     def withdrawCoin(self, coin_type, value, addr_to, subfee):
         self.log.info('withdrawCoin %s %s to %s %s', value, self.getTicker(coin_type), addr_to, ' subfee' if subfee else '')
         params = [addr_to, value, '', '', subfee, True, self.coin_clients[coin_type]['conf_target']]
@@ -2670,32 +2603,3 @@ class BasicSwap():
             session.close()
             session.remove()
             self.mxDB.release()
-
-    def callrpc(self, method, params=[], wallet=None):
-        return callrpc(self.coin_clients[Coins.PART]['rpcport'], self.coin_clients[Coins.PART]['rpcauth'], method, params, wallet)
-
-    def callcoinrpc(self, coin, method, params=[], wallet=None):
-        return callrpc(self.coin_clients[coin]['rpcport'], self.coin_clients[coin]['rpcauth'], method, params, wallet)
-
-    def calltx(self, cmd):
-        bindir = self.coin_clients[Coins.PART]['bindir']
-        command_tx = os.path.join(bindir, cfg.PARTICL_TX)
-        chainname = '' if self.chain == 'mainnet' else (' -' + self.chain)
-        args = command_tx + chainname + ' ' + cmd
-        p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
-        out = p.communicate()
-        if len(out[1]) > 0:
-            raise ValueError('TX error ' + str(out[1]))
-        return out[0].decode('utf-8').strip()
-
-    def callcoincli(self, coin_type, params, wallet=None, timeout=None):
-        bindir = self.coin_clients[coin_type]['bindir']
-        datadir = self.coin_clients[coin_type]['datadir']
-        command_cli = os.path.join(bindir, chainparams[coin_type]['name'] + '-cli' + ('.exe' if os.name == 'nt' else ''))
-        chainname = '' if self.chain == 'mainnet' else (' -' + self.chain)
-        args = command_cli + chainname + ' ' + '-datadir=' + datadir + ' ' + params
-        p = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
-        out = p.communicate(timeout=timeout)
-        if len(out[1]) > 0:
-            raise ValueError('CLI error ' + str(out[1]))
-        return out[0].decode('utf-8').strip()
diff --git a/basicswap/config.py b/basicswap/config.py
index b531b98..0abf38c 100644
--- a/basicswap/config.py
+++ b/basicswap/config.py
@@ -6,6 +6,8 @@
 
 import os
 
+DEBUG = True
+
 DATADIRS = os.path.expanduser(os.getenv('DATADIRS', '/tmp/basicswap'))
 
 PARTICL_BINDIR = os.path.expanduser(os.getenv('PARTICL_BINDIR', ''))
diff --git a/tests/basicswap/test_run.py b/tests/basicswap/test_run.py
index 15d2922..657a6cc 100644
--- a/tests/basicswap/test_run.py
+++ b/tests/basicswap/test_run.py
@@ -598,7 +598,7 @@ class Test(unittest.TestCase):
 
         self.wait_for_offer(swap_clients[1], offer_id)
         offers = swap_clients[1].listOffers()
-        assert(len(offers) == 1)
+        assert(len(offers) >= 1)
         for offer in offers:
             if offer.offer_id == offer_id:
                 bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
diff --git a/tests/lint/spelling.ignore-words.txt b/tests/lint/spelling.ignore-words.txt
new file mode 100644
index 0000000..75835e2
--- /dev/null
+++ b/tests/lint/spelling.ignore-words.txt
@@ -0,0 +1,2 @@
+eventtypes
+