├── .devcontainer ├── Dockerfile └── devcontainer.json ├── .gitignore ├── .travis.yml ├── LICENSE.md ├── README.rst ├── docker ├── Dockerfile ├── Dockerfile.octopus └── Dockerfile.original ├── docs ├── Makefile ├── authenticated_client.rst ├── authenticated_client_usage.rst ├── clients.rst ├── conf.py ├── ethereum.rst ├── ethereum_usage.rst ├── images │ └── switcheo-logo.png ├── import.rst ├── index.rst ├── install.rst ├── make.bat ├── neo.rst ├── neo_usage.rst ├── overview.rst ├── public_client.rst ├── public_client_usage.rst ├── signatures.rst └── usage.rst ├── readthedocs.yml ├── requirements.txt ├── requirements_dev.txt ├── requirements_pytest.txt ├── setup.py └── switcheo ├── Fixed8.py ├── __init__.py ├── authenticated_client.py ├── ethereum ├── __init__.py ├── signatures.py └── utils.py ├── neo ├── __init__.py ├── signatures.py ├── test_neo_utils.py ├── test_transactions.py ├── transactions.py └── utils.py ├── products.py ├── public_client.py ├── streaming_client.py ├── switcheo_client.py ├── test_authenticated_client.py ├── test_fixed8.py ├── test_public_client.py ├── test_switcheo_client.py ├── test_switcheo_utils.py └── utils.py /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM jfloff/alpine-python:3.6-slim AS build 2 | ADD requirements*.txt /app/ 3 | WORKDIR /app 4 | RUN apk add --no-cache --update python3-dev gcc musl-dev libffi-dev openssl-dev && \ 5 | python -m pip install --upgrade pip setuptools wheel cryptography && \ 6 | python -m pip install -r requirements.txt && \ 7 | python -m pip install -r requirements_dev.txt && \ 8 | python -m pip install -r requirements_pytest.txt && \ 9 | rm -rf /var/cache/apk/* 10 | ADD . /app 11 | WORKDIR /app 12 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/vscode-remote/devcontainer.json or the definition README at 2 | // https://github.com/microsoft/vscode-dev-containers/tree/master/containers/docker-existing-dockerfile 3 | { 4 | "name": "Existing Dockerfile", 5 | 6 | // Sets the run context to one level up instead of the .devcontainer folder. 7 | "context": "..", 8 | 9 | // Update the 'dockerFile' property if you aren't using the standard 'Dockerfile' filename. 10 | "dockerFile": "Dockerfile", 11 | 12 | // The optional 'runArgs' property can be used to specify additional runtime arguments. 13 | "runArgs": [ 14 | // Uncomment the next line to use Docker from inside the container. See https://aka.ms/vscode-remote/samples/docker-in-docker for details. 15 | // "-v","/var/run/docker.sock:/var/run/docker.sock", 16 | 17 | // Uncomment the next line if you will be using a ptrace-based debugger like C++, Go, and Rust. 18 | // "--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined" 19 | 20 | // You may want to add a non-root user to your Dockerfile. On Linux, this will prevent 21 | // new files getting created as root. See https://aka.ms/vscode-remote/containers/non-root-user 22 | // for the needed Dockerfile updates and then uncomment the next line. 23 | // "-u", "vscode" 24 | ], 25 | 26 | // Use 'settings' to set *default* container specific settings.json values on container create. 27 | // You can edit these settings after create using File > Preferences > Settings > Remote. 28 | "settings": { 29 | // This will ignore your local shell user setting for Linux since shells like zsh are typically 30 | // not in base container images. You can also update this to an specific shell to ensure VS Code 31 | // uses the right one for terminals and tasks. For example, /bin/bash (or /bin/ash for Alpine). 32 | "terminal.integrated.shell.linux": "/bin/bash", 33 | "python.pythonPath": "/usr/bin/python", 34 | "python.linting.pylintEnabled": true, 35 | "python.linting.pylintPath": "/usr/bin/pylint", 36 | "python.linting.enabled": true 37 | }, 38 | 39 | // Uncomment the next line if you want to publish any ports. 40 | // "appPort": [], 41 | 42 | // Uncomment the next line to run commands after the container is created - for example installing git. 43 | "postCreateCommand": "apk update --no-cache && apk add --no-cache git", 44 | 45 | // Add the IDs of extensions you want installed when the container is created in the array below. 46 | "extensions": [ 47 | "ms-python.python" 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | 47 | # Translations 48 | *.mo 49 | *.pot 50 | 51 | # Django stuff: 52 | *.log 53 | 54 | # Sphinx documentation 55 | docs/_build/ 56 | docs/html 57 | 58 | # PyBuilder 59 | target/ 60 | 61 | # PyCharm 62 | .idea 63 | 64 | # VSCode 65 | .vscode 66 | 67 | # NEO Wallets 68 | test_switcheo_wallet* 69 | 70 | # Docker Test Environments 71 | # Dockerfile* 72 | 73 | # Python Virtual Environments 74 | venv/ 75 | 76 | # Testing File 77 | test.py* 78 | 79 | # PyTest Cache 80 | 81 | .pytest_cache/ 82 | 83 | transaction\.json 84 | test\.cs 85 | test_eth\.py 86 | test_neo\.py 87 | websockets_client\.py 88 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | python: 4 | - 3.6 5 | 6 | install: 7 | - pip install -r requirements.txt 8 | - pip install -r requirements_dev.txt 9 | 10 | script: 11 | - coverage run --source switcheo setup.py test 12 | 13 | after_success: 14 | - coveralls 15 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Keith Smith 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | =================== 2 | Switcheo Python API 3 | =================== 4 | 5 | Python Client for interacting with the Switcheo API. 6 | 7 | .. image:: https://readthedocs.org/projects/switcheo-python/badge/?version=latest 8 | :target: https://switcheo-python.readthedocs.io/en/latest 9 | :alt: ReadTheDocs 10 | .. image:: https://travis-ci.org/KeithSSmith/switcheo-python.svg?branch=master 11 | :target: https://travis-ci.org/KeithSSmith/switcheo-python 12 | :alt: Travis CI 13 | .. image:: https://coveralls.io/repos/github/KeithSSmith/switcheo-python/badge.svg?branch=master 14 | :target: https://coveralls.io/github/KeithSSmith/switcheo-python?branch=master 15 | :alt: Coveralls 16 | .. image:: https://img.shields.io/pypi/v/switcheo.svg 17 | :target: https://pypi.org/project/switcheo 18 | :alt: PyPi 19 | .. image:: https://img.shields.io/pypi/pyversions/switcheo.svg 20 | :target: https://pypi.org/project/switcheo 21 | :alt: PyPi 22 | .. image:: https://img.shields.io/pypi/l/switcheo.svg 23 | :target: https://img.shields.io/pypi/l/switcheo.svg 24 | :alt: PyPi 25 | 26 | Table of Contents 27 | ----------------- 28 | 29 | - `Overview`_ 30 | - `Installation`_ 31 | - `Public Client`_ 32 | - `Authenticated Client`_ 33 | - `Donations`_ 34 | 35 | Overview 36 | -------- 37 | 38 | Introduction 39 | ^^^^^^^^^^^^ 40 | 41 | This library is intended to empower developers to more easily interact with the Switcheo Decentralized Exchange through simple Python interfaces. 42 | 43 | Switcheo is the first functional DEX in the NEO Smart Economy and with the version 2 release of its platform made an API endpoint widely available for developers to build on top of it. This package is a wrapper around the API endpoints and is attempting to replicate and expand on the original functions available. 44 | 45 | The official Switcheo documentation can be found at https://docs.switcheo.network/ 46 | 47 | API Requests 48 | ^^^^^^^^^^^^ 49 | 50 | The Switcheo API uses REST architecture to server data through its endpoints. The requests and responses of the endpoint use JSON format. 51 | 52 | While the endpoint returns JSON this package turns the request into a Python dictionary for easier interoperability and function lookup. 53 | 54 | There are two type of API endpoints presented in this package: 55 | 56 | - Public APIs (Switcheo docs refers to Exchange APIs) - do not require authentication (message signing) that provide access to exchange history, statistics, state, and various other information about the exchange. 57 | - Authenticated APIs (Switcheo docs refers to Trading APIs) - do require authentication and these endpoints include deposits, withdrawals, trading, and cancellation. 58 | 59 | Mainnet and Testnet Endpoints 60 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 61 | 62 | When developing an application it is best to do testing against the Testnet endpoints, these can be found at: 63 | 64 | +-----+----------------------------------+ 65 | | | URL | 66 | +=====+==================================+ 67 | |UI | https://beta.switcheo.exchange | 68 | +-----+----------------------------------+ 69 | |API | https://test-api.switcheo.network| 70 | +-----+----------------------------------+ 71 | 72 | For live market data and trading on the mainnet the following enpoints should be used: 73 | 74 | +-----+----------------------------------+ 75 | | | URL | 76 | +=====+==================================+ 77 | |UI | https://switcheo.exchange | 78 | +-----+----------------------------------+ 79 | |API | https://api.switcheo.network | 80 | +-----+----------------------------------+ 81 | 82 | Installation 83 | ------------ 84 | 85 | This package is designed to be light weight and is not designed to create or store NEO wallets. If this is required for your application please refer to the `neo-python` (https://github.com/CityOfZion/neo-python) project for your needs. 86 | 87 | Requirements 88 | ^^^^^^^^^^^^ 89 | 90 | - Python 3.5 or greater 91 | 92 | Environment Setup 93 | ^^^^^^^^^^^^^^^^^ 94 | 95 | Python Installation 96 | """"""""""""""""""" 97 | Since this project requires Python 3.5 or greater this can be installed via the recommended methods found at https://www.python.org/downloads/ 98 | 99 | ``virtualenv`` Dependency Management 100 | """""""""""""""""""""""""""""""""""" 101 | 102 | It is also highly recommended to use the ``virtualenv`` functionality allowing the developer to isolate dependencies between projects. For more information the following link is worth reading: https://docs.python-guide.org/dev/virtualenvs/ 103 | 104 | Docker Image 105 | """""""""""" 106 | 107 | This project also comes with a simple Docker file that can be used to execute the contents of this package inside of. This package was developed inside of the Docker container so if there are any issues during use please report them. 108 | 109 | Install with ``pip`` 110 | ^^^^^^^^^^^^^^^^^^^^ 111 | :: 112 | 113 | python -m pip install switcheo 114 | 115 | Install from PyPi 116 | ^^^^^^^^^^^^^^^^^ 117 | 118 | The easiest way to install ``switcheo`` on your machine is to download it and install from PyPi using ``pip``. First, we recommend you to create a virtual environment in order to isolate this installation from your system directories and then install it as you normally would do: 119 | 120 | :: 121 | 122 | # create project dir 123 | mkdir myproject 124 | cd myproject 125 | 126 | # create virtual environment and activate 127 | 128 | python3.6 -m venv venv # this can also be python3 -m venv venv depending on your environment 129 | source venv/bin/activate 130 | 131 | (venv) pip install switcheo 132 | 133 | 134 | Install from Git 135 | ^^^^^^^^^^^^^^^^ 136 | 137 | Clone the repository at `https://github.com/KeithSSmith/switcheo-python `_ and navigate into the project directory. 138 | Make a Python 3 virtual environment and activate it via 139 | 140 | :: 141 | 142 | python3.6 -m venv venv 143 | source venv/bin/activate 144 | 145 | Then install the requirements via 146 | 147 | :: 148 | 149 | pip install -U setuptools pip wheel 150 | pip install -e . 151 | 152 | 153 | Updating switcheo-python from Git 154 | """"""""""""""""""""""""""""""""" 155 | 156 | If you are updating switcheo-python with ``git pull``, make sure you also update the dependencies with ``pip install -r requirements.txt``. 157 | 158 | Public Client 159 | ^^^^^^^^^^^^^ 160 | 161 | Instantiate Class 162 | """"""""""""""""" 163 | :: 164 | 165 | switcheo_pub_client = PublicClient(blockchain=neo) 166 | 167 | Exchange API Status 168 | """"""""""""""""""" 169 | :: 170 | 171 | switcheo_pub_client.get_exchange_status() 172 | 173 | Exchange Time in Epoch Milliseconds 174 | """"""""""""""""""""""""""""""""""" 175 | :: 176 | 177 | switcheo_pub_client.get_exchange_time() 178 | 179 | List Smart Contract Hashes 180 | """""""""""""""""""""""""" 181 | :: 182 | 183 | switcheo_pub_client.get_contracts() 184 | 185 | 186 | List Trade Pairs 187 | """""""""""""""" 188 | :: 189 | 190 | switcheo_pub_client.get_pairs() 191 | switcheo_pub_client.get_pairs(base="SWTH") 192 | 193 | List Orders for Address (ScriptHash) 194 | """""""""""""""""""""""""""""""""""" 195 | :: 196 | 197 | switcheo_pub_client.get_orders(address=neo_get_scripthash_from_address(address)) 198 | 199 | List Contract Balance for Address (ScriptHash) 200 | """""""""""""""""""""""""""""""""""""""""""""" 201 | :: 202 | 203 | switcheo_pub_client.get_balance(address=neo_get_scripthash_from_address(address)) 204 | 205 | Tickers 206 | """"""" 207 | :: 208 | 209 | switcheo_pub_client.get_candlesticks(pair="SWTH_NEO", start_time=round(time.time()) - 350000, end_time=round(time.time()), interval=360)) 210 | switcheo_pub_client.get_last_24_hours() 211 | switcheo_pub_client.get_last_price() 212 | 213 | Offers on Order Book 214 | """""""""""""""""""" 215 | :: 216 | 217 | switcheo_pub_client.get_offers(pair="GAS_NEO") 218 | 219 | Executed Trades for a Given Pair 220 | """""""""""""""""""""""""""""""" 221 | :: 222 | 223 | switcheo_pub_client.get_trades(pair="SWTH_NEO", limit=3) 224 | 225 | Authenticated Client 226 | ^^^^^^^^^^^^^^^^^^^^ 227 | 228 | The Switcheo docs go into extensive detail about how to authenticate messages (https://docs.switcheo.network/#authentication) on the NEO blockchain. These complications have been abstracted to make it easier for the developer to use to allow for quicker development of their project. 229 | 230 | This also means it is no longer necessary to run both ``create`` and ``execute`` portions of the authenticated client tasks since both are handled with the higher level functions listed below. 231 | 232 | Instantiate Class 233 | """"""""""""""""" 234 | :: 235 | 236 | switcheo_client = AuthenticatedClient(blockchain="neo") 237 | 238 | Deposit to Smart Contract 239 | """"""""""""""""""""""""" 240 | :: 241 | 242 | switcheo_client.deposit(asset=product_dict["SWTH"], amount=1, kp=kp) 243 | 244 | Withdrawal from Smart Contract 245 | """""""""""""""""""""""""""""" 246 | :: 247 | 248 | switcheo_client.withdrawal(asset=product_dict["SWTH"], amount=0.001, kp=kp) 249 | 250 | Place a Limit Order 251 | """"""""""""""""""" 252 | :: 253 | 254 | switcheo_client.order(kp=kp, pair="SWTH_NEO", side="buy", price=0.0002, amount=100, use_native_token=True, order_type="limit") 255 | 256 | Cancel an Open Order 257 | """""""""""""""""""" 258 | :: 259 | 260 | switcheo_client.cancel_order(order_id=order['id'], kp=kp) 261 | 262 | Donations 263 | --------- 264 | 265 | Accepted at Neo address **ANwvg4giWPxrZeJtR3ro9TJf4dUHk5wjKe**. 266 | 267 | .. _MIT: https://github.com/KeithSSmith/switcheo-python/blob/master/LICENSE.md 268 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM jfloff/alpine-python:3.6-slim AS build 2 | ADD requirements*.txt /app/ 3 | WORKDIR /app 4 | RUN apk add --no-cache --update python3-dev gcc musl-dev libffi-dev openssl-dev && \ 5 | python -m pip install --upgrade pip setuptools wheel cryptography && \ 6 | python -m pip install -r requirements.txt && \ 7 | python -m pip install -r requirements_dev.txt && \ 8 | python -m pip install -r requirements_pytest.txt && \ 9 | rm -rf /var/cache/apk/* 10 | ADD . /app 11 | WORKDIR /app 12 | -------------------------------------------------------------------------------- /docker/Dockerfile.octopus: -------------------------------------------------------------------------------- 1 | FROM jfloff/alpine-python:3.6-slim AS build 2 | RUN apk add --no-cache --update graphviz && \ #python3-dev gcc musl-dev libffi-dev openssl-dev && \ 3 | echo "manylinux1_compatible = True" >> /usr/lib/python3.6/_manylinux.py && \ 4 | python -m pip install --upgrade pip octopus && \ 5 | rm -rf /var/cache/apk/* 6 | -------------------------------------------------------------------------------- /docker/Dockerfile.original: -------------------------------------------------------------------------------- 1 | # Switcheo Python Development Environment - Dockerfile 2 | FROM ubuntu:18.04 3 | 4 | ENV DEBIAN_FRONTEND noninteractive 5 | 6 | # Disable DOTNET usage information collection 7 | # https://docs.microsoft.com/en-us/dotnet/core/tools/telemetry#behavior 8 | ENV DOTNET_CLI_TELEMETRY_OPTOUT 1 9 | 10 | # Install system dependencies. always should be done in one line 11 | # https://docs.docker.com/engine/userguide/eng-image/dockerfile_best-practices/#run 12 | RUN apt-get update && apt-get install -y \ 13 | unzip \ 14 | git-core \ 15 | wget \ 16 | curl \ 17 | python3.6 \ 18 | python3.6-dev \ 19 | python3.6-venv \ 20 | python3-pip \ 21 | libleveldb-dev \ 22 | libssl-dev \ 23 | g++ && \ 24 | cd /usr/local/bin && \ 25 | ln -s /usr/bin/python3 python && \ 26 | python -m pip install --upgrade pip && \ 27 | pip install --upgrade pip 28 | 29 | # APT cleanup to reduce image size 30 | RUN rm -rf /var/lib/apt/lists/* 31 | 32 | # Update Python environment based on required packages 33 | COPY requirements.txt / 34 | RUN python -m pip install -r /requirements.txt 35 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = SwitcheoPython 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/authenticated_client.rst: -------------------------------------------------------------------------------- 1 | Authenticated Client - :mod:`authenticated_client` 2 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 3 | 4 | .. contents:: Table of Contents 5 | 6 | 7 | .. automodule:: switcheo.authenticated_client 8 | :members: 9 | :private-members: 10 | :undoc-members: 11 | -------------------------------------------------------------------------------- /docs/authenticated_client_usage.rst: -------------------------------------------------------------------------------- 1 | Authenticated Client 2 | ^^^^^^^^^^^^^^^^^^^^ 3 | 4 | The Switcheo docs go into extensive detail about how to authenticate messages (https://docs.switcheo.network/#authentication) on the NEO blockchain. These complications have been abstracted to make it easier for the developer to use to allow for quicker development of their project. 5 | 6 | This also means it is no longer necessary to run both ``create`` and ``execute`` portions of the authenticated client tasks since both are handled with the higher level functions listed below. 7 | 8 | Instantiate Class 9 | """"""""""""""""" 10 | :: 11 | 12 | switcheo_client = AuthenticatedClient(blockchain="neo") 13 | 14 | Deposit to Smart Contract 15 | """"""""""""""""""""""""" 16 | :: 17 | 18 | switcheo_client.deposit(asset=product_dict["SWTH"], amount=1, kp=kp) 19 | 20 | Withdrawal from Smart Contract 21 | """""""""""""""""""""""""""""" 22 | :: 23 | 24 | switcheo_client.withdrawal(asset=product_dict["SWTH"], amount=0.001, kp=kp) 25 | 26 | Place a Limit Order 27 | """"""""""""""""""" 28 | :: 29 | 30 | switcheo_client.order(kp=kp, pair="SWTH_NEO", side="buy", price=0.0002, amount=100, use_native_token=True, order_type="limit") 31 | 32 | Cancel an Open Order 33 | """""""""""""""""""" 34 | :: 35 | 36 | switcheo_client.cancel_order(order_id=order['id'], kp=kp) 37 | -------------------------------------------------------------------------------- /docs/clients.rst: -------------------------------------------------------------------------------- 1 | Client Documentation 2 | -------------------- 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | public_client 8 | authenticated_client 9 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file does only contain a selection of the most common options. For a 6 | # full list see the documentation: 7 | # http://www.sphinx-doc.org/en/master/config 8 | 9 | # -- Path setup -------------------------------------------------------------- 10 | 11 | # If extensions (or modules to document with autodoc) are in another directory, 12 | # add these directories to sys.path here. If the directory is relative to the 13 | # documentation root, use os.path.abspath to make it absolute, like shown here. 14 | # 15 | import os 16 | import sys 17 | 18 | # Get the project root dir, which is the parent dir of this 19 | cwd = os.getcwd() 20 | project_root = os.path.dirname(cwd) 21 | 22 | # Insert the project root dir as the first element in the PYTHONPATH. 23 | # This lets us ensure that the source package is imported, and that its 24 | # version is used. 25 | sys.path.insert(0, project_root) 26 | 27 | 28 | # -- Project information ----------------------------------------------------- 29 | 30 | project = 'Switcheo Python' 31 | copyright = '2018, Keith Smith' 32 | author = 'Keith Smith' 33 | 34 | # The short X.Y version 35 | version = '' 36 | # The full version, including alpha/beta/rc tags 37 | release = '0.3.2' 38 | 39 | 40 | # -- General configuration --------------------------------------------------- 41 | 42 | # If your documentation needs a minimal Sphinx version, state it here. 43 | # 44 | # needs_sphinx = '1.0' 45 | 46 | # Add any Sphinx extension module names here, as strings. They can be 47 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 48 | # ones. 49 | extensions = [ 50 | 'sphinx.ext.autodoc', 51 | 'sphinx.ext.viewcode', 52 | ] 53 | 54 | # Add any paths that contain templates here, relative to this directory. 55 | templates_path = ['_templates'] 56 | 57 | # The suffix(es) of source filenames. 58 | # You can specify multiple suffix as a list of string: 59 | # 60 | # source_suffix = ['.rst', '.md'] 61 | source_suffix = '.rst' 62 | 63 | # The master toctree document. 64 | master_doc = 'index' 65 | 66 | # The language for content autogenerated by Sphinx. Refer to documentation 67 | # for a list of supported languages. 68 | # 69 | # This is also used if you do content translation via gettext catalogs. 70 | # Usually you set "language" from the command line for these cases. 71 | language = None 72 | 73 | # List of patterns, relative to source directory, that match files and 74 | # directories to ignore when looking for source files. 75 | # This pattern also affects html_static_path and html_extra_path . 76 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 77 | 78 | # The name of the Pygments (syntax highlighting) style to use. 79 | pygments_style = 'sphinx' 80 | 81 | 82 | # -- Options for HTML output ------------------------------------------------- 83 | 84 | # The theme to use for HTML and HTML Help pages. See the documentation for 85 | # a list of builtin themes. 86 | # 87 | html_theme = 'sphinx_rtd_theme' 88 | 89 | # Theme options are theme-specific and customize the look and feel of a theme 90 | # further. For a list of options available for each theme, see the 91 | # documentation. 92 | # 93 | # html_theme_options = {} 94 | 95 | # Add any paths that contain custom static files (such as style sheets) here, 96 | # relative to this directory. They are copied after the builtin static files, 97 | # so a file named "default.css" will overwrite the builtin "default.css". 98 | html_static_path = ['_static'] 99 | 100 | # Custom sidebar templates, must be a dictionary that maps document names 101 | # to template names. 102 | # 103 | # The default sidebars (for documents that don't match any pattern) are 104 | # defined by theme itself. Builtin themes are using these templates by 105 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 106 | # 'searchbox.html']``. 107 | # 108 | # html_sidebars = {} 109 | 110 | 111 | # -- Options for HTMLHelp output --------------------------------------------- 112 | 113 | # Output file base name for HTML help builder. 114 | htmlhelp_basename = 'SwitcheoPythondoc' 115 | 116 | 117 | # -- Options for LaTeX output ------------------------------------------------ 118 | 119 | latex_elements = { 120 | # The paper size ('letterpaper' or 'a4paper'). 121 | # 122 | # 'papersize': 'letterpaper', 123 | 124 | # The font size ('10pt', '11pt' or '12pt'). 125 | # 126 | # 'pointsize': '10pt', 127 | 128 | # Additional stuff for the LaTeX preamble. 129 | # 130 | # 'preamble': '', 131 | 132 | # Latex figure (float) alignment 133 | # 134 | # 'figure_align': 'htbp', 135 | } 136 | 137 | # Grouping the document tree into LaTeX files. List of tuples 138 | # (source start file, target name, title, 139 | # author, documentclass [howto, manual, or own class]). 140 | latex_documents = [ 141 | (master_doc, 'SwitcheoPython.tex', 'Switcheo Python Documentation', 142 | 'Keith Smith', 'manual'), 143 | ] 144 | 145 | 146 | # -- Options for manual page output ------------------------------------------ 147 | 148 | # One entry per manual page. List of tuples 149 | # (source start file, name, description, authors, manual section). 150 | man_pages = [ 151 | (master_doc, 'switcheopython', 'Switcheo Python Documentation', 152 | [author], 1) 153 | ] 154 | 155 | 156 | # -- Options for Texinfo output ---------------------------------------------- 157 | 158 | # Grouping the document tree into Texinfo files. List of tuples 159 | # (source start file, target name, title, author, 160 | # dir menu entry, description, category) 161 | texinfo_documents = [ 162 | (master_doc, 'SwitcheoPython', 'Switcheo Python Documentation', 163 | author, 'SwitcheoPython', 'One line description of project.', 164 | 'Miscellaneous'), 165 | ] 166 | 167 | 168 | # -- Extension configuration ------------------------------------------------- 169 | -------------------------------------------------------------------------------- /docs/ethereum.rst: -------------------------------------------------------------------------------- 1 | Ethereum Signing - :mod:`ethereum` 2 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 3 | 4 | .. contents:: Table of Contents 5 | 6 | 7 | .. automodule:: switcheo.ethereum.signatures 8 | :members: 9 | :private-members: 10 | :undoc-members: 11 | -------------------------------------------------------------------------------- /docs/ethereum_usage.rst: -------------------------------------------------------------------------------- 1 | Ethereum Signatures 2 | ^^^^^^^^^^^^^^^^^^^ 3 | 4 | The Switcheo docs go into extensive detail about how to authenticate messages (https://docs.switcheo.network/#authentication) on the NEO blockchain. These complications have been abstracted to make it easier for the developer to use to allow for quicker development of their project. 5 | 6 | This also means it is no longer necessary to run both ``create`` and ``execute`` portions of the authenticated client tasks since both are handled with the higher level functions listed below. 7 | 8 | Sign Create Cancellation 9 | """""""""""""""""""""""" 10 | :: 11 | 12 | sign_create_cancellation(cancellation_params, private_key) 13 | 14 | Sign Execute Cancellation 15 | """"""""""""""""""""""""" 16 | :: 17 | 18 | sign_execute_cancellation(cancellation_params, private_key) 19 | 20 | Sign Create Deposit 21 | """"""""""""""""""" 22 | :: 23 | 24 | sign_create_deposit(deposit_params, private_key) 25 | 26 | Sign Execute Deposit 27 | """""""""""""""""""" 28 | :: 29 | 30 | sign_execute_deposit(deposit_params, private_key, infura_url) 31 | 32 | Sign Create Order 33 | """"""""""""""""" 34 | :: 35 | 36 | sign_create_order(order_params, private_key) 37 | 38 | Sign Execute Order 39 | """""""""""""""""" 40 | :: 41 | 42 | sign_execute_order(order_params, private_key) 43 | 44 | Sign Create Withdrawal 45 | """""""""""""""""""""" 46 | :: 47 | 48 | sign_create_withdrawal(withdrawal_params, private_key) 49 | 50 | Sign Execute Withdrawal 51 | """"""""""""""""""""""""" 52 | :: 53 | 54 | sign_execute_withdrawal(withdrawal_params, private_key) 55 | -------------------------------------------------------------------------------- /docs/images/switcheo-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KeithSSmith/switcheo-python/d9bb29ee48b756d469046b7871bd7f627577dc93/docs/images/switcheo-logo.png -------------------------------------------------------------------------------- /docs/import.rst: -------------------------------------------------------------------------------- 1 | Import Statements 2 | ^^^^^^^^^^^^^^^^^ 3 | 4 | Clients 5 | """"""" 6 | 7 | Import the desired Switcheo Clients into your project: 8 | 9 | :: 10 | 11 | from switcheo.authenticated_client import AuthenticatedClient 12 | from switcheo.public_client import PublicClient 13 | 14 | Helpers 15 | """"""" 16 | 17 | There are also helper functions to help transform inputs and wallet addresses into the correct format. 18 | 19 | :: 20 | 21 | from switcheo.neo.utils import private_key_to_hex, neo_get_scripthash_from_private_key, neo_get_address_from_scripthash, open_wallet 22 | 23 | 3rd Party Helpers 24 | """"""""""""""""" 25 | 26 | Much of the underlying code relies on the ``neo-python-core`` package. These can also be used as helper functions by importing them as follows: 27 | 28 | :: 29 | 30 | from neocore.KeyPair import KeyPair 31 | from neocore.Cryptography.Helper import scripthash_to_address 32 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. Switcheo Python documentation master file, created by 2 | sphinx-quickstart on Fri Aug 3 23:06:21 2018. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | =================== 7 | Switcheo Python API 8 | =================== 9 | 10 | .. image:: images/switcheo-logo.png 11 | :width: 150px 12 | :height: 150px 13 | :alt: Switcheo Decentralized Exchange 14 | :align: center 15 | 16 | .. raw:: html 17 | 18 |

switcheo-python

19 | 20 | .. image:: https://readthedocs.org/projects/switcheo-python/badge/?version=latest 21 | :target: https://switcheo-python.readthedocs.io/en/latest 22 | :alt: ReadTheDocs 23 | .. image:: https://travis-ci.org/KeithSSmith/switcheo-python.svg?branch=master 24 | :target: https://travis-ci.org/KeithSSmith/switcheo-python 25 | :alt: Travis CI 26 | .. image:: https://coveralls.io/repos/github/KeithSSmith/switcheo-python/badge.svg?branch=master 27 | :target: https://coveralls.io/github/KeithSSmith/switcheo-python?branch=master 28 | :alt: Coveralls 29 | .. image:: https://img.shields.io/pypi/v/switcheo.svg 30 | :target: https://pypi.org/project/switcheo 31 | :alt: PyPi 32 | .. image:: https://img.shields.io/pypi/pyversions/switcheo.svg 33 | :target: https://pypi.org/project/switcheo 34 | :alt: PyPi 35 | .. image:: https://img.shields.io/pypi/l/switcheo.svg 36 | :target: https://img.shields.io/pypi/l/switcheo.svg 37 | :alt: PyPi 38 | 39 | Welcome to the Switcheo Python project. The goal of this project is to empower other developers and end users by offering a Python client that is able to interact with all aspects of the Switcheo Decentralized Exchange via its REST endpoints. 40 | When using this package the developer does not have to worry about cryptographic signatures or interacting with the blockchain, that is handled within the client. 41 | Allowing developers and end user to focus on **Trading**! 42 | 43 | .. toctree:: 44 | :maxdepth: 2 45 | 46 | overview 47 | install 48 | usage 49 | clients 50 | signatures 51 | 52 | .. _MIT: https://github.com/KeithSSmith/switcheo-python/blob/master/LICENSE.md 53 | -------------------------------------------------------------------------------- /docs/install.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ------------ 3 | 4 | This package is designed to be light weight and is not designed to create or store NEO wallets. If this is required for your application please refer to the `neo-python` (https://github.com/CityOfZion/neo-python) project for your needs. 5 | 6 | Requirements 7 | ^^^^^^^^^^^^ 8 | 9 | - Python 3.5 or greater 10 | 11 | Environment Setup 12 | ^^^^^^^^^^^^^^^^^ 13 | 14 | Python Installation 15 | """"""""""""""""""" 16 | Since this project requires Python 3.5 or greater this can be installed via the recommended methods found at https://www.python.org/downloads/ 17 | 18 | 19 | ``virtualenv`` Dependency Management 20 | """""""""""""""""""""""""""""""""""" 21 | 22 | It is also highly recommended to use the ``virtualenv`` functionality allowing the developer to isolate dependencies between projects. For more information the following link is worth reading: https://docs.python-guide.org/dev/virtualenvs/ 23 | 24 | Docker Image 25 | """""""""""" 26 | 27 | This project also comes with a simple Docker file that can be used to execute the contents of this package inside of. This package was developed inside of the Docker container so if there are any issues during use please report them. 28 | 29 | Install with ``pip`` 30 | ^^^^^^^^^^^^^^^^^^^^ 31 | :: 32 | 33 | python -m pip install switcheo 34 | 35 | Install from PyPi 36 | ^^^^^^^^^^^^^^^^^ 37 | 38 | The easiest way to install ``switcheo`` on your machine is to download it and install from PyPi using ``pip``. First, we recommend you to create a virtual environment in order to isolate this installation from your system directories and then install it as you normally would do: 39 | 40 | :: 41 | 42 | # create project dir 43 | mkdir myproject 44 | cd myproject 45 | 46 | # create virtual environment and activate 47 | 48 | python3.6 -m venv venv # this can also be python3 -m venv venv depending on your environment 49 | source venv/bin/activate 50 | 51 | (venv) pip install switcheo 52 | 53 | 54 | Install from Git 55 | ^^^^^^^^^^^^^^^^ 56 | 57 | Clone the repository at `https://github.com/KeithSSmith/switcheo-python `_ and navigate into the project directory. 58 | Make a Python 3 virtual environment and activate it via 59 | 60 | :: 61 | 62 | python3.6 -m venv venv 63 | source venv/bin/activate 64 | 65 | Then install the requirements via 66 | 67 | :: 68 | 69 | pip install -U setuptools pip wheel 70 | pip install -e . 71 | 72 | 73 | Updating switcheo-python from Git 74 | """"""""""""""""""""""""""""""""" 75 | 76 | If you are updating switcheo-python with ``git pull``, make sure you also update the dependencies with ``pip install -r requirements.txt``. 77 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=SwitcheoPython 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 20 | echo.installed, then set the SPHINXBUILD environment variable to point 21 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 22 | echo.may add the Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /docs/neo.rst: -------------------------------------------------------------------------------- 1 | NEO Signing - :mod:`neo` 2 | ^^^^^^^^^^^^^^^^^^^^^^^^ 3 | 4 | .. contents:: Table of Contents 5 | 6 | 7 | .. automodule:: switcheo.neo.signatures 8 | :members: 9 | :private-members: 10 | :undoc-members: 11 | -------------------------------------------------------------------------------- /docs/neo_usage.rst: -------------------------------------------------------------------------------- 1 | NEO Signatures 2 | ^^^^^^^^^^^^^^^^^^^ 3 | 4 | The Switcheo docs go into extensive detail about how to authenticate messages (https://docs.switcheo.network/#authentication) on the NEO blockchain. These complications have been abstracted to make it easier for the developer to use to allow for quicker development of their project. 5 | 6 | This also means it is no longer necessary to run both ``create`` and ``execute`` portions of the authenticated client tasks since both are handled with the higher level functions listed below. 7 | 8 | Sign Create Cancellation 9 | """""""""""""""""""""""" 10 | :: 11 | 12 | sign_create_cancellation(cancellation_params, private_key) 13 | 14 | Sign Execute Cancellation 15 | """"""""""""""""""""""""" 16 | :: 17 | 18 | sign_execute_cancellation(cancellation_params, private_key) 19 | 20 | Sign Create Deposit 21 | """"""""""""""""""" 22 | :: 23 | 24 | sign_create_deposit(deposit_params, private_key) 25 | 26 | Sign Execute Deposit 27 | """""""""""""""""""" 28 | :: 29 | 30 | sign_execute_deposit(deposit_params, private_key) 31 | 32 | Sign Create Order 33 | """"""""""""""""" 34 | :: 35 | 36 | sign_create_order(order_params, private_key) 37 | 38 | Sign Execute Order 39 | """""""""""""""""" 40 | :: 41 | 42 | sign_execute_order(order_params, private_key) 43 | 44 | Sign Create Withdrawal 45 | """""""""""""""""""""" 46 | :: 47 | 48 | sign_create_withdrawal(withdrawal_params, private_key) 49 | 50 | Sign Execute Withdrawal 51 | """"""""""""""""""""""""" 52 | :: 53 | 54 | sign_execute_withdrawal(withdrawal_params, private_key) 55 | -------------------------------------------------------------------------------- /docs/overview.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | Overview 3 | ======== 4 | 5 | Introduction 6 | ^^^^^^^^^^^^ 7 | 8 | This library is intended to empower developers to more easily interact with the Switcheo Decentralized Exchange through simple Python interfaces. 9 | 10 | Switcheo is the first functional DEX in the NEO Smart Economy and with the version 2 release of its platform made an API endpoint widely available for developers to build on top of it. This package is a wrapper around the API endpoints and is attempting to replicate and expand on the original functions available. 11 | 12 | The official Switcheo documentation can be found at https://docs.switcheo.network/ 13 | 14 | API Requests 15 | ^^^^^^^^^^^^ 16 | 17 | The Switcheo API uses REST architecture to server data through its endpoints. The requests and responses of the endpoint use JSON format. 18 | 19 | While the endpoint returns JSON this package turns the request into a Python dictionary for easier interoperability and function lookup. 20 | 21 | There are two type of API endpoints presented in this package: 22 | 23 | - Public APIs (Switcheo docs refers to Exchange APIs) - do not require authentication (message signing) that provide access to exchange history, statistics, state, and various other information about the exchange. 24 | - Authenticated APIs (Switcheo docs refers to Trading APIs) - do require authentication and these endpoints include deposits, withdrawals, trading, and cancellation. 25 | 26 | Mainnet and Testnet Endpoints 27 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 28 | 29 | When developing an application it is best to do testing against the Testnet endpoints, these can be found at: 30 | 31 | +-----+----------------------------------+ 32 | | | URL | 33 | +=====+==================================+ 34 | |UI | https://beta.switcheo.exchange | 35 | +-----+----------------------------------+ 36 | |API | https://test-api.switcheo.network| 37 | +-----+----------------------------------+ 38 | 39 | For live market data and trading on the mainnet the following enpoints should be used: 40 | 41 | +-----+----------------------------------+ 42 | | | URL | 43 | +=====+==================================+ 44 | |UI | https://switcheo.exchange | 45 | +-----+----------------------------------+ 46 | |API | https://api.switcheo.network | 47 | +-----+----------------------------------+ 48 | 49 | 50 | Rate Limits 51 | ^^^^^^^^^^^ 52 | 53 | All endpoints are rate-limited by the Switcheo team. Hard and fast rules are not given but it has been stated that open transactions (unsigned) and transactions that have not hit the blockchain will attribute to this limit. 54 | 55 | The error code ``429`` will indicate when a rate limit has been met. It is important for the developer to implement an exponential back off strategy when encountering this error code. 56 | 57 | Further error codes can be found at https://docs.switcheo.network/#error 58 | -------------------------------------------------------------------------------- /docs/public_client.rst: -------------------------------------------------------------------------------- 1 | Public Class - :mod:`public_client` 2 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 3 | 4 | .. contents:: Table of Contents 5 | 6 | 7 | .. automodule:: switcheo.public_client 8 | :members: 9 | :private-members: 10 | :undoc-members: 11 | :inherited-members: 12 | :show-inheritance: 13 | -------------------------------------------------------------------------------- /docs/public_client_usage.rst: -------------------------------------------------------------------------------- 1 | Public Client 2 | ^^^^^^^^^^^^^ 3 | 4 | Instantiate Class 5 | """"""""""""""""" 6 | :: 7 | 8 | switcheo_pub_client = PublicClient(blockchain="neo") 9 | 10 | Exchange API Status 11 | """"""""""""""""""" 12 | :: 13 | 14 | switcheo_pub_client.get_exchange_status() 15 | 16 | Exchange Time in Epoch Milliseconds 17 | """"""""""""""""""""""""""""""""""" 18 | :: 19 | 20 | switcheo_pub_client.get_exchange_time() 21 | 22 | List Smart Contract Hashes 23 | """""""""""""""""""""""""" 24 | :: 25 | 26 | switcheo_pub_client.get_contracts() 27 | 28 | 29 | List Trade Pairs 30 | """""""""""""""" 31 | :: 32 | 33 | switcheo_pub_client.get_pairs() 34 | switcheo_pub_client.get_pairs(base="SWTH") 35 | 36 | List Orders for Address (ScriptHash) 37 | """""""""""""""""""""""""""""""""""" 38 | :: 39 | 40 | switcheo_pub_client.get_orders(address=neo_get_scripthash_from_private_key(prikey)) 41 | 42 | List Contract Balance for Address (ScriptHash) 43 | """""""""""""""""""""""""""""""""""""""""""""" 44 | :: 45 | 46 | switcheo_pub_client.get_balance(address=neo_get_scripthash_from_private_key(prikey)) 47 | 48 | Tickers 49 | """"""" 50 | :: 51 | 52 | switcheo_pub_client.get_candlesticks(pair="SWTH_NEO", start_time=round(time.time()) - 350000, end_time=round(time.time()), interval=360)) 53 | switcheo_pub_client.get_last_24_hours() 54 | switcheo_pub_client.get_last_price() 55 | 56 | Offers on Order Book 57 | """""""""""""""""""" 58 | :: 59 | 60 | switcheo_pub_client.get_offers(pair="GAS_NEO") 61 | 62 | Executed Trades for a Given Pair 63 | """""""""""""""""""""""""""""""" 64 | :: 65 | 66 | switcheo_pub_client.get_trades(pair="SWTH_NEO", limit=3) 67 | -------------------------------------------------------------------------------- /docs/signatures.rst: -------------------------------------------------------------------------------- 1 | Signatures Documentation 2 | ------------------------ 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | ethereum 8 | neo 9 | -------------------------------------------------------------------------------- /docs/usage.rst: -------------------------------------------------------------------------------- 1 | Usage 2 | ----- 3 | 4 | The primary usage of the ``switcheo-python`` package will be utilizing it in conjunction with other functionality. In order to use this in your project the following simple examples are being demonstrated. 5 | 6 | .. toctree:: 7 | :maxdepth: 2 8 | 9 | import 10 | public_client_usage 11 | authenticated_client_usage 12 | ethereum_usage 13 | neo_usage 14 | -------------------------------------------------------------------------------- /readthedocs.yml: -------------------------------------------------------------------------------- 1 | requirements_file: requirements.txt 2 | 3 | build: 4 | image: latest 5 | 6 | python: 7 | version: 3.6 8 | 9 | formats: 10 | - none 11 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | neocore==0.5.6 2 | python-socketio[client]==4.4.0 3 | requests==2.22.0 4 | web3==5.4.0 5 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | autopep8==1.4.4 2 | coverage==4.5.1 3 | coveralls==1.5.1 4 | pylint==2.4.4 5 | sphinx==1.8.1 6 | -------------------------------------------------------------------------------- /requirements_pytest.txt: -------------------------------------------------------------------------------- 1 | pytest==3.10.0 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from setuptools import setup, find_packages 5 | try: # pip version >= 10.0 6 | from pip._internal.req import parse_requirements 7 | from pip._internal.download import PipSession 8 | except ImportError: # pip version < 10.0 9 | from pip.req import parse_requirements 10 | from pip.download import PipSession 11 | 12 | install_reqs = parse_requirements('requirements.txt', session=PipSession()) 13 | reqs = [str(ir.req) for ir in install_reqs] 14 | 15 | with open("README.rst", "r") as fh: 16 | long_description = fh.read() 17 | 18 | setup( 19 | name='switcheo', 20 | python_requires='>=3.6', 21 | version='0.4.2', 22 | author='Keith Smith', 23 | author_email='keith.scotts@gmail.com', 24 | license='MIT License', 25 | url='https://github.com/KeithSSmith/switcheo-python', 26 | packages=find_packages(), 27 | include_package_data=True, 28 | install_requires=reqs, 29 | description='Python Client to interact with the Switcheo Exchange API', 30 | long_description=long_description, 31 | keywords=['switcheo', 'switcheo-api', 'trade', 'ethereum', 'neo', 'ETH', 'NEO', 32 | 'QTUM', 'client', 'api', 'wrapper', 'exchange', 'dex', 'crypto', 'currency', 'trading', 'trading-api'], 33 | classifiers=[ 34 | 'Development Status :: 3 - Alpha', 35 | 'Intended Audience :: Developers', 36 | 'Topic :: Software Development :: Libraries :: Python Modules', 37 | 'License :: OSI Approved :: MIT License', 38 | 'Natural Language :: English', 39 | 'Programming Language :: Python :: 3', 40 | 'Programming Language :: Python :: 3.6', 41 | 'Programming Language :: Python :: 3.7', 42 | ], 43 | ) 44 | -------------------------------------------------------------------------------- /switcheo/Fixed8.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | """ 3 | Description: 4 | Fixed8 5 | 6 | Usage: 7 | from neocore.Fixed8 import Fixed8 8 | """ 9 | 10 | from neocore.Fixed8 import Fixed8 11 | from switcheo.utils import reverse_hex 12 | 13 | 14 | def num2fixed8(number, size=8): 15 | if size % 1 != 0: 16 | raise ValueError('Fixed8 size {} is not a whole number.'.format(size)) 17 | return SwitcheoFixed8(float("{:.8f}".format(number))).toReverseHex()[:size*2] 18 | 19 | 20 | class SwitcheoFixed8(Fixed8): 21 | 22 | def toHex(self): 23 | hexstring = hex(round(self.value * self.D))[2:] 24 | return "{:0>16s}".format(hexstring) 25 | 26 | def toReverseHex(self): 27 | return reverse_hex(self.toHex()) 28 | -------------------------------------------------------------------------------- /switcheo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KeithSSmith/switcheo-python/d9bb29ee48b756d469046b7871bd7f627577dc93/switcheo/__init__.py -------------------------------------------------------------------------------- /switcheo/authenticated_client.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | """ 3 | Description: 4 | Authenticated Client for the Switcheo decentralized exchange. 5 | This API extends the PublicClient by offering API endpoints that require signatures from the 6 | users private key. 7 | Deposit, Withdrawal, Trading, and Cancellations on the exchange are accessible through this class. 8 | Usage: 9 | from switcheo.authenticated_client import AuthenticatedClient 10 | """ 11 | 12 | from functools import partial 13 | from switcheo.public_client import PublicClient 14 | from switcheo.utils import get_epoch_milliseconds 15 | from switcheo.neo.utils import to_neo_asset_amount 16 | from switcheo.neo.signatures import sign_create_deposit as sign_create_deposit_neo,\ 17 | sign_execute_deposit as sign_execute_deposit_neo, sign_create_order as sign_create_order_neo,\ 18 | sign_execute_order as sign_execute_order_neo, sign_create_withdrawal as sign_create_withdrawal_neo,\ 19 | sign_execute_withdrawal as sign_execute_withdrawal_neo, sign_create_cancellation as sign_create_cancellation_neo,\ 20 | sign_execute_cancellation as sign_execute_cancellation_neo 21 | from switcheo.ethereum.signatures import sign_create_deposit as sign_create_deposit_eth,\ 22 | sign_execute_deposit as sign_execute_deposit_eth, sign_create_order as sign_create_order_eth,\ 23 | sign_execute_order as sign_execute_order_eth, sign_create_withdrawal as sign_create_withdrawal_eth,\ 24 | sign_execute_withdrawal as sign_execute_withdrawal_eth, sign_create_cancellation as sign_create_cancellation_eth,\ 25 | sign_execute_cancellation as sign_execute_cancellation_eth 26 | from eth_utils import to_wei 27 | 28 | 29 | class AuthenticatedClient(PublicClient): 30 | 31 | def __init__(self, 32 | blockchain='neo', 33 | contract_version='V3', 34 | api_url='https://test-api.switcheo.network/', 35 | api_version='/v2'): 36 | PublicClient.__init__(self, 37 | blockchain=blockchain, 38 | contract_version=contract_version, 39 | api_url=api_url, 40 | api_version=api_version) 41 | self.infura_dict = { 42 | 'https://api.switcheo.network': 'https://infura.io/', 43 | 'https://api.switcheo.network/': 'https://infura.io/', 44 | 'api.switcheo.network': 'https://infura.io/', 45 | 'api.switcheo.network/': 'https://infura.io/', 46 | 'https://test-api.switcheo.network': 'https://ropsten.infura.io/', 47 | 'https://test-api.switcheo.network/': 'https://ropsten.infura.io/', 48 | 'test-api.switcheo.network': 'https://ropsten.infura.io/', 49 | 'test-api.switcheo.network/': 'https://ropsten.infura.io/' 50 | } 51 | self.infura_url = self.infura_dict[api_url] 52 | self.blockchain_amount = { 53 | 'eth': partial(to_wei, unit='ether'), 54 | 'neo': to_neo_asset_amount 55 | } 56 | self.sign_create_cancellation_function = { 57 | 'eth': sign_create_cancellation_eth, 58 | 'neo': sign_create_cancellation_neo 59 | } 60 | self.sign_execute_cancellation_function = { 61 | 'eth': sign_execute_cancellation_eth, 62 | 'neo': sign_execute_cancellation_neo 63 | } 64 | self.sign_create_deposit_function = { 65 | 'eth': sign_create_deposit_eth, 66 | 'neo': sign_create_deposit_neo 67 | } 68 | self.sign_execute_deposit_function = { 69 | 'eth': partial(sign_execute_deposit_eth, infura_url=self.infura_url), 70 | 'neo': sign_execute_deposit_neo 71 | } 72 | self.sign_create_order_function = { 73 | 'eth': sign_create_order_eth, 74 | 'neo': sign_create_order_neo 75 | } 76 | self.sign_execute_order_function = { 77 | 'eth': sign_execute_order_eth, 78 | 'neo': sign_execute_order_neo 79 | } 80 | self.sign_create_withdrawal_function = { 81 | 'eth': sign_create_withdrawal_eth, 82 | 'neo': sign_create_withdrawal_neo 83 | } 84 | self.sign_execute_withdrawal_function = { 85 | 'eth': sign_execute_withdrawal_eth, 86 | 'neo': sign_execute_withdrawal_neo 87 | } 88 | 89 | def cancel_order(self, order_id, private_key): 90 | """ 91 | This function is a wrapper function around the create and execute cancellation functions to help make this 92 | processes simpler for the end user by combining these requests in 1 step. 93 | Execution of this function is as follows:: 94 | 95 | cancel_order(order_id=order['id'], private_key=kp) 96 | cancel_order(order_id=order['id'], private_key=eth_private_key) 97 | 98 | The expected return result for this function is the same as the execute_cancellation function:: 99 | 100 | { 101 | 'id': 'b8e617d5-f5ed-4600-b8f2-7d370d837750', 102 | 'blockchain': 'neo', 103 | 'contract_hash': 'a195c1549e7da61b8da315765a790ac7e7633b82', 104 | 'address': 'fea2b883725ef2d194c9060f606cd0a0468a2c59', 105 | 'side': 'buy', 106 | 'offer_asset_id': 'c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b', 107 | 'want_asset_id': 'ab38352559b8b203bde5fddfa0b07d8b2525e132', 108 | 'offer_amount': '2000000', 109 | 'want_amount': '10000000000', 110 | 'transfer_amount': '0', 111 | 'priority_gas_amount': '0', 112 | 'use_native_token': True, 113 | 'native_fee_transfer_amount': 0, 114 | 'deposit_txn': None, 115 | 'created_at': '2018-08-05T11:16:47.021Z', 116 | 'status': 'processed', 117 | 'fills': [], 118 | 'makes': [ 119 | { 120 | 'id': '6b9f40de-f9bb-46b6-9434-d281f8c06b74', 121 | 'offer_hash': '6830d82dbdda566ab32e9a8d9d9d94d3297f67c10374d69bb35d6c5a86bd3e92', 122 | 'available_amount': '0', 123 | 'offer_asset_id': 'c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b', 124 | 'offer_amount': '2000000', 125 | 'want_asset_id': 'ab38352559b8b203bde5fddfa0b07d8b2525e132', 126 | 'want_amount': '10000000000', 127 | 'filled_amount': '0.0', 128 | 'txn': None, 129 | 'cancel_txn': None, 130 | 'price': '0.0002', 131 | 'status': 'cancelling', 132 | 'created_at': '2018-08-05T11:16:47.036Z', 133 | 'transaction_hash': 'e5b08c4a55c7494f1ec7dd93ac2bb2b4e84e77dec9e00e91be1d520cb818c415', 134 | 'trades': [] 135 | } 136 | ] 137 | } 138 | 139 | :param order_id: The order ID of the open transaction on the order book that you want to cancel. 140 | :type order_id: str 141 | :param private_key: The KeyPair that will be used to sign the transaction sent to the blockchain. 142 | :type private_key: KeyPair 143 | :return: Dictionary of the transaction details and state after sending the signed transaction to the blockchain. 144 | """ 145 | create_cancellation = self.create_cancellation(order_id=order_id, private_key=private_key) 146 | return self.execute_cancellation(cancellation_params=create_cancellation, private_key=private_key) 147 | 148 | def create_cancellation(self, order_id, private_key): 149 | """ 150 | Function to create a cancellation request for the order ID from the open orders on the order book. 151 | Execution of this function is as follows:: 152 | 153 | create_cancellation(order_id=order['id'], private_key=kp) 154 | 155 | The expected return result for this function is as follows:: 156 | 157 | { 158 | 'id': '6b9f40de-f9bb-46b6-9434-d281f8c06b74', 159 | 'transaction': { 160 | 'hash': '50d99ebd7e57dbdceb7edc2014da5f446c8f44cc0a0b6d9c762a29e8a74bb051', 161 | 'sha256': '509edb9888fa675988fa71a27600b2655e63fe979424f13f5c958897b2e99ed8', 162 | 'type': 209, 163 | 'version': 1, 164 | 'attributes': [ 165 | { 166 | 'usage': 32, 167 | 'data': '592c8a46a0d06c600f06c994d1f25e7283b8a2fe' 168 | } 169 | ], 170 | 'inputs': [ 171 | { 172 | 'prevHash': '9eaca1adcbbc0669a936576cb9ad03c11c99c356347aae3037ce1f0e4d330d85', 173 | 'prevIndex': 0 174 | }, { 175 | 'prevHash': 'c858e4d2af1e1525fa974fb2b1678caca1f81a5056513f922789594939ff713d', 176 | 'prevIndex': 37 177 | } 178 | ], 179 | 'outputs': [ 180 | { 181 | 'assetId': '602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7', 182 | 'scriptHash': 'e707714512577b42f9a011f8b870625429f93573', 183 | 'value': 1e-08 184 | } 185 | ], 186 | 'scripts': [], 187 | 'script': '....', 188 | 'gas': 0 189 | }, 190 | 'script_params': { 191 | 'scriptHash': 'a195c1549e7da61b8da315765a790ac7e7633b82', 192 | 'operation': 'cancelOffer', 193 | 'args': [ 194 | '923ebd865a6c5db39bd67403c1677f29d3949d9d8d9a2eb36a56dabd2dd83068' 195 | ] 196 | } 197 | } 198 | 199 | :param order_id: The order ID of the open transaction on the order book that you want to cancel. 200 | :type order_id: str 201 | :param private_key: The KeyPair that will be used to sign the transaction sent to the blockchain. 202 | :type private_key: KeyPair 203 | :return: Dictionary that contains the cancellation request along with blockchain transaction information. 204 | """ 205 | cancellation_params = { 206 | "order_id": order_id, 207 | "timestamp": get_epoch_milliseconds() 208 | } 209 | api_params = self.sign_create_cancellation_function[self.blockchain](cancellation_params, private_key) 210 | return self.request.post(path='/cancellations', json_data=api_params) 211 | 212 | def execute_cancellation(self, cancellation_params, private_key): 213 | """ 214 | This function executes the order created before it and signs the transaction to be submitted to the blockchain. 215 | Execution of this function is as follows:: 216 | 217 | execute_cancellation(cancellation_params=create_cancellation, private_key=kp) 218 | 219 | The expected return result for this function is as follows:: 220 | 221 | { 222 | 'id': 'b8e617d5-f5ed-4600-b8f2-7d370d837750', 223 | 'blockchain': 'neo', 224 | 'contract_hash': 'a195c1549e7da61b8da315765a790ac7e7633b82', 225 | 'address': 'fea2b883725ef2d194c9060f606cd0a0468a2c59', 226 | 'side': 'buy', 227 | 'offer_asset_id': 'c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b', 228 | 'want_asset_id': 'ab38352559b8b203bde5fddfa0b07d8b2525e132', 229 | 'offer_amount': '2000000', 230 | 'want_amount': '10000000000', 231 | 'transfer_amount': '0', 232 | 'priority_gas_amount': '0', 233 | 'use_native_token': True, 234 | 'native_fee_transfer_amount': 0, 235 | 'deposit_txn': None, 236 | 'created_at': '2018-08-05T11:16:47.021Z', 237 | 'status': 'processed', 238 | 'fills': [], 239 | 'makes': [ 240 | { 241 | 'id': '6b9f40de-f9bb-46b6-9434-d281f8c06b74', 242 | 'offer_hash': '6830d82dbdda566ab32e9a8d9d9d94d3297f67c10374d69bb35d6c5a86bd3e92', 243 | 'available_amount': '0', 244 | 'offer_asset_id': 'c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b', 245 | 'offer_amount': '2000000', 246 | 'want_asset_id': 'ab38352559b8b203bde5fddfa0b07d8b2525e132', 247 | 'want_amount': '10000000000', 248 | 'filled_amount': '0.0', 249 | 'txn': None, 250 | 'cancel_txn': None, 251 | 'price': '0.0002', 252 | 'status': 'cancelling', 253 | 'created_at': '2018-08-05T11:16:47.036Z', 254 | 'transaction_hash': 'e5b08c4a55c7494f1ec7dd93ac2bb2b4e84e77dec9e00e91be1d520cb818c415', 255 | 'trades': [] 256 | } 257 | ] 258 | } 259 | 260 | :param cancellation_params: Parameters generated from the Switcheo API to cancel an order on the order book. 261 | :type cancellation_params: dict 262 | :param private_key: The KeyPair that will be used to sign the transaction sent to the blockchain. 263 | :type private_key: KeyPair 264 | :return: Dictionary of the transaction details and state after sending the signed transaction to the blockchain. 265 | """ 266 | cancellation_id = cancellation_params['id'] 267 | api_params = self.sign_execute_cancellation_function[self.blockchain](cancellation_params, private_key) 268 | return self.request.post(path='/cancellations/{}/broadcast'.format(cancellation_id), json_data=api_params) 269 | 270 | def deposit(self, asset, amount, private_key): 271 | """ 272 | This function is a wrapper function around the create and execute deposit functions to help make this 273 | processes simpler for the end user by combining these requests in 1 step. 274 | Execution of this function is as follows:: 275 | 276 | deposit(asset="SWTH", amount=1.1, private_key=KeyPair) 277 | deposit(asset="SWTH", amount=1.1, private_key=eth_private_key) 278 | 279 | The expected return result for this function is the same as the execute_deposit function:: 280 | 281 | { 282 | 'result': 'ok' 283 | } 284 | 285 | :param asset: Symbol or Script Hash of asset ID from the available products. 286 | :type asset: str 287 | :param amount: The amount of coins/tokens to be deposited. 288 | :type amount: float 289 | :param private_key: The Private Key (ETH) or KeyPair (NEO) for the wallet being used to sign deposit message. 290 | :type private_key: KeyPair or str 291 | :return: Dictionary with the result status of the deposit attempt. 292 | """ 293 | create_deposit = self.create_deposit(asset=asset, amount=amount, private_key=private_key) 294 | return self.execute_deposit(deposit_params=create_deposit, private_key=private_key) 295 | 296 | def create_deposit(self, asset, amount, private_key): 297 | """ 298 | Function to create a deposit request by generating a transaction request from the Switcheo API. 299 | Execution of this function is as follows:: 300 | 301 | create_deposit(asset="SWTH", amount=1.1, private_key=KeyPair) 302 | create_deposit(asset="ETH", amount=1.1, private_key=eth_private_key) 303 | 304 | The expected return result for this function is as follows:: 305 | 306 | { 307 | 'id': '768e2079-1504-4dad-b688-7e1e99ec0a24', 308 | 'transaction': { 309 | 'hash': '72b74c96b9174e9b9e1b216f7e8f21a6475e6541876a62614df7c1998c6e8376', 310 | 'sha256': '2109cbb5eea67a06f5dd8663e10fcd1128e28df5721a25d993e05fe2097c34f3', 311 | 'type': 209, 312 | 'version': 1, 313 | 'attributes': [ 314 | { 315 | 'usage': 32, 316 | 'data': '592c8a46a0d06c600f06c994d1f25e7283b8a2fe' 317 | } 318 | ], 319 | 'inputs': [ 320 | { 321 | 'prevHash': 'f09b3b697c580d1730cd360da5e1f0beeae00827eb2f0055cbc85a5a4dadd8ea', 322 | 'prevIndex': 0 323 | }, { 324 | 'prevHash': 'c858e4d2af1e1525fa974fb2b1678caca1f81a5056513f922789594939ff713d', 325 | 'prevIndex': 31 326 | } 327 | ], 328 | 'outputs': [ 329 | { 330 | 'assetId': '602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7', 331 | 'scriptHash': 'e707714512577b42f9a011f8b870625429f93573', 332 | 'value': 1e-08 333 | } 334 | ], 335 | 'scripts': [], 336 | 'script': '....', 337 | 'gas': 0 338 | }, 339 | 'script_params': { 340 | 'scriptHash': 'a195c1549e7da61b8da315765a790ac7e7633b82', 341 | 'operation': 'deposit', 342 | 'args': [ 343 | '592c8a46a0d06c600f06c994d1f25e7283b8a2fe', 344 | '32e125258b7db0a0dffde5bd03b2b859253538ab', 345 | 100000000 346 | ] 347 | } 348 | } 349 | 350 | :param asset: Symbol or Script Hash of asset ID from the available products. 351 | :type asset: str 352 | :param amount: The amount of coins/tokens to be deposited. 353 | :type amount: float 354 | :param private_key: The Private Key (ETH) or KeyPair (NEO) for the wallet being used to sign deposit message. 355 | :type private_key: KeyPair or str 356 | :return: Dictionary response of signed deposit request that is ready to be executed on the specified blockchain. 357 | """ 358 | signable_params = { 359 | 'blockchain': self.blockchain, 360 | 'asset_id': asset, 361 | 'amount': str(self.blockchain_amount[self.blockchain](amount)), 362 | 'timestamp': get_epoch_milliseconds(), 363 | 'contract_hash': self.contract_hash 364 | } 365 | api_params = self.sign_create_deposit_function[self.blockchain](signable_params, private_key) 366 | return self.request.post(path='/deposits', json_data=api_params) 367 | 368 | def execute_deposit(self, deposit_params, private_key): 369 | """ 370 | Function to execute the deposit request by signing the transaction generated by the create deposit function. 371 | Execution of this function is as follows:: 372 | 373 | execute_deposit(deposit_params=create_deposit, private_key=KeyPair) 374 | execute_deposit(deposit_params=create_deposit, private_key=eth_private_key) 375 | 376 | The expected return result for this function is as follows:: 377 | 378 | { 379 | 'result': 'ok' 380 | } 381 | 382 | :param deposit_params: Parameters from the API to be signed and deposited to the Switcheo Smart Contract. 383 | :type deposit_params: dict 384 | :param private_key: The Private Key (ETH) or KeyPair (NEO) for the wallet being used to sign deposit message. 385 | :type private_key: KeyPair or str 386 | :return: Dictionary with the result status of the deposit attempt. 387 | """ 388 | deposit_id = deposit_params['id'] 389 | api_params = self.sign_execute_deposit_function[self.blockchain](deposit_params, private_key) 390 | return self.request.post(path='/deposits/{}/broadcast'.format(deposit_id), json_data=api_params) 391 | 392 | 393 | def order(self, pair, side, private_key, price=None, quantity=None, use_native_token=True, 394 | order_type="limit", offer_amount=None, receiving_address=None, worst_acceptable_price=None): 395 | """ 396 | This function is a wrapper function around the create and execute order functions to help make this processes 397 | simpler for the end user by combining these requests in 1 step. Trading can take place on normal (spot) markets 398 | and atomic swap markets. The required arguments differ between those cases. 399 | Execution of this function for spot- and swap-markets respectively is as follows:: 400 | 401 | order(pair="SWTH_NEO", side="buy", 402 | price=0.0002, quantity=100, 403 | private_key=kp, use_native_token=True, order_type="limit") 404 | 405 | order(pair="SWTH_ETH", side="buy", 406 | offer_amount=0.1, receiving_address=fea2b883725ef2d194c9060f606cd0a0468a2c59, 407 | private_key=kp, use_native_token=True, order_type="limit") 408 | 409 | The expected return result for this function is the same as the execute_order function:: 410 | 411 | { 412 | 'id': '4e6a59fd-d750-4332-aaf0-f2babfa8ad67', 413 | 'blockchain': 'neo', 414 | 'contract_hash': 'a195c1549e7da61b8da315765a790ac7e7633b82', 415 | 'address': 'fea2b883725ef2d194c9060f606cd0a0468a2c59', 416 | 'side': 'buy', 417 | 'offer_asset_id': 'c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b', 418 | 'want_asset_id': 'ab38352559b8b203bde5fddfa0b07d8b2525e132', 419 | 'offer_amount': '2000000', 420 | 'want_amount': '10000000000', 421 | 'transfer_amount': '0', 422 | 'priority_gas_amount': '0', 423 | 'use_native_token': True, 424 | 'native_fee_transfer_amount': 0, 425 | 'deposit_txn': None, 426 | 'created_at': '2018-08-05T10:38:37.714Z', 427 | 'status': 'processed', 428 | 'fills': [], 429 | 'makes': [ 430 | { 431 | 'id': 'e30a7fdf-779c-4623-8f92-8a961450d843', 432 | 'offer_hash': 'b45ddfb97ade5e0363d9e707dac9ad1c530448db263e86494225a0025006f968', 433 | 'available_amount': '2000000', 434 | 'offer_asset_id': 'c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b', 435 | 'offer_amount': '2000000', 436 | 'want_asset_id': 'ab38352559b8b203bde5fddfa0b07d8b2525e132', 437 | 'want_amount': '10000000000', 438 | 'filled_amount': '0.0', 439 | 'txn': None, 440 | 'cancel_txn': None, 441 | 'price': '0.0002', 442 | 'status': 'confirming', 443 | 'created_at': '2018-08-05T10:38:37.731Z', 444 | 'transaction_hash': '5c4cb1e73b9f2e608b6e768e0654649a4d15e08a7fe63fc536c454fa563a2f0f', 445 | 'trades': [] 446 | } 447 | ] 448 | } 449 | 450 | :param pair: The trading pair this order is being submitted for. 451 | :type pair: str 452 | :param side: The side of the trade being submitted i.e. buy or sell 453 | :type side: str 454 | :param price: The price target for this trade. 455 | :type price: float 456 | :param quantity: The amount of the asset being exchanged in the trade. 457 | :type quantity: float 458 | :param private_key: The Private Key (ETH) or KeyPair (NEO) for the wallet being used to sign deposit message. 459 | :type private_key: KeyPair or str 460 | :param use_native_token: Flag to indicate whether or not to pay fees with the Switcheo native token. 461 | :type use_native_token: bool 462 | :param order_type: The type of order being submitted, currently this can only be a limit order. 463 | :type order_type: str 464 | :return: Dictionary of the transaction on the order book. 465 | :type offer_amount: str 466 | :return: The amount of the asset that is offered in the swap trade. 467 | :type receiving_address: 468 | :return: The address the tokens received in the swap are sent to. 469 | :type worst_acceptable_price: 470 | :return: The lower or upper price limit for which the trade will be performed 471 | """ 472 | create_order = self.create_order(private_key=private_key, pair=pair, side=side, price=price, 473 | quantity=quantity, use_native_token=use_native_token, 474 | order_type=order_type, offer_amount=offer_amount, 475 | receiving_address=receiving_address, worst_acceptable_price=worst_acceptable_price) 476 | return self.execute_order(order_params=create_order, private_key=private_key) 477 | 478 | def create_order(self, pair, side, private_key, price=None, quantity=None, use_native_token=True, 479 | order_type="limit", otc_address=None, offer_amount=None, receiving_address=None, 480 | worst_acceptable_price=None): 481 | """ 482 | Function to create an order for the trade pair and details requested. 483 | Execution of this function is as follows:: 484 | 485 | create_order(pair="SWTH_NEO", side="buy", price=0.0002, quantity=100, private_key=kp, 486 | use_native_token=True, order_type="limit") 487 | 488 | The expected return result for this function is as follows:: 489 | 490 | { 491 | 'id': '4e6a59fd-d750-4332-aaf0-f2babfa8ad67', 492 | 'blockchain': 'neo', 493 | 'contract_hash': 'a195c1549e7da61b8da315765a790ac7e7633b82', 494 | 'address': 'fea2b883725ef2d194c9060f606cd0a0468a2c59', 495 | 'side': 'buy', 496 | 'offer_asset_id': 'c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b', 497 | 'want_asset_id': 'ab38352559b8b203bde5fddfa0b07d8b2525e132', 498 | 'offer_amount': '2000000', 499 | 'want_amount': '10000000000', 500 | 'transfer_amount': '0', 501 | 'priority_gas_amount': '0', 502 | 'use_native_token': True, 503 | 'native_fee_transfer_amount': 0, 504 | 'deposit_txn': None, 505 | 'created_at': '2018-08-05T10:38:37.714Z', 506 | 'status': 'pending', 507 | 'fills': [], 508 | 'makes': [ 509 | { 510 | 'id': 'e30a7fdf-779c-4623-8f92-8a961450d843', 511 | 'offer_hash': None, 512 | 'available_amount': None, 513 | 'offer_asset_id': 'c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b', 514 | 'offer_amount': '2000000', 515 | 'want_asset_id': 'ab38352559b8b203bde5fddfa0b07d8b2525e132', 516 | 'want_amount': '10000000000', 517 | 'filled_amount': None, 518 | 'txn': { 519 | 'offerHash': 'b45ddfb97ade5e0363d9e707dac9ad1c530448db263e86494225a0025006f968', 520 | 'hash': '5c4cb1e73b9f2e608b6e768e0654649a4d15e08a7fe63fc536c454fa563a2f0f', 521 | 'sha256': 'f0b70640627947584a2976edeb055a124ae85594db76453532b893c05618e6ca', 522 | 'invoke': { 523 | 'scriptHash': 'a195c1549e7da61b8da315765a790ac7e7633b82', 524 | 'operation': 'makeOffer', 525 | 'args': [ 526 | '592c8a46a0d06c600f06c994d1f25e7283b8a2fe', 527 | '9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5', 528 | 2000000, 529 | '32e125258b7db0a0dffde5bd03b2b859253538ab', 530 | 10000000000, 531 | '65333061376664662d373739632d343632332d386639322d386139363134353064383433' 532 | ] 533 | }, 534 | 'type': 209, 535 | 'version': 1, 536 | 'attributes': [ 537 | { 538 | 'usage': 32, 539 | 'data': '592c8a46a0d06c600f06c994d1f25e7283b8a2fe' 540 | } 541 | ], 542 | 'inputs': [ 543 | { 544 | 'prevHash': '0fcfd792a9d20a7795255d1d3d3927f5968b9953e80d16ffd222656edf8fedbc', 545 | 'prevIndex': 0 546 | }, { 547 | 'prevHash': 'c858e4d2af1e1525fa974fb2b1678caca1f81a5056513f922789594939ff713d', 548 | 'prevIndex': 35 549 | } 550 | ], 551 | 'outputs': [ 552 | { 553 | 'assetId': '602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7', 554 | 'scriptHash': 'e707714512577b42f9a011f8b870625429f93573', 555 | 'value': 1e-08 556 | } 557 | ], 558 | 'scripts': [], 559 | 'script': '....', 560 | 'gas': 0 561 | }, 562 | 'cancel_txn': None, 563 | 'price': '0.0002', 564 | 'status': 'pending', 565 | 'created_at': '2018-08-05T10:38:37.731Z', 566 | 'transaction_hash': '5c4cb1e73b9f2e608b6e768e0654649a4d15e08a7fe63fc536c454fa563a2f0f', 567 | 'trades': [] 568 | } 569 | ] 570 | } 571 | 572 | :param pair: The trading pair this order is being submitted for. 573 | :type pair: str 574 | :param side: The side of the trade being submitted i.e. buy or sell 575 | :type side: str 576 | :param price: The price target for this trade. 577 | :type price: float 578 | :param quantity: The amount of the asset being exchanged in the trade. 579 | :type quantity: float 580 | :param private_key: The Private Key (ETH) or KeyPair (NEO) for the wallet being used to sign deposit message. 581 | :type private_key: KeyPair or str 582 | :param use_native_token: Flag to indicate whether or not to pay fees with the Switcheo native token. 583 | :type use_native_token: bool 584 | :param order_type: The type of order being submitted, currently this can only be a limit order. 585 | :type order_type: str 586 | :param otc_address: The address to trade with for Over the Counter exchanges. 587 | :type otc_address: str 588 | :return: Dictionary of order details to specify which parts of the trade will be filled (taker) or open (maker) 589 | :type offer_amount: str 590 | :return: The amount of the asset that is offered in the swap trade. 591 | :type receiving_address: 592 | :return: The address the tokens received in the swap are sent to. 593 | :type worst_acceptable_price: 594 | :return: The lower or upper price limit for which the trade will be performed 595 | 596 | """ 597 | if side.lower() not in ["buy", "sell"]: 598 | raise ValueError("Allowed trade types are buy or sell, you entered {}".format(side.lower())) 599 | if order_type.lower() not in ["limit", "market", "otc"]: 600 | raise ValueError("Allowed order type is limit, you entered {}".format(order_type.lower())) 601 | if order_type.lower() == "otc" and otc_address is None: 602 | raise ValueError("OTC Address is required when trade type is otc (over the counter).") 603 | 604 | order_params = { 605 | "blockchain": self.blockchain, 606 | "pair": pair, 607 | "side": side, 608 | "use_native_tokens": use_native_token, 609 | "timestamp": get_epoch_milliseconds(), 610 | "contract_hash": self.contract_hash 611 | } 612 | 613 | if not offer_amount is None: 614 | order_params["price"] = None 615 | order_params["offer_amount"] = str(self.blockchain_amount[self.blockchain](offer_amount)) 616 | order_params["receiving_address"] = receiving_address 617 | order_params["worst_acceptable_price"] = worst_acceptable_price 618 | order_params["order_type"] = "market" 619 | else: 620 | order_params["price"] = '{:.8f}'.format(price) if order_type.lower() != "market" else None 621 | order_params["quantity"] = str(self.blockchain_amount[self.blockchain](quantity)) 622 | order_params["order_type"] = order_type 623 | 624 | api_params = self.sign_create_order_function[self.blockchain](order_params, private_key) 625 | return self.request.post(path='/orders', json_data=api_params) 626 | 627 | def execute_order(self, order_params, private_key): 628 | """ 629 | This function executes the order created before it and signs the transaction to be submitted to the blockchain. 630 | Execution of this function is as follows:: 631 | 632 | execute_order(order_params=create_order, private_key=kp) 633 | 634 | The expected return result for this function is the same as the execute_order function:: 635 | 636 | { 637 | 'id': '4e6a59fd-d750-4332-aaf0-f2babfa8ad67', 638 | 'blockchain': 'neo', 639 | 'contract_hash': 'a195c1549e7da61b8da315765a790ac7e7633b82', 640 | 'address': 'fea2b883725ef2d194c9060f606cd0a0468a2c59', 641 | 'side': 'buy', 642 | 'offer_asset_id': 'c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b', 643 | 'want_asset_id': 'ab38352559b8b203bde5fddfa0b07d8b2525e132', 644 | 'offer_amount': '2000000', 645 | 'want_amount': '10000000000', 646 | 'transfer_amount': '0', 647 | 'priority_gas_amount': '0', 648 | 'use_native_token': True, 649 | 'native_fee_transfer_amount': 0, 650 | 'deposit_txn': None, 651 | 'created_at': '2018-08-05T10:38:37.714Z', 652 | 'status': 'processed', 653 | 'fills': [], 654 | 'makes': [ 655 | { 656 | 'id': 'e30a7fdf-779c-4623-8f92-8a961450d843', 657 | 'offer_hash': 'b45ddfb97ade5e0363d9e707dac9ad1c530448db263e86494225a0025006f968', 658 | 'available_amount': '2000000', 659 | 'offer_asset_id': 'c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b', 660 | 'offer_amount': '2000000', 661 | 'want_asset_id': 'ab38352559b8b203bde5fddfa0b07d8b2525e132', 662 | 'want_amount': '10000000000', 663 | 'filled_amount': '0.0', 664 | 'txn': None, 665 | 'cancel_txn': None, 666 | 'price': '0.0002', 667 | 'status': 'confirming', 668 | 'created_at': '2018-08-05T10:38:37.731Z', 669 | 'transaction_hash': '5c4cb1e73b9f2e608b6e768e0654649a4d15e08a7fe63fc536c454fa563a2f0f', 670 | 'trades': [] 671 | } 672 | ] 673 | } 674 | 675 | :param order_params: Dictionary generated from the create order function. 676 | :type order_params: dict 677 | :param private_key: The Private Key (ETH) or KeyPair (NEO) for the wallet being used to sign deposit message. 678 | :type private_key: KeyPair or str 679 | :return: Dictionary of the transaction on the order book. 680 | """ 681 | order_id = order_params['id'] 682 | api_params = self.sign_execute_order_function[self.blockchain](order_params, private_key) 683 | return self.request.post(path='/orders/{}/broadcast'.format(order_id), json_data=api_params) 684 | 685 | def withdrawal(self, asset, amount, private_key): 686 | """ 687 | This function is a wrapper function around the create and execute withdrawal functions to help make this 688 | processes simpler for the end user by combining these requests in 1 step. 689 | Execution of this function is as follows:: 690 | 691 | withdrawal(asset="SWTH", amount=1.1, private_key=kp)) 692 | 693 | The expected return result for this function is the same as the execute_withdrawal function:: 694 | 695 | { 696 | 'event_type': 'withdrawal', 697 | 'amount': -100000, 698 | 'asset_id': 'ab38352559b8b203bde5fddfa0b07d8b2525e132', 699 | 'status': 'confirming', 700 | 'id': '96e5f797-435b-40ab-9085-4e95c6749218', 701 | 'blockchain': 'neo', 702 | 'reason_code': 9, 703 | 'address': 'fea2b883725ef2d194c9060f606cd0a0468a2c59', 704 | 'transaction_hash': None, 705 | 'created_at': '2018-08-05T10:03:58.885Z', 706 | 'updated_at': '2018-08-05T10:03:59.828Z', 707 | 'contract_hash': 'a195c1549e7da61b8da315765a790ac7e7633b82' 708 | } 709 | 710 | :param asset: Script Hash of asset ID from the available products. 711 | :type asset: str 712 | :param amount: The amount of coins/tokens to be withdrawn. 713 | :type amount: float 714 | :param private_key: The Private Key (ETH) or KeyPair (NEO) for the wallet being used to sign deposit message. 715 | :type private_key: KeyPair or str 716 | :return: Dictionary with the status of the withdrawal request and blockchain details. 717 | """ 718 | create_withdrawal = self.create_withdrawal(asset=asset, amount=amount, private_key=private_key) 719 | return self.execute_withdrawal(withdrawal_params=create_withdrawal, private_key=private_key) 720 | 721 | def create_withdrawal(self, asset, amount, private_key): 722 | """ 723 | Function to create a withdrawal request by generating a withdrawal ID request from the Switcheo API. 724 | Execution of this function is as follows:: 725 | 726 | create_withdrawal(asset="SWTH", amount=1.1, private_key=kp) 727 | 728 | The expected return result for this function is as follows:: 729 | 730 | { 731 | 'id': 'a5a4d396-fa9f-4191-bf50-39a3d06d5e0d' 732 | } 733 | 734 | :param asset: Script Hash of asset ID from the available products. 735 | :type asset: str 736 | :param amount: The amount of coins/tokens to be withdrawn. 737 | :type amount: float 738 | :param private_key: The Private Key (ETH) or KeyPair (NEO) for the wallet being used to sign deposit message. 739 | :type private_key: KeyPair or str 740 | :return: Dictionary with the withdrawal ID generated by the Switcheo API. 741 | """ 742 | signable_params = { 743 | 'blockchain': self.blockchain, 744 | 'asset_id': asset, 745 | 'amount': str(self.blockchain_amount[self.blockchain](amount)), 746 | 'timestamp': get_epoch_milliseconds(), 747 | 'contract_hash': self.contract_hash 748 | } 749 | api_params = self.sign_create_withdrawal_function[self.blockchain](signable_params, private_key) 750 | return self.request.post(path='/withdrawals', json_data=api_params) 751 | 752 | def execute_withdrawal(self, withdrawal_params, private_key): 753 | """ 754 | This function is to sign the message generated from the create withdrawal function and submit it to the 755 | blockchain for transfer from the smart contract to the owners address. 756 | Execution of this function is as follows:: 757 | 758 | execute_withdrawal(withdrawal_params=create_withdrawal, private_key=kp) 759 | 760 | The expected return result for this function is as follows:: 761 | 762 | { 763 | 'event_type': 'withdrawal', 764 | 'amount': -100000, 765 | 'asset_id': 'ab38352559b8b203bde5fddfa0b07d8b2525e132', 766 | 'status': 'confirming', 767 | 'id': '96e5f797-435b-40ab-9085-4e95c6749218', 768 | 'blockchain': 'neo', 769 | 'reason_code': 9, 770 | 'address': 'fea2b883725ef2d194c9060f606cd0a0468a2c59', 771 | 'transaction_hash': None, 772 | 'created_at': '2018-08-05T10:03:58.885Z', 773 | 'updated_at': '2018-08-05T10:03:59.828Z', 774 | 'contract_hash': 'a195c1549e7da61b8da315765a790ac7e7633b82' 775 | } 776 | 777 | :param withdrawal_params: Dictionary from the create withdrawal function to sign and submit to the blockchain. 778 | :type withdrawal_params: dict 779 | :param private_key: The Private Key (ETH) or KeyPair (NEO) for the wallet being used to sign deposit message. 780 | :type private_key: KeyPair or str 781 | :return: Dictionary with the status of the withdrawal request and blockchain transaction details. 782 | """ 783 | withdrawal_id = withdrawal_params['id'] 784 | api_params = self.sign_execute_withdrawal_function[self.blockchain](withdrawal_params, private_key) 785 | return self.request.post(path='/withdrawals/{}/broadcast'.format(withdrawal_id), json_data=api_params) 786 | -------------------------------------------------------------------------------- /switcheo/ethereum/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KeithSSmith/switcheo-python/d9bb29ee48b756d469046b7871bd7f627577dc93/switcheo/ethereum/__init__.py -------------------------------------------------------------------------------- /switcheo/ethereum/signatures.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | """ 3 | Description: 4 | Functions for signing data to send to the Switcheo API by using Ethereum Utilities to cryptographically sign 5 | data that gets authenticated on the Ethereum network. 6 | Usage: 7 | from switcheo.ethereum.signatures import 8 | """ 9 | 10 | import binascii 11 | from switcheo.utils import stringify_message 12 | from switcheo.ethereum.utils import sign_txn_array 13 | from eth_utils import to_checksum_address, to_normalized_address 14 | from eth_account.messages import defunct_hash_message 15 | from eth_account.account import Account 16 | from web3 import Web3, HTTPProvider 17 | 18 | 19 | def sign_create_cancellation(cancellation_params, private_key): 20 | """ 21 | Function to sign the parameters required to create a cancellation request from the Switcheo Exchange. 22 | Execution of this function is as follows:: 23 | 24 | sign_create_cancellation(cancellation_params=signable_params, private_key=eth_private_key) 25 | 26 | The expected return result for this function is as follows:: 27 | 28 | { 29 | 'order_id': '3125550a-04f9-4475-808b-42b5f89d6693', 30 | 'timestamp': 1542088842108, 31 | 'address': '0x32c46323b51c977814e05ef5e258ee4da0e4c3c3', 32 | 'signature': 'dac70ca711bcfbeefbdead2158ef8b15fab1a1....' 33 | } 34 | 35 | :param cancellation_params: Dictionary with Order ID and timestamp to sign for creating the cancellation. 36 | :type cancellation_params: dict 37 | :param private_key: The Ethereum private key to sign the deposit parameters. 38 | :type private_key: str 39 | :return: Dictionary of signed message to send to the Switcheo API. 40 | """ 41 | hash_message = defunct_hash_message(text=stringify_message(cancellation_params)) 42 | hex_message = binascii.hexlify(hash_message).decode() 43 | signed_message = binascii.hexlify(Account.signHash(hex_message, private_key=private_key)['signature']).decode() 44 | create_params = cancellation_params.copy() 45 | create_params['address'] = to_normalized_address(Account.privateKeyToAccount(private_key=private_key).address) 46 | create_params['signature'] = signed_message 47 | return create_params 48 | 49 | 50 | def sign_execute_cancellation(cancellation_params, private_key): 51 | """ 52 | Function to sign the parameters required to execute a cancellation request on the Switcheo Exchange. 53 | Execution of this function is as follows:: 54 | 55 | sign_execute_cancellation(cancellation_params=signable_params, private_key=eth_private_key) 56 | 57 | The expected return result for this function is as follows:: 58 | 59 | { 60 | 'signature': '0x65986ed2cb631d4999ce8b9c895a43f....' 61 | } 62 | 63 | :param cancellation_params: Parameters the Switcheo Exchange returns from the create cancellation. 64 | :type cancellation_params: dict 65 | :param private_key: The Ethereum private key to sign the deposit parameters. 66 | :type private_key: str 67 | :return: Dictionary of signed message to send to the Switcheo API. 68 | """ 69 | cancellation_sha256 = cancellation_params['transaction']['sha256'] 70 | signed_sha256 = binascii.hexlify( 71 | Account.signHash(cancellation_sha256, private_key=private_key)['signature']).decode() 72 | return {'signature': '0x' + signed_sha256} 73 | 74 | 75 | def sign_create_deposit(deposit_params, private_key): 76 | """ 77 | Function to sign the deposit parameters required by the Switcheo API. 78 | Execution of this function is as follows:: 79 | 80 | sign_create_deposit(deposit_params=signable_params, private_key=eth_private_key) 81 | 82 | The expected return result for this function is as follows:: 83 | 84 | { 85 | 'blockchain': 'eth', 86 | 'asset_id': 'ETH', 87 | 'amount': '10000000000000000', 88 | 'timestamp': 1542089346249, 89 | 'contract_hash': '0x607af5164d95bd293dbe2b994c7d8aef6bec03bf', 90 | 'address': '0x32c46323b51c977814e05ef5e258ee4da0e4c3c3', 91 | 'signature': 'd4b8491d6514bff28b9f2caa440f51a93f31d....' 92 | } 93 | 94 | :param deposit_params: Parameters needed to deposit to the Switcheo API and signed in this function. 95 | :type deposit_params: dict 96 | :param private_key: The Ethereum private key to sign the deposit parameters. 97 | :type private_key: str 98 | :return: Dictionary of signed message to send to the Switcheo API. 99 | """ 100 | hash_message = defunct_hash_message(text=stringify_message(deposit_params)) 101 | hex_message = binascii.hexlify(hash_message).decode() 102 | signed_message = binascii.hexlify(Account.signHash(hex_message, private_key=private_key)['signature']).decode() 103 | create_params = deposit_params.copy() 104 | create_params['address'] = to_normalized_address(Account.privateKeyToAccount(private_key=private_key).address) 105 | create_params['signature'] = signed_message 106 | return create_params 107 | 108 | 109 | def sign_execute_deposit(deposit_params, private_key, infura_url): 110 | """ 111 | Function to execute the deposit request by signing the transaction generated from the create deposit function. 112 | Execution of this function is as follows:: 113 | 114 | sign_execute_deposit(deposit_params=create_deposit, private_key=eth_private_key) 115 | 116 | The expected return result for this function is as follows:: 117 | 118 | { 119 | 'transaction_hash': '0xcf3ea5d1821544e1686fbcb1f49d423b9ea9f42772ff9ecdaf615616d780fa75' 120 | } 121 | 122 | :param deposit_params: The parameters generated by the create function that now requires a signature. 123 | :type deposit_params: dict 124 | :param private_key: The Ethereum private key to sign the deposit parameters. 125 | :type private_key: str 126 | :param infura_url: The URL used to broadcast the deposit transaction to the Ethereum network. 127 | :type infura_url: str 128 | :return: Dictionary of the signed transaction to initiate the deposit of ETH via the Switcheo API. 129 | """ 130 | create_deposit_upper = deposit_params.copy() 131 | create_deposit_upper['transaction']['from'] = to_checksum_address(create_deposit_upper['transaction']['from']) 132 | create_deposit_upper['transaction']['to'] = to_checksum_address(create_deposit_upper['transaction']['to']) 133 | create_deposit_upper['transaction'].pop('sha256') 134 | signed_create_txn = Account.signTransaction(create_deposit_upper['transaction'], private_key=private_key) 135 | execute_signed_txn = binascii.hexlify(signed_create_txn['hash']).decode() 136 | 137 | # Broadcast transaction to Ethereum Network. 138 | Web3(HTTPProvider(infura_url)).eth.sendRawTransaction(signed_create_txn.rawTransaction) 139 | 140 | return {'transaction_hash': '0x' + execute_signed_txn} 141 | 142 | 143 | def sign_create_order(order_params, private_key): 144 | """ 145 | Function to sign the create order parameters and send to the Switcheo API. 146 | Execution of this function is as follows:: 147 | 148 | sign_create_order(order_params=signable_params, private_key=eth_private_key) 149 | 150 | The expected return result for this function is as follows:: 151 | 152 | { 153 | 'blockchain': 'eth', 154 | 'pair': 'JRC_ETH', 155 | 'side': 'buy', 156 | 'price': '0.00000003', 157 | 'want_amount': '3350000000000000000000000', 158 | 'use_native_tokens': False, 159 | 'order_type': 'limit', 160 | 'timestamp': 1542089785915, 161 | 'contract_hash': '0x607af5164d95bd293dbe2b994c7d8aef6bec03bf', 162 | 'signature': '536306a2f2aee499ffd6584027029ee585293b3686....', 163 | 'address': '0x32c46323b51c977814e05ef5e258ee4da0e4c3c3' 164 | } 165 | 166 | :param order_params: Parameters to create an order to be submitted to the Switcheo Order Book. 167 | :type order_params: dict 168 | :param private_key: The Ethereum private key to sign the deposit parameters. 169 | :type private_key: str 170 | :return: Dictionary of signed message to send to the Switcheo API. 171 | """ 172 | hash_message = defunct_hash_message(text=stringify_message(order_params)) 173 | hex_message = binascii.hexlify(hash_message).decode() 174 | create_params = order_params.copy() 175 | signed_message = binascii.hexlify(Account.signHash(hex_message, private_key=private_key)['signature']).decode() 176 | create_params['signature'] = signed_message 177 | create_params['address'] = to_normalized_address(Account.privateKeyToAccount(private_key=private_key).address) 178 | return create_params 179 | 180 | 181 | def sign_execute_order(order_params, private_key): 182 | """ 183 | Function to execute the order request by signing the transaction generated from the create order function. 184 | Execution of this function is as follows:: 185 | 186 | sign_execute_order(order_params=signable_params, private_key=eth_private_key) 187 | 188 | The expected return result for this function is as follows:: 189 | 190 | { 191 | 'signatures': { 192 | 'fill_groups': {}, 193 | 'fills': {}, 194 | 'makes': { 195 | '392cd607-27ed-4702-880d-eab8d67a4201': '0x5f62c585e0978454cc89aa3b86d3ea6afbd80fc521....' 196 | } 197 | } 198 | } 199 | 200 | :param order_params: The parameters generated by the create function that now require signing. 201 | :type order_params: dict 202 | :param private_key: The Ethereum private key to sign the deposit parameters. 203 | :type private_key: str 204 | :return: Dictionary of the signed transaction to place an order on the Switcheo Order Book. 205 | """ 206 | execute_params = { 207 | 'signatures': { 208 | 'fill_groups': sign_txn_array(messages=order_params['fill_groups'], private_key=private_key), 209 | 'fills': sign_txn_array(messages=order_params['fills'], private_key=private_key), 210 | 'makes': sign_txn_array(messages=order_params['makes'], private_key=private_key), 211 | } 212 | } 213 | return execute_params 214 | 215 | 216 | def sign_create_withdrawal(withdrawal_params, private_key): 217 | """ 218 | Function to create a withdrawal from the Switcheo Smart Contract. 219 | Execution of this function is as follows:: 220 | 221 | sign_create_withdrawal(withdrawal_params=signable_params, private_key=eth_private_key) 222 | 223 | The expected return result for this function is as follows:: 224 | 225 | { 226 | 'blockchain': 'eth', 227 | 'asset_id': 'ETH', 228 | 'amount': '10000000000000000', 229 | 'timestamp': 1542090476102, 230 | 'contract_hash': '0x607af5164d95bd293dbe2b994c7d8aef6bec03bf', 231 | 'address': '0x32c46323b51c977814e05ef5e258ee4da0e4c3c3', 232 | 'signature': '375ddce62e5b3676d5e94ebb9f9a8af5963b....' 233 | } 234 | 235 | :param withdrawal_params: The parameters to be signed and create a withdraw from Switcheo. 236 | :type withdrawal_params: dict 237 | :param private_key: The Ethereum private key to sign the deposit parameters. 238 | :type private_key: str 239 | :return: Dictionary of the signed transaction to initiate the withdrawal of ETH via the Switcheo API. 240 | """ 241 | hash_message = defunct_hash_message(text=stringify_message(withdrawal_params)) 242 | hex_message = binascii.hexlify(hash_message).decode() 243 | signed_message = binascii.hexlify(Account.signHash(hex_message, private_key=private_key)['signature']).decode() 244 | create_params = withdrawal_params.copy() 245 | create_params['address'] = to_normalized_address(Account.privateKeyToAccount(private_key=private_key).address) 246 | create_params['signature'] = signed_message 247 | return create_params 248 | 249 | 250 | def sign_execute_withdrawal(withdrawal_params, private_key): 251 | """ 252 | Function to execute the withdrawal request by signing the transaction generated from the create withdrawal function. 253 | Execution of this function is as follows:: 254 | 255 | sign_execute_withdrawal(withdrawal_params=signable_params, private_key=eth_private_key) 256 | 257 | The expected return result for this function is as follows:: 258 | 259 | { 260 | 'signature': '0x33656f88b364d344e5b04f6aead01cdd3ac084489c39a9efe88c9873249bf1954525b1....' 261 | } 262 | 263 | :param withdrawal_params: The parameters generated by the create function that now require signing. 264 | :type withdrawal_params: dict 265 | :param private_key: The Ethereum private key to sign the deposit parameters. 266 | :type private_key: str 267 | :return: Dictionary of the signed transaction hash and initiate the withdrawal of ETH via the Switcheo API. 268 | """ 269 | withdrawal_sha256 = withdrawal_params['transaction']['sha256'] 270 | signed_sha256 = binascii.hexlify(Account.signHash(withdrawal_sha256, private_key=private_key)['signature']).decode() 271 | return {'signature': '0x' + signed_sha256} 272 | -------------------------------------------------------------------------------- /switcheo/ethereum/utils.py: -------------------------------------------------------------------------------- 1 | import binascii 2 | from eth_account.account import Account 3 | 4 | 5 | def sign_txn_array(messages, private_key): 6 | message_dict = {} 7 | for message in messages: 8 | signed_message =\ 9 | binascii.hexlify(Account.signHash(message['txn']['sha256'], private_key=private_key)['signature']).decode() 10 | message_dict[message['id']] = '0x' + signed_message 11 | return message_dict 12 | -------------------------------------------------------------------------------- /switcheo/neo/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KeithSSmith/switcheo-python/d9bb29ee48b756d469046b7871bd7f627577dc93/switcheo/neo/__init__.py -------------------------------------------------------------------------------- /switcheo/neo/signatures.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | """ 3 | Description: 4 | Functions for signing data to send to the Switcheo API by using NEO Utilities to cryptographically sign 5 | data that gets authenticated on the NEO network. 6 | Usage: 7 | from switcheo.neo.signatures import 8 | """ 9 | 10 | from switcheo.utils import get_epoch_milliseconds 11 | from switcheo.neo.utils import sign_message, sign_transaction, sign_txn_array, encode_message,\ 12 | neo_get_scripthash_from_private_key, private_key_to_hex 13 | 14 | 15 | def sign_create_cancellation(cancellation_params, key_pair): 16 | """ 17 | Function to sign the parameters required to create a cancellation request from the Switcheo Exchange. 18 | Execution of this function is as follows:: 19 | 20 | sign_create_cancellation(cancellation_params=signable_params, key_pair=key_pair) 21 | 22 | The expected return result for this function is as follows:: 23 | 24 | { 25 | 'order_id': 'aa647b95-d546-4d29-961e-bd62b18b07bf', 26 | 'timestamp': 1542092600331, 27 | 'address': 'fea2b883725ef2d194c9060f606cd0a0468a2c59', 28 | 'signature': '475bc3ecd2310201a3b5357b52b1866aaf5a5618932500e43503ebb....' 29 | } 30 | 31 | :param cancellation_params: Dictionary with Order ID and timestamp to sign for creating the cancellation. 32 | :type cancellation_params: dict 33 | :param key_pair: The KeyPair for the wallet being used to sign deposit message. 34 | :type key_pair: KeyPair 35 | :return: Dictionary of signed message to send to the Switcheo API. 36 | """ 37 | encoded_message = encode_message(cancellation_params) 38 | create_params = cancellation_params.copy() 39 | create_params['address'] = neo_get_scripthash_from_private_key(private_key=key_pair.PrivateKey).ToString() 40 | create_params['signature'] = sign_message(encoded_message=encoded_message, 41 | private_key_hex=private_key_to_hex(key_pair=key_pair)) 42 | return create_params 43 | 44 | 45 | def sign_execute_cancellation(cancellation_params, key_pair): 46 | """ 47 | Function to sign the parameters required to execute a cancellation request on the Switcheo Exchange. 48 | Execution of this function is as follows:: 49 | 50 | sign_execute_cancellation(cancellation_params=signable_params, key_pair=key_pair) 51 | 52 | The expected return result for this function is as follows:: 53 | 54 | { 55 | 'signature': '6a40d6c011b7517f8fd3f2d0de32dd486adfd1d424d06d56c80eb....' 56 | } 57 | 58 | :param cancellation_params: Parameters the Switcheo Exchange returns from the create cancellation. 59 | :type cancellation_params: dict 60 | :param key_pair: The KeyPair for the wallet being used to sign deposit message. 61 | :type key_pair: KeyPair 62 | :return: Dictionary of signed message to send to the Switcheo API. 63 | """ 64 | signature = sign_transaction(transaction=cancellation_params['transaction'], 65 | private_key_hex=private_key_to_hex(key_pair=key_pair)) 66 | return {'signature': signature} 67 | 68 | 69 | def sign_create_deposit(deposit_params, key_pair): 70 | """ 71 | Function to create a deposit request by generating a transaction request from the Switcheo API. 72 | Execution of this function is as follows:: 73 | 74 | sign_create_deposit(deposit_details=create_deposit, key_pair=key_pair) 75 | 76 | The expected return result for this function is as follows:: 77 | 78 | { 79 | 'blockchain': 'neo', 80 | 'asset_id': 'SWTH', 81 | 'amount': '100', 82 | 'timestamp': 1542091927575, 83 | 'contract_hash': 'a195c1549e7da61b8da315765a790ac7e7633b82', 84 | 'address': 'fea2b883725ef2d194c9060f606cd0a0468a2c59', 85 | 'signature': '24ef6c63964988a2efe5fe67f04f46fdc2f1504fb5....' 86 | } 87 | 88 | :param deposit_params: The parameters generated by the create deposit function that now requires signature. 89 | :type deposit_params: dict 90 | :param key_pair: The KeyPair for the wallet being used to sign deposit message. 91 | :type key_pair: KeyPair 92 | :return: Dictionary response of signed deposit request that is ready to be executed on the NEO blockchain. 93 | """ 94 | encoded_message = encode_message(deposit_params) 95 | create_params = deposit_params.copy() 96 | create_params['address'] = neo_get_scripthash_from_private_key(private_key=key_pair.PrivateKey).ToString() 97 | create_params['signature'] = sign_message(encoded_message=encoded_message, 98 | private_key_hex=private_key_to_hex(key_pair=key_pair)) 99 | return create_params 100 | 101 | 102 | def sign_execute_deposit(deposit_params, key_pair): 103 | """ 104 | Function to execute the deposit request by signing the transaction generated by the create deposit function. 105 | Execution of this function is as follows:: 106 | 107 | sign_execute_deposit(deposit_details=create_deposit, key_pair=key_pair) 108 | 109 | The expected return result for this function is as follows:: 110 | 111 | { 112 | 'signature': '3cc4a5cb7b7d50383e799add2ba35382b6f2f1b2e3b97802....' 113 | } 114 | 115 | :param deposit_params: The parameters generated by the create deposit function that now requires signature. 116 | :type deposit_params: dict 117 | :param key_pair: The KeyPair for the wallet being used to sign deposit message. 118 | :type key_pair: KeyPair 119 | :return: Dictionary with the result status of the deposit attempt. 120 | """ 121 | signature = sign_transaction(transaction=deposit_params['transaction'], 122 | private_key_hex=private_key_to_hex(key_pair=key_pair)) 123 | return {'signature': signature} 124 | 125 | 126 | def sign_create_order(order_params, key_pair): 127 | """ 128 | Function to sign the create order parameters and send to the Switcheo API. 129 | Execution of this function is as follows:: 130 | 131 | sign_create_order(order_params=signable_params, key_pair=key_pair) 132 | 133 | The expected return result for this function is as follows:: 134 | 135 | { 136 | 'blockchain': 'neo', 137 | 'pair': 'SWTH_NEO', 138 | 'side': 'buy', 139 | 'price': '0.00001000', 140 | 'want_amount': '1000000000000', 141 | 'use_native_tokens': True, 142 | 'order_type': 'limit', 143 | 'timestamp': 1542091535839, 144 | 'contract_hash': 'a195c1549e7da61b8da315765a790ac7e7633b82', 145 | 'address': 'fea2b883725ef2d194c9060f606cd0a0468a2c59', 146 | 'signature': '88e93c14a7d3c2cf30dec012ad5cb69f5ff26fe2a....' 147 | } 148 | 149 | :param order_params: Parameters to create an order to be submitted to the Switcheo Order Book. 150 | :type order_params: dict 151 | :param key_pair: The NEO key pair to be used to sign messages for the NEO Blockchain. 152 | :type key_pair: KeyPair 153 | :return: Dictionary of signed message to send to the Switcheo API. 154 | """ 155 | encoded_message = encode_message(order_params) 156 | create_params = order_params.copy() 157 | create_params['address'] = neo_get_scripthash_from_private_key(private_key=key_pair.PrivateKey).ToString() 158 | create_params['signature'] = sign_message(encoded_message=encoded_message, 159 | private_key_hex=private_key_to_hex(key_pair=key_pair)) 160 | return create_params 161 | 162 | 163 | def sign_execute_order(order_params, key_pair): 164 | """ 165 | Function to execute the order request by signing the transaction generated from the create order function. 166 | Execution of this function is as follows:: 167 | 168 | sign_execute_order(order_params=signable_params, key_pair=key_pair) 169 | 170 | The expected return result for this function is as follows:: 171 | 172 | { 173 | 'signatures': { 174 | 'fill_groups': {}, 175 | 'fills': {}, 176 | 'makes': { 177 | '952defd3-ad8a-4db3-bbd1-27d58ff6c7bd': '3f5aa331a731a808fe260502421cbb06ae3d5ea5ddfb1....' 178 | } 179 | } 180 | } 181 | 182 | :param order_params: The parameters generated by the create function that now require signing. 183 | :type order_params: dict 184 | :param key_pair: The NEO key pair to be used to sign messages for the NEO Blockchain. 185 | :type key_pair: KeyPair 186 | :return: Dictionary of the signed transaction to place an order on the Switcheo Order Book. 187 | """ 188 | execute_params = { 189 | 'signatures': { 190 | 'fill_groups': {}, 191 | 'fills': sign_txn_array(messages=order_params['fills'], 192 | private_key_hex=private_key_to_hex(key_pair=key_pair)), 193 | 'makes': sign_txn_array(messages=order_params['makes'], 194 | private_key_hex=private_key_to_hex(key_pair=key_pair)) 195 | } 196 | } 197 | return execute_params 198 | 199 | 200 | def sign_create_withdrawal(withdrawal_params, key_pair): 201 | """ 202 | Function to create the withdrawal request by signing the parameters necessary for withdrawal. 203 | Execution of this function is as follows:: 204 | 205 | sign_create_withdrawal(withdrawal_params=signable_params, private_key=eth_private_key) 206 | 207 | The expected return result for this function is as follows:: 208 | 209 | { 210 | 'blockchain': 'neo', 211 | 'asset_id': 'SWTH', 212 | 'amount': '100', 213 | 'timestamp': 1542090737236, 214 | 'contract_hash': 'a195c1549e7da61b8da315765a790ac7e7633b82', 215 | 'address': 'fea2b883725ef2d194c9060f606cd0a0468a2c59', 216 | 'signature': 'f66d604c0a80940bf70ce9e13c0fd47bc79de....' 217 | } 218 | 219 | :param withdrawal_params: Dictionary specifications for withdrawal from the Switcheo Smart Contract. 220 | :type withdrawal_params: dict 221 | :param key_pair: The NEO key pair to be used to sign messages for the NEO Blockchain. 222 | :type key_pair: KeyPair 223 | :return: Dictionary of parameters to be sent to the Switcheo API 224 | """ 225 | encoded_message = encode_message(withdrawal_params) 226 | create_params = withdrawal_params.copy() 227 | create_params['address'] = neo_get_scripthash_from_private_key(private_key=key_pair.PrivateKey).ToString() 228 | create_params['signature'] = sign_message(encoded_message=encoded_message, 229 | private_key_hex=private_key_to_hex(key_pair=key_pair)) 230 | return create_params 231 | 232 | 233 | def sign_execute_withdrawal(withdrawal_params, key_pair): 234 | """ 235 | Function to execute the withdrawal request by signing the transaction generated from the create withdrawal function. 236 | Execution of this function is as follows:: 237 | 238 | sign_execute_withdrawal(withdrawal_params=signable_params, private_key=eth_private_key) 239 | 240 | The expected return result for this function is as follows:: 241 | 242 | { 243 | 'id': '3e1c0802-b44e-4681-a94d-29c1dec2f518', 244 | 'timestamp': 1542090738192, 245 | 'signature': 'e05a7b7bd30eb85959d75ea634cee06ad35d96502a763ae40....' 246 | } 247 | 248 | :param withdrawal_params: Parameters passed from the create withdrawal function to be signed and confirmed. 249 | :type withdrawal_params: dict 250 | :param key_pair: The NEO key pair to be used to sign messages for the NEO Blockchain. 251 | :type key_pair: KeyPair 252 | :return: Dictionary of parameters to be sent to the Switcheo API 253 | """ 254 | withdrawal_id = withdrawal_params['id'] 255 | signable_params = { 256 | 'id': withdrawal_id, 257 | 'timestamp': get_epoch_milliseconds() 258 | } 259 | encoded_message = encode_message(signable_params) 260 | execute_params = signable_params.copy() 261 | execute_params['signature'] = sign_message(encoded_message=encoded_message, 262 | private_key_hex=private_key_to_hex(key_pair=key_pair)) 263 | return execute_params 264 | -------------------------------------------------------------------------------- /switcheo/neo/test_neo_utils.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from switcheo.neo.utils import create_offer_hash, encode_message, to_neo_asset_amount, private_key_to_hex, open_wallet,\ 3 | neo_get_scripthash_from_address, neo_get_address_from_scripthash, neo_get_scripthash_from_private_key,\ 4 | neo_get_public_key_from_private_key, sign_message, sign_transaction, sign_txn_array 5 | from neocore.KeyPair import KeyPair 6 | 7 | 8 | testnet_privatekey = b'p\xf6B\x89K\xc7=\xc5\x00\x13\xbem\x1d\xbe\x19\x8fC#~\xaf\x94X\xd1\x93\xc0\xb4\x16\xc58]\x97\x17' 9 | kp = KeyPair(priv_key=testnet_privatekey) 10 | testnet_privatekey_hexstring = '70f642894bc73dc50013be6d1dbe198f43237eaf9458d193c0b416c5385d9717' 11 | testnet_scripthash = 'fea2b883725ef2d194c9060f606cd0a0468a2c59' 12 | testnet_scripthash_uint160 = neo_get_scripthash_from_private_key(private_key=testnet_privatekey) 13 | testnet_address = 'APuP9GsSCPJKrexPe49afDV8CQYubZGWd8' 14 | testnet_publickey = '303231353534363535356234326164643737343933636332316462356461396639376163646666343966346433653739633239666363303361303661356539373662' 15 | 16 | message = 'This is a test.' 17 | encoded_message = '010001f0112254686973206973206120746573742e220000' 18 | json_message = {"name": "John Smith", "age": 27, "siblings": ["Jane", "Joe"]} 19 | json_encoded_message = '010001f0387b22616765223a32372c226e616d65223a224a6f686e20536d697468222c227369626c696e6773223a5b224a616e65222c224a6f65225d7d0000' 20 | 21 | transaction_dict = {'hash': '72b74c96b9174e9b9e1b216f7e8f21a6475e6541876a62614df7c1998c6e8376', 22 | 'sha256': '2109cbb5eea67a06f5dd8663e10fcd1128e28df5721a25d993e05fe2097c34f3', 23 | 'type': 209, 24 | 'version': 1, 25 | 'attributes': [{'usage': 32, 'data': '592c8a46a0d06c600f06c994d1f25e7283b8a2fe'}], 26 | 'inputs': [{'prevHash': 'f09b3b697c580d1730cd360da5e1f0beeae00827eb2f0055cbc85a5a4dadd8ea', 'prevIndex': 0}, 27 | {'prevHash': 'c858e4d2af1e1525fa974fb2b1678caca1f81a5056513f922789594939ff713d', 'prevIndex': 31}], 28 | 'outputs': [{'assetId': '602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7', 'scriptHash': 'e707714512577b42f9a011f8b870625429f93573', 'value': 1e-08}], 29 | 'scripts': [], 30 | 'script': '0800e1f505000000001432e125258b7db0a0dffde5bd03b2b859253538ab14592c8a46a0d06c600f06c994d1f25e7283b8a2fe53c1076465706f73697467823b63e7c70a795a7615a38d1ba67d9e54c195a1', 31 | 'gas': 0} 32 | 33 | transaction_array = [{ 34 | 'id': 'e30a7fdf-779c-4623-8f92-8a961450d843', 35 | 'offer_hash': None, 36 | 'available_amount': None, 37 | 'offer_asset_id': 'c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b', 38 | 'offer_amount': '2000000', 39 | 'want_asset_id': 'ab38352559b8b203bde5fddfa0b07d8b2525e132', 40 | 'want_amount': '10000000000', 41 | 'filled_amount': None, 42 | 'txn': { 43 | 'offerHash': 'b45ddfb97ade5e0363d9e707dac9ad1c530448db263e86494225a0025006f968', 44 | 'hash': '5c4cb1e73b9f2e608b6e768e0654649a4d15e08a7fe63fc536c454fa563a2f0f', 45 | 'sha256': 'f0b70640627947584a2976edeb055a124ae85594db76453532b893c05618e6ca', 46 | 'invoke': { 47 | 'scriptHash': 'a195c1549e7da61b8da315765a790ac7e7633b82', 48 | 'operation': 'makeOffer', 49 | 'args': [ 50 | '592c8a46a0d06c600f06c994d1f25e7283b8a2fe', 51 | '9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5', 52 | 2000000, 53 | '32e125258b7db0a0dffde5bd03b2b859253538ab', 54 | 10000000000, 55 | '65333061376664662d373739632d343632332d386639322d386139363134353064383433' 56 | ] 57 | }, 58 | 'type': 209, 59 | 'version': 1, 60 | 'attributes': [ 61 | { 62 | 'usage': 32, 63 | 'data': '592c8a46a0d06c600f06c994d1f25e7283b8a2fe' 64 | }, { 65 | 'usage': 129, 66 | 'data': '592c8a46a0d06c600f06c994d1f25e7283b8a2fe' 67 | }, { 68 | 'usage': 144, 69 | 'data': '592c8a46a0d06c600f06c994d1f25e7283b8a2fe' 70 | }, { 71 | 'usage': 2, 72 | 'data': '592c8a46a0d06c600f06c994d1f25e7283b8a2fe' 73 | } 74 | ], 75 | 'inputs': [ 76 | { 77 | 'prevHash': '0fcfd792a9d20a7795255d1d3d3927f5968b9953e80d16ffd222656edf8fedbc', 78 | 'prevIndex': 0 79 | }, { 80 | 'prevHash': 'c858e4d2af1e1525fa974fb2b1678caca1f81a5056513f922789594939ff713d', 81 | 'prevIndex': 35 82 | } 83 | ], 84 | 'outputs': [ 85 | { 86 | 'assetId': '602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7', 87 | 'scriptHash': 'e707714512577b42f9a011f8b870625429f93573', 88 | 'value': 1e-08 89 | } 90 | ], 91 | 'scripts': [], 92 | 'script': '0800e1f505000000001432e125258b7db0a0dffde5bd03b2b859253538ab14592c8a46a0d06c600f06c994d1f25e7283b8a2fe53c1076465706f73697467823b63e7c70a795a7615a38d1ba67d9e54c195a1', 93 | 'gas': 0 94 | }, 95 | 'cancel_txn': None, 96 | 'price': '0.0002', 97 | 'status': 'pending', 98 | 'created_at': '2018-08-05T10:38:37.731Z', 99 | 'transaction_hash': '5c4cb1e73b9f2e608b6e768e0654649a4d15e08a7fe63fc536c454fa563a2f0f', 100 | 'trades': [] 101 | }, { 102 | 'id': '7dac087c-3709-48ea-83e1-83eadfc4cbe5', 103 | 'offer_hash': None, 104 | 'available_amount': None, 105 | 'offer_asset_id': 'c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b', 106 | 'offer_amount': '2000000', 107 | 'want_asset_id': 'ab38352559b8b203bde5fddfa0b07d8b2525e132', 108 | 'want_amount': '10000000000', 109 | 'filled_amount': None, 110 | 'txn': { 111 | 'offerHash': 'b45ddfb97ade5e0363d9e707dac9ad1c530448db263e86494225a0025006f968', 112 | 'hash': '5c4cb1e73b9f2e608b6e768e0654649a4d15e08a7fe63fc536c454fa563a2f0f', 113 | 'sha256': 'f0b70640627947584a2976edeb055a124ae85594db76453532b893c05618e6ca', 114 | 'invoke': { 115 | 'scriptHash': 'a195c1549e7da61b8da315765a790ac7e7633b82', 116 | 'operation': 'makeOffer', 117 | 'args': [ 118 | '592c8a46a0d06c600f06c994d1f25e7283b8a2fe', 119 | '9b7cffdaa674beae0f930ebe6085af9093e5fe56b34a5c220ccdcf6efc336fc5', 120 | 2000000, 121 | '32e125258b7db0a0dffde5bd03b2b859253538ab', 122 | 10000000000, 123 | '65333061376664662d373739632d343632332d386639322d386139363134353064383433' 124 | ] 125 | }, 126 | 'type': 209, 127 | 'version': 1, 128 | 'attributes': [ 129 | { 130 | 'usage': 32, 131 | 'data': '592c8a46a0d06c600f06c994d1f25e7283b8a2fe' 132 | } 133 | ], 134 | 'inputs': [ 135 | { 136 | 'prevHash': '0fcfd792a9d20a7795255d1d3d3927f5968b9953e80d16ffd222656edf8fedbc', 137 | 'prevIndex': 0 138 | }, { 139 | 'prevHash': 'c858e4d2af1e1525fa974fb2b1678caca1f81a5056513f922789594939ff713d', 140 | 'prevIndex': 35 141 | } 142 | ], 143 | 'outputs': [ 144 | { 145 | 'assetId': '602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7', 146 | 'scriptHash': 'e707714512577b42f9a011f8b870625429f93573', 147 | 'value': 1e-08 148 | } 149 | ], 150 | 'scripts': [], 151 | 'script': '0800e1f505000000001432e125258b7db0a0dffde5bd03b2b859253538ab14592c8a46a0d06c600f06c994d1f25e7283b8a2fe53c1076465706f73697467823b63e7c70a795a7615a38d1ba67d9e54c195a1', 152 | 'gas': 0 153 | }, 154 | 'cancel_txn': None, 155 | 'price': '0.0002', 156 | 'status': 'pending', 157 | 'created_at': '2018-08-05T10:38:37.731Z', 158 | 'transaction_hash': '5c4cb1e73b9f2e608b6e768e0654649a4d15e08a7fe63fc536c454fa563a2f0f', 159 | 'trades': [] 160 | }] 161 | 162 | 163 | class TestNeoUtils(unittest.TestCase): 164 | 165 | def test_sign_message(self): 166 | signed_message = 'f60f6896a87d6d35e62668958e08fb5a7c26c1c381c5ed6ad5b1ad9f6f6e826b1ab7695ad4ddd337438da1cd802b5e19dc6861700f4bd87a58dfa002cde11a3a' 167 | self.assertEqual(sign_message(encoded_message=encoded_message,private_key_hex=testnet_privatekey_hexstring), 168 | signed_message) 169 | 170 | def test_sign_transaction(self): 171 | signed_transaction = 'cb511f7acf269d22690cf656b2f868fc10d12baff2f398ad175ae7e5a1e599f02d63a52d13aa3f3a6b57eaa0231e9fc4f1ab7900c34240033232c5a9f6b8214b' 172 | self.assertEqual(sign_transaction(transaction=transaction_dict, private_key_hex=testnet_privatekey_hexstring), 173 | signed_transaction) 174 | 175 | def test_sign_array(self): 176 | pass 177 | # signed_array = {'e30a7fdf-779c-4623-8f92-8a961450d843': 'b1b821d7aa3c3d388370eba8e910de5c3605fcae2d584b0e89e932658f6b335a6aac65c52928e6eebf85919464897b8966a5a4dbcfd92eb28a3ae88299533f2c', '7dac087c-3709-48ea-83e1-83eadfc4cbe5': 'b1b821d7aa3c3d388370eba8e910de5c3605fcae2d584b0e89e932658f6b335a6aac65c52928e6eebf85919464897b8966a5a4dbcfd92eb28a3ae88299533f2c'} 178 | # self.assertEqual(sign_txn_array(messages=transaction_array, private_key_hex=testnet_privatekey_hexstring)['e30a7fdf-779c-4623-8f92-8a961450d843'], 179 | # signed_array['e30a7fdf-779c-4623-8f92-8a961450d843']) 180 | 181 | def test_encode_message(self): 182 | self.assertEqual(encode_message(message=message), encoded_message) 183 | self.assertEqual(encode_message(message=json_message), json_encoded_message) 184 | 185 | def test_to_neo_asset_amount(self): 186 | self.assertEqual(to_neo_asset_amount(10), '1000000000') 187 | self.assertEqual(to_neo_asset_amount(.01), '1000000') 188 | with self.assertRaises(ValueError): 189 | to_neo_asset_amount(0.0000000000001) 190 | with self.assertRaises(ValueError): 191 | to_neo_asset_amount(100000000) 192 | 193 | def test_private_key_to_hex(self): 194 | self.assertEqual(private_key_to_hex(key_pair=kp), testnet_privatekey_hexstring) 195 | 196 | def test_neo_get_scripthash_from_address(self): 197 | self.assertEqual(neo_get_scripthash_from_address(address=testnet_address), testnet_scripthash) 198 | 199 | def test_neo_get_address_from_scripthash(self): 200 | self.assertEqual(neo_get_address_from_scripthash(scripthash=testnet_scripthash), testnet_address) 201 | 202 | def test_neo_get_public_key_from_private_key(self): 203 | self.assertEqual(neo_get_public_key_from_private_key(private_key=testnet_privatekey).ToString(), testnet_publickey) 204 | 205 | def test_neo_get_scripthash_from_private_key(self): 206 | self.assertEqual(str(neo_get_scripthash_from_private_key(private_key=testnet_privatekey)), testnet_scripthash) 207 | 208 | def test_open_wallet(self): 209 | self.assertEqual(open_wallet(testnet_privatekey_hexstring).PublicKey, kp.PublicKey) 210 | self.assertEqual(open_wallet(testnet_privatekey_hexstring).PrivateKey, kp.PrivateKey) 211 | self.assertEqual(open_wallet(testnet_privatekey_hexstring).GetAddress(), kp.GetAddress()) 212 | 213 | def test_create_offer_hash(self): 214 | self.assertEqual(create_offer_hash(neo_address='APuP9GsSCPJKrexPe49afDV8CQYubZGWd8', 215 | offer_asset_amt=6000000, 216 | offer_asset_hash='c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b', 217 | want_asset_amt=30000000000, 218 | want_asset_hash='ab38352559b8b203bde5fddfa0b07d8b2525e132', 219 | txn_uuid='ecb6ee9e-de8d-46d6-953b-afcc976be1ae'), 220 | '95a9502f11c62b85cf790b83104c89d3198a3b4dac6ba8a0e19090a8ee2207c7') 221 | -------------------------------------------------------------------------------- /switcheo/neo/test_transactions.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from switcheo.neo.transactions import serialize_transaction, serialize_transaction_attribute,\ 3 | serialize_transaction_input, serialize_transaction_output, serialize_witness, serialize_claim_exclusive,\ 4 | serialize_contract_exclusive, serialize_invocation_exclusive 5 | 6 | 7 | transaction_dict = {'hash': '72b74c96b9174e9b9e1b216f7e8f21a6475e6541876a62614df7c1998c6e8376', 8 | 'sha256': '2109cbb5eea67a06f5dd8663e10fcd1128e28df5721a25d993e05fe2097c34f3', 9 | 'type': 209, 10 | 'version': 1, 11 | 'attributes': [{'usage': 32, 'data': '592c8a46a0d06c600f06c994d1f25e7283b8a2fe'}, 12 | {'usage': 32, 'data': '6a3d9b359fc17d711017daa6c0e14d6172a791ed'}], 13 | 'inputs': [{'prevHash': 'f09b3b697c580d1730cd360da5e1f0beeae00827eb2f0055cbc85a5a4dadd8ea', 'prevIndex': 0}, 14 | {'prevHash': 'c858e4d2af1e1525fa974fb2b1678caca1f81a5056513f922789594939ff713d', 'prevIndex': 31}], 15 | 'outputs': [{'assetId': '602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7', 'scriptHash': 'e707714512577b42f9a011f8b870625429f93573', 'value': 1e-08}], 16 | 'scripts': [], 17 | 'script': '0800e1f505000000001432e125258b7db0a0dffde5bd03b2b859253538ab14592c8a46a0d06c600f06c994d1f25e7283b8a2fe53c1076465706f73697467823b63e7c70a795a7615a38d1ba67d9e54c195a1', 18 | 'gas': 0} 19 | 20 | 21 | class TestTransactions(unittest.TestCase): 22 | 23 | def test_serialize_transaction(self): 24 | serialized_transaction = 'd101520800e1f505000000001432e125258b7db0a0dffde5bd03b2b859253538ab14592c8a46a0d06c600f06c994d1f25e7283b8a2fe53c1076465706f73697467823b63e7c70a795a7615a38d1ba67d9e54c195a100000000000000000220592c8a46a0d06c600f06c994d1f25e7283b8a2fe206a3d9b359fc17d711017daa6c0e14d6172a791ed02ead8ad4d5a5ac8cb55002feb2708e0eabef0e1a50d36cd30170d587c693b9bf000003d71ff3949598927923f5156501af8a1ac8c67b1b24f97fa25151eafd2e458c81f0001e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c6001000000000000007335f929546270b8f811a0f9427b5712457107e7' 25 | self.assertEqual(serialize_transaction(transaction=transaction_dict, signed=False), serialized_transaction) 26 | 27 | def test_serialize_transaction_attribute(self): 28 | serialized_attributes = [] 29 | serialized_attribute_expected_list = ['20592c8a46a0d06c600f06c994d1f25e7283b8a2fe', 30 | '206a3d9b359fc17d711017daa6c0e14d6172a791ed'] 31 | for attribute in transaction_dict['attributes']: 32 | serialized_attributes.append(serialize_transaction_attribute(attr=attribute)) 33 | self.assertListEqual(serialized_attributes, serialized_attribute_expected_list) 34 | 35 | def test_serialize_transaction_input(self): 36 | serialized_inputs = [] 37 | serialized_input_expected_list = ['ead8ad4d5a5ac8cb55002feb2708e0eabef0e1a50d36cd30170d587c693b9bf00000', 38 | '3d71ff3949598927923f5156501af8a1ac8c67b1b24f97fa25151eafd2e458c81f00'] 39 | for txn_input in transaction_dict['inputs']: 40 | serialized_inputs.append(serialize_transaction_input(txn_input=txn_input)) 41 | self.assertListEqual(serialized_inputs, serialized_input_expected_list) 42 | 43 | def test_serialize_transaction_output(self): 44 | serialized_outputs = [] 45 | serialized_output_expected_list = ['e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c6001000000000000007335f929546270b8f811a0f9427b5712457107e7'] 46 | for txn_output in transaction_dict['outputs']: 47 | serialized_outputs.append(serialize_transaction_output(txn_output=txn_output)) 48 | self.assertListEqual(serialized_outputs, serialized_output_expected_list) 49 | 50 | def test_serialize_witness(self): 51 | # This is not used by Switcheo and I can't find a good test transaction for this, will pass for now. 52 | pass 53 | 54 | def test_serialize_claim_exclusive(self): 55 | with self.assertRaises(ValueError): 56 | serialize_claim_exclusive(transaction=transaction_dict) 57 | # Switcheo will not be allowing for GAS claims so this should never be necessary. 58 | # transaction_claim_dict = transaction_dict.copy() 59 | # transaction_claim_dict['type'] = 2 60 | # self.assertEqual(serialize_claim_exclusive(transaction=transaction_claim_dict), '') 61 | 62 | def test_serialize_contract_exclusive(self): 63 | with self.assertRaises(ValueError): 64 | serialize_contract_exclusive(transaction=transaction_dict) 65 | transaction_contract_dict = transaction_dict.copy() 66 | transaction_contract_dict['type'] = 128 67 | self.assertEqual(serialize_contract_exclusive(transaction=transaction_contract_dict), '') 68 | 69 | def test_serialize_invocation_exclusive(self): 70 | serialized_invocation = '520800e1f505000000001432e125258b7db0a0dffde5bd03b2b859253538ab14592c8a46a0d06c600f06c994d1f25e7283b8a2fe53c1076465706f73697467823b63e7c70a795a7615a38d1ba67d9e54c195a10000000000000000' 71 | self.assertEqual(serialize_invocation_exclusive(transaction=transaction_dict), serialized_invocation) 72 | transaction_invocation_dict = transaction_dict.copy() 73 | transaction_invocation_dict['type'] = 128 74 | with self.assertRaises(ValueError): 75 | serialize_invocation_exclusive(transaction=transaction_invocation_dict) 76 | -------------------------------------------------------------------------------- /switcheo/neo/transactions.py: -------------------------------------------------------------------------------- 1 | # 2 | # switcheo/neo/transactions.py 3 | # Keith Smith 4 | # 5 | # For testnet requests to the Switcheo exchange 6 | 7 | 8 | from switcheo.utils import reverse_hex, num2hexstring, num2varint 9 | from switcheo.Fixed8 import SwitcheoFixed8, num2fixed8 10 | 11 | 12 | max_transaction_attribute_size = 65535 13 | 14 | 15 | def serialize_transaction(transaction, signed=True): 16 | serialized_txn = '' 17 | serialized_txn += str(num2hexstring(transaction['type'])) 18 | serialized_txn += str(num2hexstring(transaction['version'])) 19 | serialized_txn += str(serialize_exclusive[transaction['type']](transaction)) 20 | serialized_txn += str(num2varint(len(transaction['attributes']))) 21 | for attribute in transaction['attributes']: 22 | serialized_txn += serialize_transaction_attribute(attribute) 23 | serialized_txn += str(num2varint(len(transaction['inputs']))) 24 | for txn_input in transaction['inputs']: 25 | serialized_txn += serialize_transaction_input(txn_input) 26 | serialized_txn += str(num2varint(len(transaction['outputs']))) 27 | for txn_output in transaction['outputs']: 28 | serialized_txn += serialize_transaction_output(txn_output) 29 | if signed and transaction['scripts'] and len(transaction['scripts']) > 0: 30 | serialized_txn += str(num2varint(len(transaction['scripts']))) 31 | for script in transaction['scripts']: 32 | serialized_txn += serialize_witness(script) 33 | return serialized_txn 34 | 35 | 36 | def serialize_transaction_attribute(attr): 37 | if len(attr['data']) > max_transaction_attribute_size: 38 | raise ValueError('Transaction attribute data is larger than the Maximum allowed attribute size.') 39 | out = num2hexstring(attr['usage']) 40 | if attr['usage'] == 0x81: 41 | out += num2hexstring(len(attr['data']) // 2) 42 | elif attr['usage'] == 0x90 or attr['usage'] >= 0xf0: 43 | out += num2varint(len(attr['data']) // 2) 44 | if attr['usage'] == 0x02 or attr['usage'] == 0x03: 45 | out += attr['data'][2:64] 46 | else: 47 | out += attr['data'] 48 | return out 49 | 50 | 51 | def serialize_transaction_input(txn_input): 52 | return reverse_hex(txn_input['prevHash']) + reverse_hex(num2hexstring(txn_input['prevIndex'], 2)) 53 | 54 | 55 | def serialize_transaction_output(txn_output): 56 | value = SwitcheoFixed8(float(txn_output['value'])).toReverseHex() 57 | return reverse_hex(txn_output['assetId']) + value + reverse_hex(txn_output['scriptHash']) 58 | 59 | 60 | def serialize_witness(witness): 61 | invocation_len = num2varint(len(witness['invocationScript']) // 2) 62 | verification_len = num2varint(len(witness['verificationScript']) // 2) 63 | return invocation_len + witness['invocationScript'] + verification_len + witness['verificationScript'] 64 | 65 | 66 | # Switcheo does not allow for GAS claims so this function should not be used. 67 | def serialize_claim_exclusive(transaction): 68 | if transaction['type'] != 0x02: 69 | raise ValueError( 70 | 'The transaction type {} does not match the claim exclusive method.'.format(transaction['type'])) 71 | out = num2varint(len(transaction['claims'])) 72 | for claim in transaction['claims']: 73 | out += serialize_transaction_input(claim) 74 | return out 75 | 76 | 77 | def serialize_contract_exclusive(transaction): 78 | if transaction['type'] != 0x80: 79 | raise ValueError( 80 | 'The transaction type {} does not match the contract exclusive method.'.format(transaction['type'])) 81 | return '' 82 | 83 | 84 | def serialize_invocation_exclusive(transaction): 85 | if transaction['type'] != 0xd1: 86 | raise ValueError( 87 | 'The transaction type {} does not match the invocation exclusive method.'.format(transaction['type'])) 88 | out = num2varint(int(len(transaction['script'])/2)) 89 | out += transaction['script'] 90 | if transaction['version'] >= 1: 91 | out += num2fixed8(transaction['gas']) 92 | return out 93 | 94 | 95 | serialize_exclusive = { 96 | 2: serialize_claim_exclusive, 97 | 128: serialize_contract_exclusive, 98 | 209: serialize_invocation_exclusive 99 | } 100 | -------------------------------------------------------------------------------- /switcheo/neo/utils.py: -------------------------------------------------------------------------------- 1 | # 2 | # switcheo/neo/utils.py 3 | # Keith Smith 4 | # 5 | # For testnet requests to the Switcheo exchange 6 | 7 | import math 8 | import binascii 9 | import base58 10 | from neocore.Cryptography.Crypto import Crypto 11 | from neocore.KeyPair import KeyPair 12 | from neocore.Cryptography.Helper import scripthash_to_address 13 | from switcheo.utils import num2hexstring, stringify_message, reverse_hex, num2varint 14 | from switcheo.neo.transactions import serialize_transaction 15 | 16 | 17 | def sign_message(encoded_message, private_key_hex): 18 | return Crypto.Sign(message=encoded_message.strip(), private_key=private_key_hex).hex() 19 | 20 | 21 | def sign_transaction(transaction, private_key_hex): 22 | serialized_transaction = serialize_transaction(transaction=transaction, signed=False) 23 | return sign_message(encoded_message=serialized_transaction, private_key_hex=private_key_hex) 24 | 25 | 26 | def sign_txn_array(messages, private_key_hex): 27 | message_dict = {} 28 | for message in messages: 29 | message_dict[message['id']] = sign_transaction(transaction=message['txn'], 30 | private_key_hex=private_key_hex) 31 | return message_dict 32 | 33 | 34 | def encode_message(message): 35 | message_hex = binascii.hexlify(stringify_message(message).encode('utf-8')).decode() 36 | message_hex_length = num2varint(len(message_hex) // 2) 37 | return '010001f0' + message_hex_length + message_hex + '0000' 38 | 39 | 40 | def to_neo_asset_amount(amount, power=8): 41 | if 0.00000001 < amount < 1000000: 42 | return "{:.0f}".format(amount * math.pow(10, power)) 43 | else: 44 | raise ValueError('Asset amount {} outside of acceptable range {}-{}.'.format(amount, 0.00000001, 1000000)) 45 | 46 | 47 | def private_key_to_hex(key_pair): 48 | return bytes(key_pair.PrivateKey).hex() 49 | 50 | 51 | def neo_get_scripthash_from_address(address): 52 | """ 53 | Convert a Public Address String to a ScriptHash (Address) String. 54 | 55 | :param address: The Public address to convert. 56 | :type address: str 57 | :return: String containing the converted ScriptHash. 58 | """ 59 | hash_bytes = binascii.hexlify(base58.b58decode_check(address)) 60 | return reverse_hex(hash_bytes[2:].decode('utf-8')) 61 | 62 | 63 | def neo_get_address_from_scripthash(scripthash): 64 | """ 65 | Core methods for manipulating keys 66 | NEP2 <=> WIF <=> Private => Public => ScriptHash <=> Address 67 | Keys are arranged in order of derivation. 68 | Arrows determine the direction. 69 | """ 70 | scripthash_bytes = binascii.unhexlify(reverse_hex(scripthash)) 71 | return scripthash_to_address(scripthash_bytes) 72 | 73 | 74 | def neo_get_public_key_from_private_key(private_key): 75 | kp = KeyPair(priv_key=private_key) 76 | return kp.PublicKey 77 | 78 | 79 | def neo_get_scripthash_from_private_key(private_key): 80 | script = b'21' + neo_get_public_key_from_private_key(private_key).encode_point(True) + b'ac' 81 | return Crypto.ToScriptHash(data=script) 82 | 83 | 84 | def open_wallet(private_key): 85 | pk = bytes.fromhex(private_key) 86 | return KeyPair(priv_key=pk) 87 | 88 | 89 | def create_offer_hash(neo_address, offer_asset_hash, offer_asset_amt, want_asset_hash, want_asset_amt, txn_uuid): 90 | reverse_user_hash = reverse_hex(neo_get_scripthash_from_address(neo_address)) 91 | reverse_offer_asset_hash = reverse_hex(offer_asset_hash) 92 | reverse_offer_amount = num2hexstring(number=offer_asset_amt, size=8, little_endian=True) 93 | reverse_want_asset_hash = reverse_hex(want_asset_hash) 94 | reverse_want_amount = num2hexstring(number=want_asset_amt, size=8, little_endian=True) 95 | nonce_hex = txn_uuid.encode('utf-8').hex() 96 | offer_key_bytes = reverse_user_hash + reverse_offer_asset_hash + reverse_want_asset_hash + reverse_offer_amount +\ 97 | reverse_want_amount + nonce_hex 98 | offer_hash = reverse_hex(Crypto.Hash256(binascii.a2b_hex(offer_key_bytes)).hex()) 99 | return offer_hash 100 | -------------------------------------------------------------------------------- /switcheo/products.py: -------------------------------------------------------------------------------- 1 | product_dict = { 2 | 'NEO': 'c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b', 3 | 'GAS': '602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7', 4 | 'SWTH': 'ab38352559b8b203bde5fddfa0b07d8b2525e132', 5 | 'ACAT': '7f86d61ff377f1b12e589a5907152b57e2ad9a7a', 6 | 'APH': 'a0777c3ce2b169d4a23bcba4565e3225a0122d95', 7 | 'AVA': 'de2ed49b691e76754c20fe619d891b78ef58e537', 8 | 'CPX': '45d493a6f73fa5f404244a5fb8472fc014ca5885', 9 | 'DBC': 'b951ecbbc5fe37a9c280a76cb0ce0014827294cf', 10 | 'EFX': 'acbc532904b6b51b5ea6d19b803d78af70e7e6f9', 11 | 'GALA': '9577c3f972d769220d69d1c4ddbd617c44d067aa', 12 | 'LRN': '06fa8be9b6609d963e8fc63977b9f8dc5f10895f', 13 | 'MCT': 'a87cc2a513f5d8b4a42432343687c2127c60bc3f', 14 | 'NKN': 'c36aee199dbba6c3f439983657558cfb67629599', 15 | 'OBT': '0e86a40588f715fcaf7acd1812d50af478e6e917', 16 | 'ONT': 'ceab719b8baa2310f232ee0d277c061704541cfb', 17 | 'PKC': 'af7c7328eee5a275a3bcaee2bf0cf662b5e739be', 18 | 'RHT': '2328008e6f6c7bd157a342e789389eb034d9cbc4', 19 | 'RPX': 'ecc6b20d3ccac1ee9ef109af5a7cdb85706b1df9', 20 | 'TKY': '132947096727c84c7f9e076c90f08fec3bc17f18', 21 | 'TNC': '08e8c4400f1af2c20c28e0018f29535eb85d15b6', 22 | 'TOLL': '78fd589f7894bf9642b4a573ec0e6957dfd84c48', 23 | 'QLC': '0d821bd7b6d53f5c2b40e217c6defc8bbe896cf5', 24 | 'SOUL': 'ed07cffad18f1308db51920d99a2af60ac66a7b3', 25 | 'ZPT': 'ac116d4b8d4ca55e6b6d4ecce2192039b51cccc5', 26 | 'SWH': '78e6d16b914fe15bc16150aeb11d0c2a8e532bdd' 27 | } 28 | -------------------------------------------------------------------------------- /switcheo/public_client.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | """ 3 | Description: 4 | Public Client for the Switcheo decentralized exchange. 5 | It is not required to use your private key or WIF to interact with these endpoints which can be used 6 | to track the exchange state. 7 | Usage: 8 | from switcheo.public_client import PublicClient 9 | """ 10 | 11 | from switcheo.utils import Request 12 | 13 | 14 | class PublicClient(object): 15 | """ 16 | This class allows the user to interact with the Switcheo decentralized exchange API to retrieve information about 17 | the available options on the exchange, including system health, time, trade offers, etc. 18 | """ 19 | 20 | def __init__(self, 21 | blockchain="neo", 22 | contract_version='V3', 23 | api_url='https://test-api.switcheo.network/', 24 | api_version='/v2'): 25 | """ 26 | 27 | :param blockchain: Choose which blockchain to trade on. Allowed value are neo (future eth and qtum) 28 | :type blockchain: str 29 | :param api_url: The URL for the Switcheo API endpoint. 30 | :type api_url: str 31 | :param api_version: Choose the version of the Switcho API to use. 32 | :type api_version: str 33 | """ 34 | self.request = Request(api_url=api_url, api_version=api_version, timeout=30) 35 | self.blockchain = blockchain 36 | self.blockchain_key = blockchain.upper() 37 | self.contracts = self.get_contracts() 38 | self.contract_version = contract_version.upper() 39 | self.contract_hash = self.contracts[self.blockchain_key][self.contract_version] 40 | self.current_contract_hash = self.get_latest_contracts()[self.blockchain_key] 41 | 42 | def get_exchange_status(self): 43 | """ 44 | Function to fetch the state of the exchange. 45 | Execution of this function is as follows:: 46 | 47 | get_exchange_status() 48 | 49 | The expected return result for this function is as follows:: 50 | 51 | { 52 | 'status': 'ok' 53 | } 54 | 55 | :return: Dictionary in the form of a JSON message with the exchange status. 56 | """ 57 | return self.request.status() 58 | 59 | def get_exchange_time(self): 60 | """ 61 | Function to fetch the time on the exchange in to synchronized server time. 62 | Execution of this function is as follows:: 63 | 64 | get_exchange_time() 65 | 66 | The expected return result for this function is as follows:: 67 | 68 | { 69 | 'timestamp': 1533362081336 70 | } 71 | 72 | :return: Dictionary in the form of a JSON message with the exchange epoch time in milliseconds. 73 | """ 74 | return self.request.get(path='/exchange/timestamp') 75 | 76 | def get_contracts(self): 77 | """ 78 | Function to fetch the contract hashes for each smart contract deployed on the defined blockchain. 79 | Execution of this function is as follows:: 80 | 81 | get_contracts() 82 | 83 | The expected return result for this function is as follows:: 84 | 85 | { 86 | 'NEO': { 87 | 'V1': '0ec5712e0f7c63e4b0fea31029a28cea5e9d551f', 88 | 'V1_5': '01bafeeafe62e651efc3a530fde170cf2f7b09bd', 89 | 'V2': '91b83e96f2a7c4fdf0c1688441ec61986c7cae26' 90 | }, 91 | 'ETH': { 92 | 'V1': '0x0000000000000000000000000000000000000000' 93 | } 94 | } 95 | 96 | :return: Dictionary containing the list of smart contract hashes per blockchain and version. 97 | """ 98 | return self.request.get(path='/exchange/contracts') 99 | 100 | def get_latest_contracts(self): 101 | """ 102 | Function to fetch the active contract hash for each smart contract deployed on the defined blockchain. 103 | Execution of this function is as follows:: 104 | 105 | get_latest_contracts() 106 | 107 | The expected return result for this function is as follows:: 108 | 109 | { 110 | "NEO": "d524fbb2f83f396368bc0183f5e543cae54ef532", 111 | "ETH": "0x6ee18298fd6bc2979df9d27569842435a7d55e65", 112 | "EOS": "oboluswitch4", 113 | "QTUM": "0x2b25406b0000c3661e9c88890690fd4b5c7b4234" 114 | } 115 | 116 | :return: Dictionary containing the latest smart contract hash for each blockchain. 117 | """ 118 | return self.request.get(path='/exchange/latest_contracts') 119 | 120 | def get_pairs(self, base=None, show_details=False, show_inactive=False): 121 | """ 122 | Function to fetch a list of trading pairs offered on the Switcheo decentralized exchange. 123 | Execution of this function is as follows:: 124 | 125 | get_pairs() # Fetch all pairs 126 | get_pairs(base="SWTH") # Fetch only SWTH base pairs 127 | get_pairs(show_details=True) # Fetch all pairs with extended information !Attention return value changes! 128 | 129 | The expected return result for this function is as follows:: 130 | 131 | [ 132 | 'GAS_NEO', 133 | 'SWTH_NEO', 134 | 'MCT_NEO', 135 | 'NKN_NEO', 136 | .... 137 | 'SWTH_GAS', 138 | 'MCT_GAS', 139 | 'NKN_GAS', 140 | .... 141 | 'MCT_SWTH', 142 | 'NKN_SWTH' 143 | ] 144 | 145 | If you use the show_details parameter the server return a list with dictionaries as follows:: 146 | 147 | [ 148 | {'name': 'GAS_NEO', 'precision': 3}, 149 | {'name': 'SWTH_NEO', 'precision': 6}, 150 | {'name': 'ACAT_NEO', 'precision': 8}, 151 | {'name': 'APH_NEO', 'precision': 5}, 152 | {'name': 'ASA_NEO', 'precision': 8}, 153 | .... 154 | ] 155 | 156 | :param base: The base trade pair to optionally filter available trade pairs. 157 | :type base: str 158 | :param show_details: Extended information for the pairs. 159 | :type show_details: bool 160 | :return: List of trade pairs available for trade on Switcheo. 161 | """ 162 | api_params = {} 163 | if show_details: 164 | api_params["show_details"] = show_details 165 | if show_inactive: 166 | api_params["show_inactive"] = show_inactive 167 | if base is not None and base in ["NEO", "GAS", "SWTH", "USD", "ETH"]: 168 | api_params["bases"] = [base] 169 | return self.request.get(path='/exchange/pairs', params=api_params) 170 | 171 | def get_token_details(self, show_listing_details=False, show_inactive=False): 172 | """ 173 | Function to fetch the available tokens available to trade on the Switcheo exchange. 174 | Execution of this function is as follows:: 175 | 176 | get_token_details() 177 | get_token_details(show_listing_details=True) 178 | get_token_details(show_inactive=True) 179 | get_token_details(show_listing_details=True, show_inactive=True) 180 | 181 | The expected return result for this function is as follows:: 182 | 183 | { 184 | 'NEO': { 185 | 'hash': 'c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b', 186 | 'decimals': 8 187 | }, 188 | 'GAS': { 189 | 'hash': '602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7', 190 | 'decimals': 8 191 | }, 192 | 'SWTH': { 193 | 'hash': 'ab38352559b8b203bde5fddfa0b07d8b2525e132', 194 | 'decimals': 8 195 | }, 196 | ... 197 | } 198 | 199 | :param show_listing_details: Parameter flag to indicate whether or not to show the token listing details. 200 | :type show_listing_details: bool 201 | :param show_inactive: Flag to return the tokens that are no longer traded on the Switcheo Exchange. 202 | :type show_inactive: bool 203 | :return: Dictionary in the form of a JSON message with the available tokens for trade on the Switcheo exchange. 204 | """ 205 | api_params = { 206 | "show_listing_details": show_listing_details, 207 | "show_inactive": show_inactive 208 | } 209 | return self.request.get(path='/exchange/tokens', params=api_params) 210 | 211 | def get_exchange_message(self): 212 | """ 213 | Function to fetch the Switcheo Exchange message to ensure pertinent information is handled gracefully. 214 | Execution of this function is as follows:: 215 | 216 | get_exchange_message() 217 | 218 | The expected return result for this function is as follows:: 219 | 220 | { 221 | 'message': 'Welcome to Switcheo Beta.', 222 | 'message_type': 'info' 223 | } 224 | 225 | :return: Dictionary containing the sum of all addresses smart contract balances by processing state. 226 | """ 227 | return self.request.get(path='/exchange/announcement_message') 228 | 229 | def get_exchange_fees(self): 230 | """ 231 | Function to fetch the Switcheo Exchange fees to assist with automated trading calculations. 232 | Execution of this function is as follows:: 233 | 234 | get_exchange_fees() 235 | 236 | The expected return result for this function is as follows:: 237 | 238 | [ 239 | { 240 | "maker": { 241 | "default": 0 242 | }, 243 | "taker": { 244 | "default": 0.002 245 | }, 246 | "network_fee_subsidy_threshold": { 247 | "neo": 0.1, 248 | "eth": 0.1 249 | }, 250 | "max_taker_fee_ratio": { 251 | "neo": 0.005, 252 | "eth": 0.1 253 | }, 254 | "native_fee_discount": 0.75, 255 | "native_fee_asset_id": "ab38352559b8b203bde5fddfa0b07d8b2525e132", 256 | "enforce_native_fees": [ 257 | "RHT", 258 | "RHTC" 259 | ], 260 | "native_fee_exchange_rates": { 261 | "NEO": "952.38095238", 262 | "GAS": "297.14285714", 263 | ... 264 | "ETH": "0", 265 | "JRC": "0", 266 | "SWC": "0" 267 | }, 268 | "network_fees": { 269 | "eth": "2466000000000000", 270 | "neo": "200000" 271 | }, 272 | "network_fees_for_wdl": { 273 | "eth": "873000000000000", 274 | "neo": "0" 275 | } 276 | } 277 | ] 278 | 279 | :return: Dictionary containing the sum of all addresses smart contract balances by processing state. 280 | """ 281 | return self.request.get(path='/fees') 282 | 283 | def get_exchange_swap_pairs(self): 284 | """ 285 | Function to fetch the Switcheo Exchange list of atomic swap pairs. 286 | Execution of this function is as follows:: 287 | 288 | get_exchange_swap_pairs() 289 | 290 | The expected return result for this function is as follows:: 291 | 292 | [ 293 | "SWTH_ETH", 294 | "NEO_ETH", 295 | "EOS_ETH", 296 | "NEO_DAI", 297 | "SDUSD_DAI", 298 | "EOS_NEO", 299 | "NEO_WBTC" 300 | ] 301 | 302 | :return: Dictionary containing the sum of all addresses smart contract balances by processing state. 303 | """ 304 | return self.request.get(path='/exchange/swap_pairs') 305 | 306 | def get_exchange_swap_pricing(self, pair): 307 | """ 308 | Function to fetch the swap pricing for the pair requested. 309 | Execution of this function is as follows:: 310 | 311 | get_exchange_swap_pricing(pair="SWTH_ETH") 312 | 313 | The expected return result for this function is as follows:: 314 | 315 | { 316 | "buy": { 317 | "x": "740144428000000", 318 | "y": "96969492513000000000", 319 | "k": "71771429569484667564000000000000000" 320 | }, 321 | "sell": { 322 | "x": "96969492513000000000", 323 | "y": "740144428000000", 324 | "k": "71771429569484667564000000000000000" 325 | } 326 | } 327 | 328 | :param pair: The trading pair used to request candle statistics. 329 | :type pair: str 330 | :return: List of dictionaries containing the candles statistics based on the parameter filters. 331 | """ 332 | api_params = { 333 | "pair": pair 334 | } 335 | return self.request.get(path='/exchange/swap_pricing', params=api_params) 336 | 337 | def get_exchange_swap_contracts(self): 338 | """ 339 | Function to fetch the Switcheo Exchange list of atomic swap contracts. 340 | Execution of this function is as follows:: 341 | 342 | get_exchange_swap_contracts() 343 | 344 | The expected return result for this function is as follows:: 345 | 346 | { 347 | "ETH": { 348 | "V1": "0xeab64b2320a1fc1e65f4c7253385ec18e4b4313b" 349 | } 350 | } 351 | 352 | :return: Dictionary containing the sum of all addresses smart contract balances by processing state. 353 | 354 | """ 355 | return self.request.get(path='/exchange/atomic_swap_contracts') 356 | 357 | def get_candlesticks(self, pair, start_time, end_time, interval): 358 | """ 359 | Function to fetch trading metrics from the past 24 hours for all trading pairs offered on the exchange. 360 | Execution of this function is as follows:: 361 | 362 | get_candlesticks(pair="SWTH_NEO", 363 | start_time=round(time.time()) - 350000, 364 | end_time=round(time.time()), 365 | interval=360)) 366 | 367 | The expected return result for this function is as follows:: 368 | 369 | [{ 370 | 'time': '1533168000', 371 | 'open': '0.00046835', 372 | 'close': '0.00046835', 373 | 'high': '0.00046835', 374 | 'low': '0.00046835', 375 | 'volume': '240315335.0', 376 | 'quote_volume': '513110569018.0' 377 | },{ 378 | 'time': '1533081600', 379 | 'open': '0.00046835', 380 | 'close': '0.00046835', 381 | 'high': '0.00046835', 382 | 'low': '0.00046835', 383 | 'volume': '1170875.0', 384 | 'quote_volume': '2500000000.0' 385 | }, 386 | ... 387 | ] 388 | 389 | :param pair: The trading pair used to request candle statistics. 390 | :type pair: str 391 | :param start_time: The start time (in epoch seconds) range for collecting candle statistics. 392 | :type start_time: int 393 | :param end_time: The end time (in epoch seconds) range for collecting candle statistics. 394 | :type end_time: int 395 | :param interval: The time interval (in minutes) for candle statistics. Allowed values: 1, 5, 30, 60, 360, 1440 396 | :type interval: int 397 | :return: List of dictionaries containing the candles statistics based on the parameter filters. 398 | """ 399 | api_params = { 400 | "pair": pair, 401 | "interval": interval, 402 | "start_time": start_time, 403 | "end_time": end_time, 404 | "contract_hash": self.contract_hash 405 | } 406 | return self.request.get(path='/tickers/candlesticks', params=api_params) 407 | 408 | def get_last_24_hours(self): 409 | """ 410 | Function to fetch trading metrics from the past 24 hours for all trading pairs offered on the exchange. 411 | Execution of this function is as follows:: 412 | 413 | get_last_24_hours() 414 | 415 | The expected return result for this function is as follows:: 416 | 417 | [{ 418 | 'pair': 'SWTH_NEO', 419 | 'open': '0.000407', 420 | 'close': 0.00040911', 421 | 'high': '0.00041492', 422 | 'low': '0.00036', 423 | 'volume': '34572662197.0', 424 | 'quote_volume': '86879788270667.0' 425 | },{ 426 | 'pair': 'GAS_NEO', 427 | 'open': '0.3', 428 | 'close': '0.30391642', 429 | 'high': '0.352', 430 | 'low': '0.2925', 431 | 'volume': '4403569005.0', 432 | 'quote_volume': '14553283004.0' 433 | },{ 434 | .... 435 | }] 436 | 437 | :return: List of dictionaries containing the statistics of each trading pair over the last 24 hours. 438 | """ 439 | return self.request.get(path='/tickers/last_24_hours') 440 | 441 | def get_last_price(self, symbols=None, bases=None): 442 | """ 443 | Function to fetch the most recently executed trade on the order book for each trading pair. 444 | Execution of this function is as follows:: 445 | 446 | get_last_price() 447 | get_last_price(symbols=['SWTH','GAS']) 448 | get_last_price(bases=['NEO']) 449 | get_last_price(symbols=['SWTH','GAS'], bases=['NEO']) 450 | 451 | The expected return result for this function is as follows:: 452 | 453 | { 454 | 'SWTH': { 455 | 'GAS': '0.0015085', 456 | 'NEO': '0.00040911' 457 | }, 458 | 'GAS': { 459 | 'NEO': '0.30391642' 460 | }, 461 | .... 462 | } 463 | 464 | :param symbols: The trading symbols to retrieve the last price on the Switcheo Exchange. 465 | :type symbols: list 466 | :param bases: The base pair to retrieve the last price of symbols on the Switcheo Exchange. 467 | :type bases: list 468 | :return: Dictionary of trade symbols with the most recently executed trade price. 469 | """ 470 | api_params = {} 471 | if symbols is not None: 472 | api_params['symbols'] = symbols 473 | if bases is not None: 474 | api_params['bases'] = bases 475 | return self.request.get(path='/tickers/last_price', params=api_params) 476 | 477 | def get_offers(self, pair="SWTH_NEO"): 478 | """ 479 | Function to fetch the open orders on the order book for the trade pair requested. 480 | Execution of this function is as follows:: 481 | 482 | get_offers(pair="SWTH_NEO") 483 | 484 | The expected return result for this function is as follows:: 485 | 486 | [{ 487 | 'id': '2716c0ca-59bb-4c86-8ee4-6b9528d0e5d2', 488 | 'offer_asset': 'GAS', 489 | 'want_asset': 'NEO', 490 | 'available_amount': 9509259, 491 | 'offer_amount': 30000000, 492 | 'want_amount': 300000000, 493 | 'address': '7f345d1a031c4099540dbbbc220d4e5640ab2b6f' 494 | }, { 495 | .... 496 | }] 497 | 498 | :param pair: The trading pair that will be used to request open offers on the order book. 499 | :type pair: str 500 | :return: List of dictionaries consisting of the open offers for the requested trading pair. 501 | """ 502 | api_params = { 503 | "pair": pair, 504 | "contract_hash": self.contract_hash 505 | } 506 | return self.request.get(path='/offers', params=api_params) 507 | 508 | def get_offer_book(self, pair="SWTH_NEO"): 509 | """ 510 | Function to fetch the open orders formatted on the order book for the trade pair requested. 511 | Execution of this function is as follows:: 512 | 513 | get_offer_book(pair="SWTH_NEO") 514 | 515 | The expected return result for this function is as follows:: 516 | 517 | { 518 | 'asks': [{ 519 | 'price': '0.00068499', 520 | 'quantity': '43326.8348443' 521 | }, { 522 | 'price': '0.000685', 523 | 'quantity': '59886.34' 524 | }, { 525 | .... 526 | }], 527 | 'bids': [{ 528 | 'price': '0.00066602', 529 | 'quantity': '3255.99999999' 530 | }, { 531 | 'price': '0.00066601', 532 | 'quantity': '887.99999999' 533 | }, { 534 | .... 535 | }] 536 | } 537 | 538 | :param pair: The trading pair that will be used to request open offers on the order book. 539 | :type pair: str 540 | :return: List of dictionaries consisting of the open offers for the requested trading pair. 541 | """ 542 | api_params = { 543 | "pair": pair, 544 | "contract_hash": self.contract_hash 545 | } 546 | return self.request.get(path='/offers/book', params=api_params) 547 | 548 | def get_trades(self, pair="SWTH_NEO", start_time=None, end_time=None, limit=5000): 549 | """ 550 | Function to fetch a list of filled trades for the parameters requested. 551 | Execution of this function is as follows:: 552 | 553 | get_trades(pair="SWTH_NEO", limit=3) 554 | 555 | The expected return result for this function is as follows:: 556 | 557 | [{ 558 | 'id': '15bb16e2-7a80-4de1-bb59-bcaff877dee0', 559 | 'fill_amount': 100000000, 560 | 'take_amount': 100000000, 561 | 'event_time': '2018-08-04T15:00:12.634Z', 562 | 'is_buy': True 563 | }, { 564 | 'id': 'b6f9e530-60ff-46ff-9a71-362097a2025e', 565 | 'fill_amount': 47833882, 566 | 'take_amount': 97950000000, 567 | 'event_time': '2018-08-03T02:44:47.706Z', 568 | 'is_buy': True 569 | }, { 570 | 'id': '7a308ccc-b7f5-46a3-bf6b-752ab076cc9f', 571 | 'fill_amount': 1001117, 572 | 'take_amount': 2050000000, 573 | 'event_time': '2018-08-03T02:32:50.703Z', 574 | 'is_buy': True 575 | }] 576 | 577 | :param pair: The trading pair that will be used to request filled trades. 578 | :type pair: str 579 | :param start_time: Only return trades after this time (in epoch seconds). 580 | :type start_time: int 581 | :param end_time: Only return trades before this time (in epoch seconds). 582 | :type end_time: int 583 | :param limit: The number of filled trades to return. Min: 1, Max: 10000, Default: 5000 584 | :type limit: int 585 | :return: List of dictionaries consisting of filled orders that meet requirements of the parameters passed to it. 586 | """ 587 | if limit > 10000 or limit < 1: 588 | raise ValueError("Attempting to request more trades than allowed by the API.") 589 | api_params = { 590 | "blockchain": self.blockchain, 591 | "pair": pair, 592 | "contract_hash": self.contract_hash 593 | } 594 | if start_time is not None: 595 | api_params['from'] = start_time 596 | if end_time is not None: 597 | api_params['to'] = end_time 598 | if limit != 5000: 599 | api_params['limit'] = limit 600 | return self.request.get(path='/trades', params=api_params) 601 | 602 | def get_recent_trades(self, pair="SWTH_NEO"): 603 | """ 604 | Function to fetch a list of the 20 most recently filled trades for the parameters requested. 605 | Execution of this function is as follows:: 606 | 607 | get_recent_trades(pair="SWTH_NEO") 608 | 609 | The expected return result for this function is as follows:: 610 | 611 | [{ 612 | 'id': '15bb16e2-7a80-4de1-bb59-bcaff877dee0', 613 | 'fill_amount': 100000000, 614 | 'take_amount': 100000000, 615 | 'event_time': '2018-08-04T15:00:12.634Z', 616 | 'is_buy': True 617 | }, { 618 | 'id': 'b6f9e530-60ff-46ff-9a71-362097a2025e', 619 | 'fill_amount': 47833882, 620 | 'take_amount': 97950000000, 621 | 'event_time': '2018-08-03T02:44:47.706Z', 622 | 'is_buy': True 623 | }, ...., { 624 | 'id': '7a308ccc-b7f5-46a3-bf6b-752ab076cc9f', 625 | 'fill_amount': 1001117, 626 | 'take_amount': 2050000000, 627 | 'event_time': '2018-08-03T02:32:50.703Z', 628 | 'is_buy': True 629 | }] 630 | 631 | :param pair: The trading pair that will be used to request filled trades. 632 | :type pair: str 633 | :return: List of 20 dictionaries consisting of filled orders for the trade pair. 634 | """ 635 | api_params = { 636 | "pair": pair 637 | } 638 | return self.request.get(path='/trades/recent', params=api_params) 639 | 640 | def get_orders(self, address, chain_name='NEO', contract_version='V3', pair=None, from_epoch_time=None, 641 | order_status=None, before_id=None, limit=50): 642 | """ 643 | Function to fetch the order history of the given address. 644 | Execution of this function is as follows:: 645 | 646 | get_orders(address=neo_get_scripthash_from_address(address=address)) 647 | 648 | The expected return result for this function is as follows:: 649 | 650 | [{ 651 | 'id': '7cbdf481-6acf-4bf3-a1ed-4773f31e6931', 652 | 'blockchain': 'neo', 653 | 'contract_hash': 'a195c1549e7da61b8da315765a790ac7e7633b82', 654 | 'address': 'fea2b883725ef2d194c9060f606cd0a0468a2c59', 655 | 'side': 'buy', 656 | 'offer_asset_id': 'c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b', 657 | 'want_asset_id': 'ab38352559b8b203bde5fddfa0b07d8b2525e132', 658 | 'offer_amount': '53718500', 659 | 'want_amount': '110000000000', 660 | 'transfer_amount': '0', 661 | 'priority_gas_amount': '0', 662 | 'use_native_token': True, 663 | 'native_fee_transfer_amount': 0, 664 | 'deposit_txn': None, 665 | 'created_at': '2018-08-03T02:44:47.692Z', 666 | 'status': 'processed', 667 | 'fills': [{ 668 | 'id': 'b6f9e530-60ff-46ff-9a71-362097a2025e', 669 | 'offer_hash': '95b3b03be0bff8f58aa86a8dd599700bbaeaffc05078329d5b726b6b995f4cda', 670 | 'offer_asset_id': 'c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b', 671 | 'want_asset_id': 'ab38352559b8b203bde5fddfa0b07d8b2525e132', 672 | 'fill_amount': '47833882', 673 | 'want_amount': '97950000000', 674 | 'filled_amount': '', 675 | 'fee_asset_id': 'ab38352559b8b203bde5fddfa0b07d8b2525e132', 676 | 'fee_amount': '73462500', 677 | 'price': '0.00048835', 678 | 'txn': None, 679 | 'status': 'success', 680 | 'created_at': '2018-08-03T02:44:47.706Z', 681 | 'transaction_hash': '694745a09e33845ec008cfb79c73986a556e619799ec73274f82b30d85bda13a' 682 | }], 683 | 'makes': [{ 684 | 'id': '357088a0-cc80-49ab-acdd-980589c2d7d8', 685 | 'offer_hash': '420cc85abf02feaceb1bcd91489a0c1949c972d2a9a05ae922fa15d79de80c00', 686 | 'available_amount': '0', 687 | 'offer_asset_id': 'c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b', 688 | 'offer_amount': '5884618', 689 | 'want_asset_id': 'ab38352559b8b203bde5fddfa0b07d8b2525e132', 690 | 'want_amount': '12050000000', 691 | 'filled_amount': '0.0', 692 | 'txn': None, 693 | 'cancel_txn': None, 694 | 'price': '0.000488350041493775933609958506224066390041494', 695 | 'status': 'cancelled', 696 | 'created_at': '2018-08-03T02:44:47.708Z', 697 | 'transaction_hash': '1afa946546550151bbbd19f197a87cec92e9be58c44ec431cae42076298548b7', 698 | 'trades': [] 699 | }] 700 | }, { 701 | .... 702 | }] 703 | 704 | :param address: The ScriptHash of the address to filter orders for. 705 | :type address: str 706 | :param pair: The trading pair to filter order requests on. 707 | :type pair: str 708 | :param chain_name: The name of the chain to find orders against. 709 | :type chain_name: str 710 | :param contract_version: The version of the contract to find orders against. 711 | :type contract_version: str 712 | :param from_epoch_time: Only return orders that are last updated at or after this time. 713 | :type from_epoch_time: int 714 | :param order_status: Only return orders have this status. Possible values are open, cancelled, completed. 715 | :type order_status: str 716 | :param before_id: Only return orders that are created before the order with this id. 717 | :type before_id: str 718 | :param limit: Only return up to this number of orders (min: 1, max: 200, default: 50). 719 | :type limit: int 720 | :return: List of dictionaries containing the orders for the given NEO address and (optional) trading pair. 721 | """ 722 | api_params = { 723 | "address": address, 724 | "contract_hash": self.get_contracts()[chain_name.upper()][contract_version.upper()], 725 | "limit": limit 726 | } 727 | if pair is not None: 728 | api_params['pair'] = pair 729 | if from_epoch_time is not None: 730 | api_params['from_epoch_time'] = from_epoch_time 731 | if order_status is not None: 732 | api_params['order_status'] = order_status 733 | if before_id is not None: 734 | api_params['before_id'] = before_id 735 | return self.request.get(path='/orders', params=api_params) 736 | 737 | def get_balance(self, addresses, contracts): 738 | """ 739 | Function to fetch the current account balance for the given address in the Switcheo smart contract. 740 | Execution of this function is as follows:: 741 | 742 | get_balance(address=neo_get_scripthash_from_address(address=address)) 743 | 744 | The expected return result for this function is as follows:: 745 | 746 | { 747 | 'confirming': {}, 748 | 'confirmed': { 749 | 'GAS': '100000000.0', 750 | 'SWTH': '97976537500.0', 751 | 'NEO': '52166118.0' 752 | }, 753 | 'locked': {} 754 | } 755 | 756 | :param addresses: The ScriptHash of the address(es) to retrieve its Smart Contract balance. 757 | :type addresses: list 758 | :param contracts: The contract hash(es) to retrieve all addresses Smart Contract balance. 759 | :type contracts: list 760 | :return: Dictionary containing the sum of all addresses smart contract balances by processing state. 761 | """ 762 | api_params = { 763 | "addresses[]": addresses, 764 | "contract_hashes[]": contracts 765 | } 766 | return self.request.get(path='/balances', params=api_params) 767 | -------------------------------------------------------------------------------- /switcheo/streaming_client.py: -------------------------------------------------------------------------------- 1 | from socketio import ClientNamespace as SocketIOClientNamespace 2 | from operator import itemgetter 3 | from switcheo.utils import stringify_message, sha1_hash_digest 4 | import threading 5 | 6 | 7 | class OrderBooksNamespace(SocketIOClientNamespace): 8 | 9 | def __init__(self): 10 | self.lock = threading.Lock() 11 | self.namespace = '/v2/books' 12 | self.order_book = {} 13 | SocketIOClientNamespace.__init__(self, namespace=self.namespace) 14 | 15 | def on_connect(self): 16 | pass 17 | 18 | def on_disconnect(self): 19 | pass 20 | 21 | def on_join(self): 22 | pass 23 | 24 | def on_all(self, data): 25 | self.lock.acquire() 26 | self.order_book[data["room"]["pair"]] = data 27 | self.lock.release() 28 | digest_hash = data["digest"] 29 | book = data["book"] 30 | book_digest_hash = sha1_hash_digest(stringify_message(book)) 31 | if digest_hash != book_digest_hash: 32 | self.emit(event="leave", data=data["room"], namespace='/v2/books') 33 | self.emit(event="join", data=data["room"], namespace='/v2/books') 34 | 35 | def on_updates(self, data): 36 | update_digest = data["digest"] 37 | update_pair = data["room"]["pair"] 38 | update_events = data["events"] 39 | buy_event = False 40 | sell_event = False 41 | if "symbol" in self.order_book[update_pair]["book"]: 42 | del self.order_book[update_pair]["book"]["symbol"] 43 | self.lock.acquire() 44 | for event in update_events: 45 | price_match = False 46 | event_iteration = 0 47 | if event["side"] == "buy": 48 | event_side = "buys" 49 | buy_event = True 50 | elif event["side"] == "sell": 51 | event_side = "sells" 52 | sell_event = True 53 | event_price = event["price"] 54 | event_change = event["delta"] 55 | for side in self.order_book[update_pair]["book"][event_side]: 56 | if side["price"] == event_price: 57 | price_match = True 58 | updated_amount = int(side["amount"]) + int(event_change) 59 | if updated_amount == 0: 60 | self.order_book[update_pair]["book"][event_side].remove(side) 61 | else: 62 | updated_book = {} 63 | updated_book["amount"] = str(updated_amount) 64 | updated_book["price"] = str(event_price) 65 | self.order_book[update_pair]["book"][event_side][event_iteration] = updated_book 66 | break 67 | event_iteration += 1 68 | if not price_match: 69 | new_book = {} 70 | new_book["amount"] = event_change 71 | new_book["price"] = event_price 72 | self.order_book[update_pair]["book"][event_side].append(new_book) 73 | if buy_event and sell_event: 74 | self.order_book[update_pair]["book"]["buys"] = sorted( 75 | self.order_book[update_pair]["book"]["buys"], key=itemgetter("price"), reverse=True) 76 | self.order_book[update_pair]["book"]["sells"] = sorted( 77 | self.order_book[update_pair]["book"]["sells"], key=itemgetter("price"), reverse=True) 78 | elif buy_event: 79 | self.order_book[update_pair]["book"]["buys"] = sorted( 80 | self.order_book[update_pair]["book"]["buys"], key=itemgetter("price"), reverse=True) 81 | elif sell_event: 82 | self.order_book[update_pair]["book"]["sells"] = sorted( 83 | self.order_book[update_pair]["book"]["sells"], key=itemgetter("price"), reverse=True) 84 | book = self.order_book[update_pair]["book"] 85 | self.lock.release() 86 | book_digest_hash = sha1_hash_digest(stringify_message(book)) 87 | if update_digest != book_digest_hash: 88 | self.emit(event="leave", data=data["room"], namespace='/v2/books') 89 | self.emit(event="join", data=data["room"], namespace='/v2/books') 90 | 91 | 92 | class TradeEventsNamespace(SocketIOClientNamespace): 93 | 94 | def __init__(self): 95 | self.lock = threading.Lock() 96 | self.namespace = '/v2/trades' 97 | self.trade_events = {} 98 | SocketIOClientNamespace.__init__(self, namespace=self.namespace) 99 | 100 | def on_connect(self): 101 | pass 102 | 103 | def on_disconnect(self): 104 | pass 105 | 106 | def on_join(self): 107 | pass 108 | 109 | def on_all(self, data): 110 | self.lock.acquire() 111 | self.trade_events[data["room"]["pair"]] = data 112 | self.lock.release() 113 | digest_hash = data["digest"] 114 | trades = data["trades"] 115 | trade_digest_hash = sha1_hash_digest(stringify_message(trades)) 116 | if digest_hash != trade_digest_hash: 117 | self.emit(event="leave", data=data["room"], namespace='/v2/trades') 118 | self.emit(event="join", data=data["room"], namespace='/v2/trades') 119 | 120 | def on_updates(self, data): 121 | update_digest = data["digest"] 122 | update_pair = data["room"]["pair"] 123 | update_events = data["events"] 124 | update_limit = data["limit"] 125 | self.lock.acquire() 126 | self.trade_events[update_pair]["trades"] = update_events + \ 127 | self.trade_events[update_pair]["trades"] 128 | trade_slice = update_limit - 1 129 | self.trade_events[update_pair]["trades"] = self.trade_events[update_pair]["trades"][0:trade_slice] 130 | trades = self.trade_events[update_pair]["trades"] 131 | self.lock.release() 132 | trade_digest_hash = sha1_hash_digest(stringify_message(trades)) 133 | if update_digest != trade_digest_hash: 134 | self.emit(event="leave", data=data["room"], namespace='/v2/trades') 135 | self.emit(event="join", data=data["room"], namespace='/v2/trades') 136 | 137 | 138 | class OrderEventsNamespace(SocketIOClientNamespace): 139 | 140 | def __init__(self): 141 | self.lock = threading.Lock() 142 | self.namespace = '/v2/orders' 143 | self.order_events = {} 144 | SocketIOClientNamespace.__init__(self, namespace=self.namespace) 145 | 146 | def on_connect(self): 147 | pass 148 | 149 | def on_disconnect(self): 150 | pass 151 | 152 | def on_join(self): 153 | pass 154 | 155 | def on_all(self, data): 156 | self.lock.acquire() 157 | self.order_events = data 158 | self.lock.release() 159 | 160 | def on_updates(self, data): 161 | update_events = data["events"] 162 | self.lock.acquire() 163 | self.order_events["orders"] + update_events 164 | self.lock.release() 165 | -------------------------------------------------------------------------------- /switcheo/switcheo_client.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | """ 3 | Description: 4 | Switcheo Client is designed to standardize interactions with the Python Client. 5 | It can access the Public and Authenticated Clients and is designed to be more user friendly than the 6 | forward facing REST API's. 7 | Ideally, more simplified/advanced trading functions will be built here (trailing stop, all or none, etc) 8 | Usage: 9 | from switcheo.switcheo_client import SwitcheoClient 10 | """ 11 | 12 | from switcheo.utils import current_contract_version 13 | from switcheo.authenticated_client import AuthenticatedClient 14 | from switcheo.public_client import PublicClient 15 | from switcheo.neo.utils import neo_get_scripthash_from_address 16 | 17 | network_dict = { 18 | "neo": "neo", 19 | "NEO": "neo", 20 | "eth": "eth", 21 | "ETH": "eth", 22 | "ethereum": "eth", 23 | "Ethereum": "eth" 24 | } 25 | 26 | url_dict = { 27 | "main": 'https://api.switcheo.network/', 28 | "test": 'https://test-api.switcheo.network/' 29 | } 30 | 31 | 32 | class SwitcheoClient(AuthenticatedClient, PublicClient): 33 | 34 | def __init__(self, 35 | switcheo_network="test", 36 | blockchain_network="neo", 37 | private_key=None): 38 | self.api_url = url_dict[switcheo_network] 39 | self.blockchain = network_dict[blockchain_network] 40 | self.contract_version = current_contract_version( 41 | PublicClient().get_latest_contracts()[self.blockchain.upper()], PublicClient().get_contracts()) 42 | super().__init__(blockchain=self.blockchain, 43 | contract_version=self.contract_version, 44 | api_url=self.api_url) 45 | self.private_key = private_key 46 | 47 | def order_history(self, address, pair=None): 48 | return self.get_orders(neo_get_scripthash_from_address(address=address), pair=pair) 49 | 50 | def balance_current_contract(self, *addresses): 51 | address_list = [] 52 | for address in addresses: 53 | address_list.append(neo_get_scripthash_from_address(address=address)) 54 | return self.get_balance(addresses=address_list, contracts=self.current_contract_hash) 55 | 56 | def balance_by_contract(self, *addresses): 57 | address_list = [] 58 | contract_dict = {} 59 | for address in addresses: 60 | address_list.append(neo_get_scripthash_from_address(address=address)) 61 | contracts = self.get_contracts() 62 | for blockchain in contracts: 63 | contract_dict[blockchain] = {} 64 | for key in contracts[blockchain]: 65 | contract_dict[blockchain][key] =\ 66 | self.get_balance(addresses=address_list, contracts=contracts[blockchain][key]) 67 | return contract_dict 68 | 69 | def balance_by_address_by_contract(self, *addresses): 70 | contract_dict = {} 71 | for address in addresses: 72 | contract_dict[address] = self.balance_by_contract(address) 73 | return contract_dict 74 | 75 | def limit_buy(self, price, quantity, pair, use_native_token=True): 76 | """ 77 | 78 | limit_buy(price=0.0002, quantity=1000, pair='SWTH_NEO') 79 | limit_buy(price=0.0000001, quantity=1000000, pair='JRC_ETH') 80 | 81 | :param price: 82 | :param quantity: 83 | :param pair: 84 | :param use_native_token: 85 | :return: 86 | """ 87 | if 'ETH' in pair: 88 | use_native_token = False 89 | return self.order(order_type="limit", 90 | side="buy", 91 | pair=pair, 92 | price=price, 93 | quantity=quantity, 94 | private_key=self.private_key, 95 | use_native_token=use_native_token) 96 | 97 | def limit_sell(self, price, quantity, pair, use_native_token=True): 98 | """ 99 | 100 | limit_sell(price=0.0006, quantity=500, pair='SWTH_NEO') 101 | limit_sell(price=0.000001, quantity=100000, pair='JRC_ETH') 102 | 103 | :param price: 104 | :param quantity: 105 | :param pair: 106 | :param use_native_token: 107 | :return: 108 | """ 109 | if 'ETH' in pair: 110 | use_native_token = False 111 | return self.order(order_type="limit", 112 | side="sell", 113 | pair=pair, 114 | price=price, 115 | quantity=quantity, 116 | private_key=self.private_key, 117 | use_native_token=use_native_token) 118 | 119 | def market_buy(self, quantity, pair, use_native_token=True): 120 | """ 121 | 122 | market_buy(quantity=100, pair='SWTH_NEO') 123 | market_buy(quantity=100000, pair='JRC_ETH') 124 | 125 | :param quantity: 126 | :param pair: 127 | :param use_native_token: 128 | :return: 129 | """ 130 | if 'ETH' in pair: 131 | use_native_token = False 132 | return self.order(order_type="market", 133 | side="buy", 134 | pair=pair, 135 | price=0, 136 | quantity=quantity, 137 | private_key=self.private_key, 138 | use_native_token=use_native_token) 139 | 140 | def market_sell(self, quantity, pair, use_native_token=True): 141 | """ 142 | 143 | market_sell(quantity=100, pair='SWTH_NEO') 144 | market_sell(quantity=100000, pair='JRC_ETH') 145 | 146 | :param quantity: 147 | :param pair: 148 | :param use_native_token: 149 | :return: 150 | """ 151 | if 'ETH' in pair: 152 | use_native_token = False 153 | return self.order(order_type="market", 154 | side="sell", 155 | pair=pair, 156 | price=0, 157 | quantity=quantity, 158 | private_key=self.private_key, 159 | use_native_token=use_native_token) 160 | -------------------------------------------------------------------------------- /switcheo/test_authenticated_client.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from switcheo.neo.utils import open_wallet 3 | from switcheo.authenticated_client import AuthenticatedClient 4 | 5 | 6 | ac = AuthenticatedClient(blockchain='neo') 7 | testnet_privatekey_hexstring = '70f642894bc73dc50013be6d1dbe198f43237eaf9458d193c0b416c5385d9717' 8 | kp = open_wallet(testnet_privatekey_hexstring) 9 | 10 | 11 | class TestAuthenticatedClient(unittest.TestCase): 12 | 13 | def test_deposit(self): 14 | deposited_dict = {'result': 'ok'} 15 | deposit_dict = ac.deposit(asset="SWTH", amount=0.000001, private_key=kp) 16 | deposit_dict.pop('transaction_hash') 17 | self.assertDictEqual(deposit_dict, deposited_dict) 18 | deposit_dict = ac.deposit(asset="GAS", amount=0.000001, private_key=kp) 19 | deposit_dict.pop('transaction_hash') 20 | self.assertDictEqual(deposit_dict, deposited_dict) 21 | 22 | def test_withdrawal(self): 23 | swth_withdrawn_dict = { 24 | 'event_type': 'withdrawal', 25 | 'amount': '-100', 26 | 'asset_id': 'ab38352559b8b203bde5fddfa0b07d8b2525e132', 27 | 'blockchain': 'neo', 28 | 'reason_code': 9, 29 | 'address': 'fea2b883725ef2d194c9060f606cd0a0468a2c59', 30 | 'transaction_hash': None, 31 | 'contract_hash': '58efbb3cca7f436a55b1a05c0f36788d2d9a032e', 32 | 'approval_transaction_hash': None, 33 | 'group_index': 0, 34 | 'params_hash': None 35 | } 36 | 37 | swth_withdrawal_dict = ac.withdrawal(asset="SWTH", amount=0.000001, private_key=kp) 38 | swth_withdrawal_dict.pop('id') 39 | swth_withdrawal_dict.pop('status') 40 | swth_withdrawal_dict.pop('created_at') 41 | swth_withdrawal_dict.pop('updated_at') 42 | self.assertDictEqual(swth_withdrawal_dict, swth_withdrawn_dict) 43 | 44 | gas_withdrawn_dict = { 45 | 'event_type': 'withdrawal', 46 | 'amount': '-100', 47 | 'asset_id': '602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7', 48 | 'blockchain': 'neo', 49 | 'reason_code': 9, 50 | 'address': 'fea2b883725ef2d194c9060f606cd0a0468a2c59', 51 | 'transaction_hash': None, 52 | 'contract_hash': '58efbb3cca7f436a55b1a05c0f36788d2d9a032e', 53 | 'approval_transaction_hash': None, 54 | 'group_index': 0, 55 | 'params_hash': None 56 | } 57 | gas_withdrawal_dict = ac.withdrawal(asset="GAS", amount=0.000001, private_key=kp) 58 | gas_withdrawal_dict.pop('id') 59 | gas_withdrawal_dict.pop('status') 60 | gas_withdrawal_dict.pop('created_at') 61 | gas_withdrawal_dict.pop('updated_at') 62 | self.assertDictEqual(gas_withdrawal_dict, gas_withdrawn_dict) 63 | 64 | def test_create_and_cancel_order(self): 65 | order = ac.order(pair="SWTH_NEO", side="buy", 66 | price=0.00001, quantity=10000, private_key=kp, 67 | use_native_token=True, order_type="limit") 68 | ac.cancel_order(order_id=order['id'], private_key=kp) 69 | testnet_scripthash = 'fea2b883725ef2d194c9060f606cd0a0468a2c59' 70 | cancelled = False 71 | for trade in ac.get_orders(address=testnet_scripthash): 72 | if trade['id'] == order['id'] and\ 73 | trade['status'] == 'processed' and\ 74 | trade['makes'][0]['status'] in ['cancelled', 'cancelling']: 75 | cancelled = True 76 | self.assertTrue(cancelled) 77 | 78 | def test_order_filter(self): 79 | # Test side filter 80 | with self.assertRaises(ValueError): 81 | ac.order(pair="SWTH_NEO", side="test", 82 | price=0.0001, quantity=100, private_key=kp, 83 | use_native_token=True, order_type="limit") 84 | # Test order_type filter 85 | with self.assertRaises(ValueError): 86 | ac.order(pair="SWTH_NEO", side="buy", 87 | price=0.0001, quantity=100, private_key=kp, 88 | use_native_token=True, order_type="test") 89 | -------------------------------------------------------------------------------- /switcheo/test_fixed8.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from switcheo.Fixed8 import SwitcheoFixed8, num2fixed8 3 | 4 | 5 | class TestFixed8(unittest.TestCase): 6 | 7 | def test_fixed8_inheritance(self): 8 | self.assertEqual(SwitcheoFixed8(2050000).ToString(), '0.0205') 9 | self.assertEqual(SwitcheoFixed8(100000000).ToInt(), 1) 10 | 11 | def test_to_hex(self): 12 | self.assertEqual(SwitcheoFixed8(205).toHex(), '00000004c5e52d00') 13 | self.assertEqual(SwitcheoFixed8(0.0205).toHex(), '00000000001f47d0') 14 | 15 | def test_to_reverse_hex(self): 16 | self.assertEqual(SwitcheoFixed8(205).toReverseHex(), '002de5c504000000') 17 | self.assertEqual(SwitcheoFixed8(0.0205).toReverseHex(), 'd0471f0000000000') 18 | 19 | def test_num2fixed8(self): 20 | self.assertEqual(num2fixed8(205), '002de5c504000000') 21 | self.assertEqual(num2fixed8(0.0205), 'd0471f0000000000') 22 | with self.assertRaises(ValueError): 23 | num2fixed8(205, size=1.1) 24 | -------------------------------------------------------------------------------- /switcheo/test_public_client.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import time 3 | from switcheo.public_client import PublicClient 4 | 5 | 6 | pc = PublicClient(blockchain='neo') 7 | pc_eth = PublicClient(blockchain='eth', contract_version='V2') 8 | 9 | 10 | class TestPublicClient(unittest.TestCase): 11 | 12 | def test_get_exchange_status(self): 13 | exchange_status_dict = {'status': 'ok'} 14 | self.assertDictEqual(pc.get_exchange_status(), exchange_status_dict) 15 | 16 | def test_get_exchange_time(self): 17 | exchange_time_dict = {'timestamp': 1533362081336} 18 | self.assertGreater(pc.get_exchange_time()['timestamp'], exchange_time_dict['timestamp']) 19 | 20 | def test_get_token_details(self): 21 | exchange_token_list = ['NEO', 'GAS', 'SWTH', 'ETH'] 22 | exchange_token_request = pc.get_token_details() 23 | self.assertTrue(set(exchange_token_request.keys()).issuperset(set(exchange_token_list))) 24 | 25 | def test_get_candlesticks(self): 26 | candles_key_list = ['time', 'open', 'close', 'high', 'low', 'volume', 'quote_volume'] 27 | candles_request = pc.get_candlesticks(pair="SWTH_NEO", 28 | start_time=round(time.time()) - 360000, 29 | end_time=round(time.time()), 30 | interval=60) 31 | for candle in candles_request: 32 | candles_request = list(candle.keys()) 33 | self.assertTrue(set(candles_request).issubset(set(candles_key_list))) 34 | 35 | def test_get_last_24_hours(self): 36 | last_stats_list = ['pair', 'open', 'close', 'high', 'low', 'volume', 'quote_volume'] 37 | last_24_hours_request = pc.get_last_24_hours() 38 | for pair in last_24_hours_request: 39 | last_24_hours_request = list(pair.keys()) 40 | self.assertTrue(set(last_24_hours_request).issubset(set(last_stats_list))) 41 | 42 | def test_get_last_price(self): 43 | last_price_dict = { 44 | 'SWTH': ['ETH', 'NEO'] 45 | } 46 | last_price_request = pc.get_last_price() 47 | for pair in last_price_request: 48 | last_price_request[pair] = list(last_price_request[pair].keys()) 49 | self.assertTrue(set(last_price_request.keys()).issuperset(set(last_price_dict.keys()))) 50 | for pair in last_price_dict: 51 | self.assertTrue(set(last_price_request[pair]).issubset(set(last_price_dict[pair]))) 52 | 53 | def test_get_offers(self): 54 | offers_list = [{ 55 | 'id': '023bff30-ca83-453c-90e9-95502b52f492', 56 | 'offer_asset': 'SWTH', 57 | 'want_asset': 'NEO', 58 | 'available_amount': 9509259, 59 | 'offer_amount': 9509259, 60 | 'want_amount': 9509259, 61 | 'address': '7f345d1a031c4099540dbbbc220d4e5640ab2b6f'}] 62 | offers_set_list = set(offers_list[0].keys()) 63 | offered_list = pc.get_offers() 64 | self.assertTrue(set(offered_list[0].keys()).issubset(offers_set_list)) 65 | offered_set_list = set() 66 | for offer in offered_list: 67 | for key in offer.keys(): 68 | offered_set_list.add(key) 69 | self.assertTrue(offered_set_list.issubset(offers_set_list)) 70 | 71 | offered_list = pc_eth.get_offers(pair="JRC_ETH") 72 | self.assertTrue(set(offered_list[0].keys()).issubset(offers_set_list)) 73 | offered_set_list = set() 74 | for offer in offered_list: 75 | for key in offer.keys(): 76 | offered_set_list.add(key) 77 | self.assertTrue(offered_set_list.issubset(offers_set_list)) 78 | 79 | def test_get_trades(self): 80 | trades_key_list = ['id', 'fill_amount', 'take_amount', 'event_time', 'is_buy'] 81 | trades_list = pc.get_trades(pair="SWTH_NEO", 82 | limit=1, 83 | start_time=int(round(time.time())) - 2419200, 84 | end_time=int(round(time.time()))) 85 | trades_list = trades_list[0].keys() 86 | self.assertTrue(set(trades_list).issubset(set(trades_key_list))) 87 | with self.assertRaises(ValueError): 88 | pc.get_trades(pair="SWTH_NEO", limit=0) 89 | with self.assertRaises(ValueError): 90 | pc.get_trades(pair="SWTH_NEO", limit=1000000) 91 | 92 | def test_get_pairs(self): 93 | all_pairs = ['GAS_NEO', 'SWTH_NEO', 'TMN_NEO', 'TKY_NEO', 'LEO_ETH', 'MKR_ETH', 'ETH_WBTC'] 94 | neo_pairs = ['GAS_NEO', 'SWTH_NEO', 'ACAT_NEO', 'ASA_NEO', 'AVA_NEO', 'FTWX_NEO', 'MCT_NEO', 95 | 'NOS_NEO', 'NRVE_NEO', 'PHX_NEO', 'QLC_NEO', 'SOUL_NEO', 'TKY_NEO', 'TMN_NEO'] 96 | self.assertTrue(set(pc.get_pairs(show_inactive=True)).issuperset(set(all_pairs))) 97 | self.assertTrue(set(pc.get_pairs(base="NEO", show_inactive=True)).issuperset(set(neo_pairs))) 98 | 99 | def test_get_contracts(self): 100 | contracts_dict = { 101 | 'NEO': { 102 | 'V1': '0ec5712e0f7c63e4b0fea31029a28cea5e9d551f', 103 | 'V1_5': 'c41d8b0c30252ce7e8b6d95e9ce13fdd68d2a5a8', 104 | 'V2': 'a195c1549e7da61b8da315765a790ac7e7633b82', 105 | 'V3': '58efbb3cca7f436a55b1a05c0f36788d2d9a032e' 106 | }, 107 | 'ETH': { 108 | 'V1': '0x4dcf0244742e72309666db20d367f6dd196e884e', 109 | 'V2': '0x4d19fd42e780d56ff6464fe9e7d5158aee3d125d' 110 | }, 111 | 'EOS': { 112 | 'V1': 'toweredbyob2' 113 | }, 114 | 'QTUM': { 115 | 'V1': 'fake_qtum_contract_hash' 116 | } 117 | } 118 | self.assertDictEqual(pc.get_contracts(), contracts_dict) 119 | 120 | def test_get_orders(self): 121 | orders_list = [{ 122 | 'id': 'ecb6ee9e-de8d-46d6-953b-afcc976be1ae', 123 | 'blockchain': 'neo', 124 | 'contract_hash': 'a195c1549e7da61b8da315765a790ac7e7633b82', 125 | 'address': 'fea2b883725ef2d194c9060f606cd0a0468a2c59', 126 | 'pair': 'SWTH_NEO', 127 | 'side': 'buy', 128 | 'price': '0.000001', 129 | 'quantity': '1000000', 130 | 'offer_asset_id': 'c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b', 131 | 'want_asset_id': 'ab38352559b8b203bde5fddfa0b07d8b2525e132', 132 | 'offer_amount': '6000000', 133 | 'want_amount': '30000000000', 134 | 'transfer_amount': '0', 135 | 'priority_gas_amount': '0', 136 | 'use_native_token': True, 137 | 'native_fee_transfer_amount': 0, 138 | 'deposit_txn': None, 139 | 'created_at': '2018-08-08T18:39:13.864Z', 140 | 'status': 'processed', 141 | 'order_status': 'processed', 142 | 'txn': None, 143 | 'offer_asset_blockchain': 'neo', 144 | 'want_asset_blockchain': 'neo', 145 | 'broadcast_cutoff_at': '2019-05-04T15:53:04.809Z', 146 | 'scheduled_cancellation_at': None, 147 | 'counterpart_swap': None, 148 | "unlock_swap_txn": None, 149 | 'fills': [], 150 | 'fill_groups': [], 151 | 'makes': [] 152 | }] 153 | switcheo_orders_list = orders_list.copy() 154 | orders_list = orders_list[0].keys() 155 | testnet_scripthash = 'fea2b883725ef2d194c9060f606cd0a0468a2c59' 156 | all_orders = pc.get_orders(address=testnet_scripthash) 157 | switcheo_orders = pc.get_orders(address=testnet_scripthash, pair="SWTH_NEO") 158 | self.assertGreaterEqual(len(all_orders), len(switcheo_orders)) 159 | self.assertTrue(set(all_orders[0].keys()).issubset(set(orders_list))) 160 | switcheo_orders_list_set = set() 161 | switcheo_orders_set = set() 162 | for order in switcheo_orders_list: 163 | switcheo_orders_list_set.add(order['offer_asset_id']) 164 | switcheo_orders_list_set.add(order['want_asset_id']) 165 | for order in switcheo_orders: 166 | switcheo_orders_set.add(order['offer_asset_id']) 167 | switcheo_orders_set.add(order['want_asset_id']) 168 | self.assertTrue(switcheo_orders_set.issubset(switcheo_orders_list_set)) 169 | 170 | def test_get_balance(self): 171 | balance_dict = { 172 | 'confirming': {}, 173 | 'confirmed': { 174 | 'GAS': '90000000.0', 175 | 'SWTH': '73580203956.0', 176 | 'NEO': '113073528.0'}, 177 | 'locked': { 178 | 'GAS': '9509259.0', 179 | 'NEO': '4000000.0'}} 180 | balance_dict_set = set(balance_dict.keys()) 181 | first_address = ['ca7316f459db1d3b444f57fe1ab875b3a607c200'] 182 | second_address = ['fea2b883725ef2d194c9060f606cd0a0468a2c59'] 183 | all_addresses = ['ca7316f459db1d3b444f57fe1ab875b3a607c200', 'fea2b883725ef2d194c9060f606cd0a0468a2c59'] 184 | contracts = pc.get_contracts() 185 | all_contracts = [] 186 | for chain in contracts: 187 | for contract in contracts[chain]: 188 | if chain == 'NEO': 189 | all_contracts.append(contracts[chain][contract]) 190 | first_balance_dict = pc.get_balance(addresses=first_address, contracts=all_contracts) 191 | first_balance_dict_set = set(first_balance_dict.keys()) 192 | second_balance_dict = pc.get_balance(addresses=second_address, contracts=all_contracts) 193 | second_balance_dict_set = set(second_balance_dict.keys()) 194 | all_balance_dict = pc.get_balance(addresses=all_addresses, contracts=all_contracts) 195 | all_balance_dict_set = set(all_balance_dict.keys()) 196 | self.assertTrue(first_balance_dict_set.issubset(balance_dict_set)) 197 | self.assertTrue(second_balance_dict_set.issubset(balance_dict_set)) 198 | self.assertTrue(all_balance_dict_set.issubset(balance_dict_set)) 199 | 200 | sum_balance_dict = {'confirmed': { 201 | 'GAS': str(int(float(first_balance_dict['confirmed']['GAS'])) + int( 202 | float(second_balance_dict['confirmed']['GAS']))), 203 | 'NEO': str(int(float(first_balance_dict['confirmed']['NEO'])) + int( 204 | float(second_balance_dict['confirmed']['NEO']))), 205 | 'SWTH': str(int(float(first_balance_dict['confirmed']['SWTH'])) + int( 206 | float(second_balance_dict['confirmed']['SWTH']))), 207 | }} 208 | self.assertDictEqual(all_balance_dict['confirmed'], sum_balance_dict['confirmed']) 209 | -------------------------------------------------------------------------------- /switcheo/test_switcheo_client.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from switcheo.switcheo_client import SwitcheoClient 3 | 4 | 5 | sc = SwitcheoClient() 6 | testnet_address1 = 'APuP9GsSCPJKrexPe49afDV8CQYubZGWd8' 7 | testnet_address2 = 'AFqt5vxyg4KKVTcTV4sR5oYMyUGCbrMQVt' 8 | 9 | 10 | class TestSwitcheoClient(unittest.TestCase): 11 | 12 | def test_order_history(self): 13 | orders_list = [{ 14 | 'id': 'ecb6ee9e-de8d-46d6-953b-afcc976be1ae', 15 | 'blockchain': 'neo', 16 | 'contract_hash': 'a195c1549e7da61b8da315765a790ac7e7633b82', 17 | 'address': 'fea2b883725ef2d194c9060f606cd0a0468a2c59', 18 | 'pair': 'SWTH_NEO', 19 | 'side': 'buy', 20 | 'price': '0.00001', 21 | 'quantity': '1000000000000', 22 | 'offer_asset_id': 'c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b', 23 | 'want_asset_id': 'ab38352559b8b203bde5fddfa0b07d8b2525e132', 24 | 'offer_amount': '6000000', 25 | 'want_amount': '30000000000', 26 | 'transfer_amount': '0', 27 | 'priority_gas_amount': '0', 28 | 'use_native_token': True, 29 | 'native_fee_transfer_amount': 0, 30 | 'deposit_txn': None, 31 | 'created_at': '2018-08-08T18:39:13.864Z', 32 | 'status': 'processed', 33 | 'order_status': 'cancelled', 34 | 'txn': None, 35 | 'offer_asset_blockchain': 'neo', 36 | 'want_asset_blockchain': 'neo', 37 | 'broadcast_cutoff_at': '2019-05-04T15:53:04.809Z', 38 | 'scheduled_cancellation_at': None, 39 | 'counterpart_swap': None, 40 | "unlock_swap_txn": None, 41 | 'fills': [], 42 | 'fill_groups': [], 43 | 'makes': [] 44 | }] 45 | orders_list = orders_list[0].keys() 46 | all_orders = sc.order_history(address=testnet_address1) 47 | self.assertTrue(set(all_orders[0].keys()).issubset(set(orders_list))) 48 | 49 | def test_balance_current_contract(self): 50 | expected_balance_current_contract_child_key_set = set(['confirming', 'confirmed', 'locked']) 51 | balance_current_contract = sc.balance_current_contract(testnet_address1) 52 | balance_current_contract_child_key_set = set(balance_current_contract.keys()) 53 | self.assertTrue( 54 | balance_current_contract_child_key_set.issubset(expected_balance_current_contract_child_key_set)) 55 | 56 | def test_balance_by_contract(self): 57 | expected_balance_by_contract_key_set = set(['NEO', 'ETH', 'QTUM', 'EOS']) 58 | expected_balance_by_contract_child_key_set = set(['V1', 'V1_5', 'V2', 'V3']) 59 | expected_balance_by_contract_sub_key_set = set(['confirming', 'confirmed', 'locked']) 60 | balance_by_contract = sc.balance_by_contract(testnet_address1) 61 | balance_by_contract_key_set = set(balance_by_contract.keys()) 62 | self.assertTrue(balance_by_contract_key_set.issubset(expected_balance_by_contract_key_set)) 63 | balance_by_contract_neo = balance_by_contract['NEO'] 64 | balance_by_contract_eth = balance_by_contract['ETH'] 65 | balance_by_contract_child_key_set = set(balance_by_contract_neo.keys()) 66 | self.assertTrue(balance_by_contract_child_key_set.issubset(expected_balance_by_contract_child_key_set)) 67 | balance_by_contract_sub_key_set = set(balance_by_contract_neo['V1']) 68 | self.assertTrue(balance_by_contract_sub_key_set.issubset(expected_balance_by_contract_sub_key_set)) 69 | balance_by_contract_child_key_set = set(balance_by_contract_eth.keys()) 70 | self.assertTrue(balance_by_contract_child_key_set.issubset(expected_balance_by_contract_child_key_set)) 71 | balance_by_contract_sub_key_set = set(balance_by_contract_eth['V1']) 72 | self.assertTrue(balance_by_contract_sub_key_set.issubset(expected_balance_by_contract_sub_key_set)) 73 | 74 | def test_balance_by_address_by_contract(self): 75 | expected_balance_by_address_key_set = set([testnet_address1, testnet_address2]) 76 | balance_by_address_key_set = set(sc.balance_by_address_by_contract(testnet_address1, testnet_address2).keys()) 77 | self.assertTrue(balance_by_address_key_set.issubset(expected_balance_by_address_key_set)) 78 | -------------------------------------------------------------------------------- /switcheo/test_switcheo_utils.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from switcheo.utils import get_epoch_milliseconds, num2hexstring, num2varint, reverse_hex,\ 3 | stringify_message, current_contract_hash, Request 4 | from switcheo.public_client import PublicClient 5 | 6 | 7 | r = Request(api_url='https://jsonplaceholder.typicode.com/', api_version='') 8 | s = Request() 9 | 10 | 11 | class TestSwitcheoUtils(unittest.TestCase): 12 | 13 | def test_get_epoch_milliseconds(self): 14 | self.assertGreaterEqual(get_epoch_milliseconds(), 13) 15 | 16 | def test_num2hexstring(self): 17 | self.assertEqual(num2hexstring(0), '00') 18 | self.assertEqual(num2hexstring(255), 'ff') 19 | self.assertEqual(num2hexstring(256, size=2, little_endian=True), '0001') 20 | self.assertEqual(num2hexstring(2222, size=2, little_endian=True), 'ae08') 21 | 22 | def test_num2varint(self): 23 | self.assertEqual(num2varint(0), '00') 24 | self.assertEqual(num2varint(252), 'fc') 25 | self.assertEqual(num2varint(253), 'fdfd00') 26 | self.assertEqual(num2varint(255), 'fdff00') 27 | self.assertEqual(num2varint(256), 'fd0001') 28 | self.assertEqual(num2varint(2222), 'fdae08') 29 | self.assertEqual(num2varint(111111), 'fe07b20100') 30 | self.assertEqual(num2varint(11111111111), 'ffc719469602000000') 31 | 32 | def test_reverse_hex(self): 33 | self.assertEqual(reverse_hex('ABCD'), 'CDAB') 34 | self.assertEqual(reverse_hex('0000000005f5e100'), '00e1f50500000000') 35 | 36 | def test_stringify_message(self): 37 | json_msg = {"name": "John Smith", "age": 27, "siblings": ["Jane", "Joe"]} 38 | stringify_msg = '{"age":27,"name":"John Smith","siblings":["Jane","Joe"]}' 39 | self.assertEqual(stringify_message(json_msg), stringify_msg) 40 | 41 | def test_current_contract_hash(self): 42 | pc = PublicClient() 43 | expected_current_contract_dict = { 44 | 'NEO': '58efbb3cca7f436a55b1a05c0f36788d2d9a032e', 45 | 'ETH': '0x4d19fd42e780d56ff6464fe9e7d5158aee3d125d', 46 | 'QTUM': 'fake_qtum_contract_hash', 47 | 'EOS': 'toweredbyob2' 48 | } 49 | self.assertDictEqual(current_contract_hash(pc.contracts), expected_current_contract_dict) 50 | 51 | def test_request_get(self): 52 | json_msg = { 53 | "userId": 1, 54 | "id": 1, 55 | "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit", 56 | "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"} 57 | self.assertDictEqual(r.get(path='/posts/1'), json_msg) 58 | 59 | def test_request_post(self): 60 | json_dict = { 61 | 'title': 'foo', 62 | 'body': 'bar', 63 | 'userId': 1} 64 | json_msg = { 65 | 'id': 101, 66 | 'title': 'foo', 67 | 'body': 'bar', 68 | 'userId': 1} 69 | self.assertDictEqual(r.post(path='/posts', json_data=json_dict), json_msg) 70 | 71 | def test_request_status(self): 72 | self.assertDictEqual(s.status(), {'status': 'ok'}) 73 | -------------------------------------------------------------------------------- /switcheo/utils.py: -------------------------------------------------------------------------------- 1 | # 2 | # switcheo/utils.py 3 | # Keith Smith 4 | # 5 | # For testnet requests to the Switcheo exchange 6 | 7 | import json 8 | import requests 9 | import time 10 | import hashlib 11 | 12 | 13 | def get_epoch_milliseconds(): 14 | return int(round(time.time() * 1000)) 15 | 16 | 17 | def stringify_message(message): 18 | """Return a JSON message that is alphabetically sorted by the key name 19 | 20 | Args: 21 | message 22 | """ 23 | return json.dumps(message, sort_keys=True, separators=(',', ':')) 24 | 25 | 26 | def sha1_hash_digest(message): 27 | """ 28 | Converts Stringified (JavaScript) JSON to a SHA-1 Hash. 29 | 30 | Args: 31 | message 32 | """ 33 | return hashlib.sha1(message.encode()).hexdigest() 34 | 35 | 36 | def reverse_hex(message): 37 | return "".join([message[x:x + 2] for x in range(0, len(message), 2)][::-1]) 38 | 39 | 40 | def num2hexstring(number, size=1, little_endian=False): 41 | """ 42 | Converts a number to a big endian hexstring of a suitable size, optionally little endian 43 | :param {number} number 44 | :param {number} size - The required size in hex chars, eg 2 for Uint8, 4 for Uint16. Defaults to 2. 45 | :param {boolean} little_endian - Encode the hex in little endian form 46 | :return {string} 47 | """ 48 | # if (type(number) != = 'number') throw new Error('num must be numeric') 49 | # if (num < 0) throw new RangeError('num is unsigned (>= 0)') 50 | # if (size % 1 !== 0) throw new Error('size must be a whole integer') 51 | # if (!Number.isSafeInteger(num)) throw new RangeError(`num (${num}) must be a safe integer`) 52 | size = size * 2 53 | hexstring = hex(number)[2:] 54 | if len(hexstring) % size != 0: 55 | hexstring = ('0' * size + hexstring)[len(hexstring):] 56 | if little_endian: 57 | hexstring = reverse_hex(hexstring) 58 | return hexstring 59 | 60 | 61 | def num2varint(num): 62 | """ 63 | Converts a number to a variable length Int. Used for array length header 64 | 65 | :param: {number} num - The number 66 | :return: {string} hexstring of the variable Int. 67 | """ 68 | # if (typeof num !== 'number') throw new Error('VarInt must be numeric') 69 | # if (num < 0) throw new RangeError('VarInts are unsigned (> 0)') 70 | # if (!Number.isSafeInteger(num)) throw new RangeError('VarInt must be a safe integer') 71 | if num < 0xfd: 72 | return num2hexstring(num) 73 | elif num <= 0xffff: 74 | # uint16 75 | return 'fd' + num2hexstring(number=num, size=2, little_endian=True) 76 | elif num <= 0xffffffff: 77 | # uint32 78 | return 'fe' + num2hexstring(number=num, size=4, little_endian=True) 79 | else: 80 | # uint64 81 | return 'ff' + num2hexstring(number=num, size=8, little_endian=True) 82 | 83 | 84 | def current_contract_hash(contracts): 85 | contract_dict = {} 86 | for chain in contracts: 87 | max_key = 1 88 | for key in contracts[chain].keys(): 89 | if float(key[1:].replace('_', '.')) > max_key: 90 | max_key = float(key[1:].replace('_', '.')) 91 | max_key_str = 'V' + str(max_key).replace('.', '_').replace('_0', '') 92 | contract_dict[chain] = contracts[chain][max_key_str] 93 | return contract_dict 94 | 95 | 96 | def current_contract_version(contract, contracts): 97 | contract_dict = {} 98 | for chain in contracts: 99 | for key in contracts[chain].keys(): 100 | contract_hash = contracts[chain][key] 101 | contract_dict[contract_hash] = key 102 | return contract_dict[contract] 103 | 104 | 105 | class SwitcheoApiException(Exception): 106 | 107 | def __init__(self, error_code, error_message, error): 108 | super(SwitcheoApiException, self).__init__(error_message) 109 | self.error_code = error_code 110 | self.error = error 111 | 112 | 113 | class Request(object): 114 | 115 | def __init__(self, api_url='https://test-api.switcheo.network/', api_version="/v2", timeout=30): 116 | self.base_url = api_url.rstrip('/') 117 | self.url = self.base_url + api_version 118 | self.timeout = timeout 119 | 120 | def get(self, path, params=None): 121 | """Perform GET request""" 122 | r = requests.get(url=self.url + path, params=params, timeout=self.timeout) 123 | r.raise_for_status() 124 | return r.json() 125 | 126 | def post(self, path, data=None, json_data=None, params=None): 127 | """Perform POST request""" 128 | r = requests.post(url=self.url + path, data=data, json=json_data, params=params, timeout=self.timeout) 129 | try: 130 | r.raise_for_status() 131 | except requests.exceptions.HTTPError: 132 | raise SwitcheoApiException(r.json().get('error_code'), r.json().get('error_message'), r.json().get('error')) 133 | return r.json() 134 | 135 | def status(self): 136 | r = requests.get(url=self.base_url) 137 | r.raise_for_status() 138 | return r.json() 139 | --------------------------------------------------------------------------------