diff --git a/.travis.yml b/.travis.yml
index 066eaa74d..fd7d38db2 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -55,3 +55,29 @@ jobs:
             script: cd test; poetry run ./run_tests.py --interface=library
         -   name: With command line interface
             script: cd test; poetry run ./run_tests.py --interface=cli
+        -   name: With linux binary distribution command line interface
+            services: docker
+            before_script:
+                - docker build -t hwi-builder -f contrib/build.Dockerfile .
+            script:
+                - docker run -it --name hwi-builder -v $PWD:/opt/hwi --rm  --workdir /opt/hwi hwi-builder /bin/bash -c "contrib/build_bin.sh && contrib/build_dist.sh"
+                - sudo chown -R `whoami`:`whoami` dist/
+                - cd test; poetry run ./run_tests.py --interface=bindist
+                - cd ..; sha256sum dist/*
+        -   name: macOS binary distribution (no tests)
+            os: osx
+            osx_image: xcode7.3
+            language: generic
+            addons:
+                homebrew:
+                    packages:
+                        - libusb
+                        - pyenv
+                artifacts:
+                    working_dir: dist
+            install:
+                - brew update && brew upgrade pyenv
+                - cat contrib/reproducible-python.diff | PYTHON_CONFIGURE_OPTS="--enable-framework" BUILD_DATE="Jan  1 2019" BUILD_TIME="00:00:00" pyenv install -kp 3.6.8
+            script:
+                - contrib/build_bin.sh
+                - shasum -a 256 dist/*
diff --git a/test/run_tests.py b/test/run_tests.py
index a7afea153..18c43a370 100755
--- a/test/run_tests.py
+++ b/test/run_tests.py
@@ -32,7 +32,7 @@
 dbb_group.add_argument('--bitbox', help='Path to Digital bitbox simulator.', default='work/mcu/build/bin/simulator')
 
 parser.add_argument('--bitcoind', help='Path to bitcoind.', default='work/bitcoin/src/bitcoind')
-parser.add_argument('--interface', help='Which interface to send commands over', choices=['library', 'cli'], default='library')
+parser.add_argument('--interface', help='Which interface to send commands over', choices=['library', 'cli', 'bindist'], default='library')
 args = parser.parse_args()
 
 # Run tests
diff --git a/test/test_coldcard.py b/test/test_coldcard.py
index d11176573..85d7364ed 100755
--- a/test/test_coldcard.py
+++ b/test/test_coldcard.py
@@ -82,7 +82,7 @@ def test_pin(self):
     parser = argparse.ArgumentParser(description='Test Coldcard implementation')
     parser.add_argument('simulator', help='Path to the Coldcard simulator')
     parser.add_argument('bitcoind', help='Path to bitcoind binary')
-    parser.add_argument('--interface', help='Which interface to send commands over', choices=['library', 'cli'], default='library')
+    parser.add_argument('--interface', help='Which interface to send commands over', choices=['library', 'cli', 'bindist'], default='library')
     args = parser.parse_args()
 
     # Start bitcoind
diff --git a/test/test_device.py b/test/test_device.py
index 3f8b9bc67..03623ab63 100644
--- a/test/test_device.py
+++ b/test/test_device.py
@@ -83,6 +83,10 @@ def do_command(self, args):
             proc = subprocess.Popen(['hwi ' + ' '.join(args)], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, shell=True)
             result = proc.communicate()
             return json.loads(result[0].decode())
+        elif self.interface == 'bindist':
+            proc = subprocess.Popen(['../dist/hwi ' + ' '.join(args)], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, shell=True)
+            result = proc.communicate()
+            return json.loads(result[0].decode())
         else:
             return process_commands(args)
 
diff --git a/test/test_digitalbitbox.py b/test/test_digitalbitbox.py
index 5a65aa1f2..39e6b9710 100755
--- a/test/test_digitalbitbox.py
+++ b/test/test_digitalbitbox.py
@@ -137,7 +137,7 @@ def test_backup(self):
     parser = argparse.ArgumentParser(description='Test Digital Bitbox implementation')
     parser.add_argument('simulator', help='Path to simulator binary')
     parser.add_argument('bitcoind', help='Path to bitcoind binary')
-    parser.add_argument('--interface', help='Which interface to send commands over', choices=['library', 'cli'], default='library')
+    parser.add_argument('--interface', help='Which interface to send commands over', choices=['library', 'cli', 'bindist'], default='library')
     args = parser.parse_args()
 
     # Start bitcoind
diff --git a/test/test_keepkey.py b/test/test_keepkey.py
index 2525228a1..4b116aa47 100755
--- a/test/test_keepkey.py
+++ b/test/test_keepkey.py
@@ -86,6 +86,10 @@ def do_command(self, args):
             proc = subprocess.Popen(['hwi ' + ' '.join(args)], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, shell=True)
             result = proc.communicate()
             return json.loads(result[0].decode())
+        elif self.interface == 'bindist':
+            proc = subprocess.Popen(['../dist/hwi ' + ' '.join(args)], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, shell=True)
+            result = proc.communicate()
+            return json.loads(result[0].decode())
         else:
             return process_commands(args)
 
@@ -233,7 +237,7 @@ def keepkey_test_suite(emulator, rpc, userpass, interface):
     parser = argparse.ArgumentParser(description='Test Keepkey implementation')
     parser.add_argument('emulator', help='Path to the Keepkey emulator')
     parser.add_argument('bitcoind', help='Path to bitcoind binary')
-    parser.add_argument('--interface', help='Which interface to send commands over', choices=['library', 'cli'], default='library')
+    parser.add_argument('--interface', help='Which interface to send commands over', choices=['library', 'cli', 'bindist'], default='library')
     args = parser.parse_args()
 
     # Start bitcoind
diff --git a/test/test_ledger.py b/test/test_ledger.py
index e6233ff46..9c85f2ba8 100755
--- a/test/test_ledger.py
+++ b/test/test_ledger.py
@@ -84,7 +84,7 @@ def test_backup(self):
 if __name__ == '__main__':
     parser = argparse.ArgumentParser(description='Test Ledger implementation')
     parser.add_argument('bitcoind', help='Path to bitcoind binary')
-    parser.add_argument('--interface', help='Which interface to send commands over', choices=['library', 'cli'], default='library')
+    parser.add_argument('--interface', help='Which interface to send commands over', choices=['library', 'cli', 'bindist'], default='library')
     args = parser.parse_args()
 
     # Start bitcoind
diff --git a/test/test_trezor.py b/test/test_trezor.py
index d4e271ac0..d2abfe919 100755
--- a/test/test_trezor.py
+++ b/test/test_trezor.py
@@ -86,6 +86,10 @@ def do_command(self, args):
             proc = subprocess.Popen(['hwi ' + ' '.join(args)], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, shell=True)
             result = proc.communicate()
             return json.loads(result[0].decode())
+        elif self.interface == 'bindist':
+            proc = subprocess.Popen(['../dist/hwi ' + ' '.join(args)], stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, shell=True)
+            result = proc.communicate()
+            return json.loads(result[0].decode())
         else:
             return process_commands(args)
 
@@ -233,7 +237,7 @@ def trezor_test_suite(emulator, rpc, userpass, interface):
     parser = argparse.ArgumentParser(description='Test Trezor implementation')
     parser.add_argument('emulator', help='Path to the Trezor emulator')
     parser.add_argument('bitcoind', help='Path to bitcoind binary')
-    parser.add_argument('--interface', help='Which interface to send commands over', choices=['library', 'cli'], default='library')
+    parser.add_argument('--interface', help='Which interface to send commands over', choices=['library', 'cli', 'bindist'], default='library')
     args = parser.parse_args()
 
     # Start bitcoind