New demos for Python packages (#1734)
End-to-end demos of constructing and signing an order and submitting it to a Relayer. Docs are generated from the code, and include usage examples that are verified through automated testing.
This commit is contained in:
parent
28c4ca73ab
commit
3099ba71eb
@ -17,7 +17,6 @@ PACKAGE_DEPENDENCY_LIST = [
|
||||
"sra_client",
|
||||
"order_utils",
|
||||
"middlewares",
|
||||
"contract_demo"
|
||||
]
|
||||
|
||||
for package in PACKAGE_DEPENDENCY_LIST:
|
||||
|
@ -1,13 +0,0 @@
|
||||
{
|
||||
"domain": "0x-contract-demo-py",
|
||||
"build_command": "python setup.py build_sphinx",
|
||||
"upload_directory": "build/docs/html",
|
||||
"index_key": "index.html",
|
||||
"error_key": "index.html",
|
||||
"trailing_slashes": true,
|
||||
"cache": 3600,
|
||||
"aws_profile": "default",
|
||||
"aws_region": "us-east-1",
|
||||
"cdn": false,
|
||||
"dns_configured": true
|
||||
}
|
@ -1,39 +0,0 @@
|
||||
## 0x-contract-demo
|
||||
|
||||
A demonstration of calling 0x smart contracts from Python.
|
||||
|
||||
Read the [documentation](http://0x-contract-demo-py.s3-website-us-east-1.amazonaws.com/)
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome improvements and fixes from the wider community! To report bugs within this package, please create an issue in this repository.
|
||||
|
||||
Please read our [contribution guidelines](../../CONTRIBUTING.md) before getting started.
|
||||
|
||||
### Install Code and Dependencies
|
||||
|
||||
Ensure that you have installed Python >=3.6 and Docker. Then:
|
||||
|
||||
```bash
|
||||
pip install -e .[dev]
|
||||
```
|
||||
|
||||
### Test
|
||||
|
||||
Tests depend on a running ganache instance with the 0x contracts deployed in it. For convenience, a docker container is provided that has ganache-cli and a snapshot containing the necessary contracts. A shortcut is provided to run that docker container: `./setup.py ganache`. With that running, the tests can be run with `./setup.py test`.
|
||||
|
||||
### Clean
|
||||
|
||||
`./setup.py clean --all`
|
||||
|
||||
### Lint
|
||||
|
||||
`./setup.py lint`
|
||||
|
||||
### Build Documentation
|
||||
|
||||
`./setup.py build_sphinx`
|
||||
|
||||
### More
|
||||
|
||||
See `./setup.py --help-commands` for more info.
|
@ -1,146 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
"""setuptools module for 0x-contract-demo package."""
|
||||
|
||||
import distutils.command.build_py
|
||||
from distutils.command.clean import clean
|
||||
import subprocess # nosec
|
||||
from shutil import rmtree
|
||||
from os import environ, path
|
||||
from sys import argv
|
||||
|
||||
from setuptools import setup
|
||||
from setuptools.command.test import test as TestCommand
|
||||
|
||||
|
||||
class TestCommandExtension(TestCommand):
|
||||
"""Run pytest tests."""
|
||||
|
||||
def run_tests(self):
|
||||
"""Invoke pytest."""
|
||||
import pytest
|
||||
|
||||
exit(pytest.main())
|
||||
|
||||
|
||||
class LintCommand(distutils.command.build_py.build_py):
|
||||
"""Custom setuptools command class for running linters."""
|
||||
|
||||
description = "Run linters"
|
||||
|
||||
def run(self):
|
||||
"""Run linter shell commands."""
|
||||
lint_commands = [
|
||||
# formatter:
|
||||
"black --line-length 79 --check --diff test setup.py".split(),
|
||||
# style guide checker (formerly pep8):
|
||||
"pycodestyle test setup.py".split(),
|
||||
# docstring style checker:
|
||||
"pydocstyle test setup.py".split(),
|
||||
# static type checker:
|
||||
"mypy test setup.py".split(),
|
||||
# security issue checker:
|
||||
"bandit -r ./setup.py".split(),
|
||||
# general linter:
|
||||
"pylint test setup.py".split(),
|
||||
# pylint takes relatively long to run, so it runs last, to enable
|
||||
# fast failures.
|
||||
]
|
||||
|
||||
# tell mypy where to find interface stubs for 3rd party libs
|
||||
environ["MYPYPATH"] = path.join(
|
||||
path.dirname(path.realpath(argv[0])), "stubs"
|
||||
)
|
||||
|
||||
for lint_command in lint_commands:
|
||||
print(
|
||||
"Running lint command `", " ".join(lint_command).strip(), "`"
|
||||
)
|
||||
subprocess.check_call(lint_command) # nosec
|
||||
|
||||
|
||||
class CleanCommandExtension(clean):
|
||||
"""Custom command to do custom cleanup."""
|
||||
|
||||
def run(self):
|
||||
"""Run the regular clean, followed by our custom commands."""
|
||||
super().run()
|
||||
rmtree(".mypy_cache", ignore_errors=True)
|
||||
rmtree(".tox", ignore_errors=True)
|
||||
rmtree(".pytest_cache", ignore_errors=True)
|
||||
|
||||
|
||||
class GanacheCommand(distutils.command.build_py.build_py):
|
||||
"""Custom command to publish to pypi.org."""
|
||||
|
||||
description = "Run ganache daemon to support tests."
|
||||
|
||||
def run(self):
|
||||
"""Run ganache."""
|
||||
cmd_line = "docker run -d -p 8545:8545 0xorg/ganache-cli:2.2.2".split()
|
||||
subprocess.call(cmd_line) # nosec
|
||||
|
||||
|
||||
class PublishDocsCommand(distutils.command.build_py.build_py):
|
||||
"""Custom command to publish docs to S3."""
|
||||
|
||||
description = (
|
||||
"Publish docs to "
|
||||
+ "http://0x-contract-addresses-py.s3-website-us-east-1.amazonaws.com/"
|
||||
)
|
||||
|
||||
def run(self):
|
||||
"""Run npm package `discharge` to build & upload docs."""
|
||||
subprocess.check_call("discharge deploy".split()) # nosec
|
||||
|
||||
|
||||
setup(
|
||||
name="0x-contract-demo",
|
||||
version="1.0.0",
|
||||
description="Demonstration of calling 0x contracts",
|
||||
url=(
|
||||
"https://github.com/0xProject/0x-monorepo/tree/development"
|
||||
+ "/python-packages/contract_demo"
|
||||
),
|
||||
author="F. Eugene Aumson",
|
||||
author_email="feuGeneA@users.noreply.github.com",
|
||||
cmdclass={
|
||||
"clean": CleanCommandExtension,
|
||||
"lint": LintCommand,
|
||||
"test": TestCommandExtension,
|
||||
"ganache": GanacheCommand,
|
||||
"publish_docs": PublishDocsCommand,
|
||||
},
|
||||
install_requires=[
|
||||
"0x-contract-addresses",
|
||||
"0x-contract-artifacts",
|
||||
"0x-order-utils",
|
||||
"0x-web3", # TEMPORARY! pending resolution of our web3.py PR#1147
|
||||
"mypy_extensions",
|
||||
],
|
||||
extras_require={
|
||||
"dev": [
|
||||
"bandit",
|
||||
"black",
|
||||
"coverage",
|
||||
"coveralls",
|
||||
"mypy",
|
||||
"mypy_extensions",
|
||||
"pycodestyle",
|
||||
"pydocstyle",
|
||||
"pylint",
|
||||
"pytest",
|
||||
"sphinx",
|
||||
"tox",
|
||||
]
|
||||
},
|
||||
python_requires=">=3.6, <4",
|
||||
license="Apache 2.0",
|
||||
zip_safe=False, # required per mypy
|
||||
command_options={
|
||||
"build_sphinx": {
|
||||
"source_dir": ("setup.py", "test"),
|
||||
"build_dir": ("setup.py", "build/docs"),
|
||||
}
|
||||
},
|
||||
)
|
@ -1,7 +0,0 @@
|
||||
from distutils.core import Command
|
||||
|
||||
class clean(Command):
|
||||
def initialize_options(self: clean) -> None: ...
|
||||
def finalize_options(self: clean) -> None: ...
|
||||
def run(self: clean) -> None: ...
|
||||
...
|
@ -1,7 +0,0 @@
|
||||
from distutils.core import Command
|
||||
|
||||
class clean(Command):
|
||||
def initialize_options(self: clean) -> None: ...
|
||||
def finalize_options(self: clean) -> None: ...
|
||||
def run(self: clean) -> None: ...
|
||||
...
|
@ -1,4 +0,0 @@
|
||||
from typing import Union
|
||||
|
||||
def to_checksum_address(value: Union[str, bytes]) -> str:
|
||||
...
|
@ -1,8 +0,0 @@
|
||||
from distutils.dist import Distribution
|
||||
from typing import Any, List
|
||||
|
||||
def setup(**attrs: Any) -> Distribution: ...
|
||||
|
||||
class Command: ...
|
||||
|
||||
def find_packages(where: str) -> List[str]: ...
|
@ -1,3 +0,0 @@
|
||||
from setuptools import Command
|
||||
|
||||
class test(Command): ...
|
@ -1,2 +0,0 @@
|
||||
class Web3:
|
||||
...
|
@ -1,3 +0,0 @@
|
||||
class Contract:
|
||||
def call(self): ...
|
||||
...
|
@ -1 +0,0 @@
|
||||
"""Demonstrations of calling 0x smart contracts."""
|
@ -1,117 +0,0 @@
|
||||
"""Test calling methods on the Exchange contract."""
|
||||
|
||||
from eth_utils import to_checksum_address
|
||||
from web3 import Web3
|
||||
from web3.utils import datatypes
|
||||
|
||||
from zero_ex.contract_addresses import NETWORK_TO_ADDRESSES, NetworkId
|
||||
import zero_ex.contract_artifacts
|
||||
from zero_ex.json_schemas import assert_valid
|
||||
from zero_ex.order_utils import (
|
||||
Order,
|
||||
OrderInfo,
|
||||
order_to_jsdict,
|
||||
generate_order_hash_hex,
|
||||
)
|
||||
|
||||
|
||||
def test_get_order_info():
|
||||
"""Demonstrate Exchange.getOrderInfo()."""
|
||||
order: Order = {
|
||||
"makerAddress": "0x0000000000000000000000000000000000000000",
|
||||
"takerAddress": "0x0000000000000000000000000000000000000000",
|
||||
"feeRecipientAddress": "0x0000000000000000000000000000000000000000",
|
||||
"senderAddress": "0x0000000000000000000000000000000000000000",
|
||||
"makerAssetAmount": 1000000000000000000,
|
||||
"takerAssetAmount": 1000000000000000000,
|
||||
"makerFee": 0,
|
||||
"takerFee": 0,
|
||||
"expirationTimeSeconds": 12345,
|
||||
"salt": 12345,
|
||||
"makerAssetData": (b"\x00") * 20,
|
||||
"takerAssetData": (b"\x00") * 20,
|
||||
}
|
||||
|
||||
web3_instance = Web3(Web3.HTTPProvider("http://127.0.0.1:8545"))
|
||||
|
||||
# false positive from pylint: disable=no-member
|
||||
contract_address = NETWORK_TO_ADDRESSES[
|
||||
NetworkId(int(web3_instance.net.version))
|
||||
].exchange
|
||||
|
||||
assert_valid(
|
||||
order_to_jsdict(order, exchange_address=contract_address),
|
||||
"/orderSchema",
|
||||
)
|
||||
|
||||
# false positive from pylint: disable=no-member
|
||||
exchange: datatypes.Contract = web3_instance.eth.contract(
|
||||
address=to_checksum_address(contract_address),
|
||||
abi=zero_ex.contract_artifacts.abi_by_name("Exchange"),
|
||||
)
|
||||
|
||||
order_info = OrderInfo(*exchange.call().getOrderInfo(order))
|
||||
|
||||
assert isinstance(order_info.order_status, int)
|
||||
assert order_info.order_status == 4
|
||||
|
||||
assert isinstance(order_info.order_hash, bytes)
|
||||
assert order_info.order_hash.hex() == generate_order_hash_hex(
|
||||
order,
|
||||
exchange_address=NETWORK_TO_ADDRESSES[NetworkId.GANACHE].exchange,
|
||||
)
|
||||
|
||||
assert isinstance(order_info.order_taker_asset_filled_amount, int)
|
||||
assert order_info.order_taker_asset_filled_amount == 0
|
||||
|
||||
|
||||
def test_get_orders_info():
|
||||
"""Demonstrate Exchange.getOrderInfo()."""
|
||||
order: Order = {
|
||||
"makerAddress": "0x0000000000000000000000000000000000000000",
|
||||
"takerAddress": "0x0000000000000000000000000000000000000000",
|
||||
"feeRecipientAddress": "0x0000000000000000000000000000000000000000",
|
||||
"senderAddress": "0x0000000000000000000000000000000000000000",
|
||||
"makerAssetAmount": 1000000000000000000,
|
||||
"takerAssetAmount": 1000000000000000000,
|
||||
"makerFee": 0,
|
||||
"takerFee": 0,
|
||||
"expirationTimeSeconds": 12345,
|
||||
"salt": 12345,
|
||||
"makerAssetData": (b"\x00") * 20,
|
||||
"takerAssetData": (b"\x00") * 20,
|
||||
}
|
||||
|
||||
web3_instance = Web3(Web3.HTTPProvider("http://127.0.0.1:8545"))
|
||||
|
||||
# false positive from pylint: disable=no-member
|
||||
contract_address = NETWORK_TO_ADDRESSES[
|
||||
NetworkId(int(web3_instance.net.version))
|
||||
].exchange
|
||||
|
||||
assert_valid(
|
||||
order_to_jsdict(order, exchange_address=contract_address),
|
||||
"/orderSchema",
|
||||
)
|
||||
|
||||
# false positive from pylint: disable=no-member
|
||||
exchange: datatypes.Contract = web3_instance.eth.contract(
|
||||
address=to_checksum_address(contract_address),
|
||||
abi=zero_ex.contract_artifacts.abi_by_name("Exchange"),
|
||||
)
|
||||
|
||||
orders_info = exchange.call().getOrdersInfo([order])
|
||||
|
||||
for order_info in orders_info:
|
||||
order_info = OrderInfo(*order_info)
|
||||
assert isinstance(order_info.order_status, int)
|
||||
assert order_info.order_status == 4
|
||||
|
||||
assert isinstance(order_info.order_hash, bytes)
|
||||
assert order_info.order_hash.hex() == generate_order_hash_hex(
|
||||
order,
|
||||
exchange_address=NETWORK_TO_ADDRESSES[NetworkId.GANACHE].exchange,
|
||||
)
|
||||
|
||||
assert isinstance(order_info.order_taker_asset_filled_amount, int)
|
||||
assert order_info.order_taker_asset_filled_amount == 0
|
@ -1,4 +1,31 @@
|
||||
"""JSON schemas and associated utilities."""
|
||||
"""JSON schemas and associated utilities.
|
||||
|
||||
Validating a 0x Order
|
||||
---------------------
|
||||
|
||||
Here is an example on how to validate a 0x order.
|
||||
|
||||
>>> from zero_ex.json_schemas import assert_valid
|
||||
>>> example_order = {
|
||||
... 'makerAddress': '0x5409ed021d9299bf6814279a6a1411a7e866a631',
|
||||
... 'takerAddress': '0x0000000000000000000000000000000000000000',
|
||||
... 'senderAddress': '0x0000000000000000000000000000000000000000',
|
||||
... 'exchangeAddress': '0x4f833a24e1f95d70f028921e27040ca56e09ab0b',
|
||||
... 'feeRecipientAddress':
|
||||
... '0x0000000000000000000000000000000000000000',
|
||||
... 'makerAssetData': '0xf47261b0000000000000000000000000'
|
||||
... 'c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
|
||||
... 'takerAssetData': '0xf47261b0000000000000000000000000'
|
||||
... 'e41d2489571d322189246dafa5ebde1f4699f498',
|
||||
... 'salt': 123456789,
|
||||
... 'makerFee': 0,
|
||||
... 'takerFee': 0,
|
||||
... 'makerAssetAmount': 1000000000000000000,
|
||||
... 'takerAssetAmount': 500000000000000000000,
|
||||
... 'expirationTimeSeconds': 1553553429
|
||||
... }
|
||||
>>> assert_valid(example_order, "/orderSchema")
|
||||
"""
|
||||
|
||||
from os import path
|
||||
import json
|
||||
|
@ -6,7 +6,55 @@ For local testing one may construct such a provider pointing at an instance of
|
||||
contracts deployed on it. For convenience, a docker container is provided for
|
||||
just this purpose. To start it:
|
||||
`docker run -d -p 8545:8545 0xorg/ganache-cli:2.2.2`:code:.
|
||||
"""
|
||||
|
||||
Creating a 0x Order
|
||||
--------------------
|
||||
|
||||
Here is a short demonstration on how to create a 0x order.
|
||||
|
||||
>>> import pprint
|
||||
>>> from zero_ex.contract_addresses import (
|
||||
... NETWORK_TO_ADDRESSES, NetworkId)
|
||||
>>> from zero_ex.order_utils import asset_data_utils, Order
|
||||
>>> NULL_ADDRESS = "0x0000000000000000000000000000000000000000"
|
||||
>>> my_address = "0x5409ed021d9299bf6814279a6a1411a7e866a631"
|
||||
>>> exchange_address = NETWORK_TO_ADDRESSES[NetworkId.MAINNET].exchange
|
||||
>>> weth_address = NETWORK_TO_ADDRESSES[NetworkId.MAINNET].ether_token
|
||||
>>> zrx_address = NETWORK_TO_ADDRESSES[NetworkId.MAINNET].zrx_token
|
||||
>>> maker_asset_data = (
|
||||
... asset_data_utils.encode_erc20_asset_data(weth_address))
|
||||
>>> taker_asset_data = (
|
||||
... asset_data_utils.encode_erc20_asset_data(zrx_address))
|
||||
>>> example_order: Order = {
|
||||
... "makerAddress": my_address,
|
||||
... "takerAddress": NULL_ADDRESS,
|
||||
... "exchangeAddress": exchange_address,
|
||||
... "senderAddress": NULL_ADDRESS,
|
||||
... "feeRecipientAddress": NULL_ADDRESS,
|
||||
... "makerAssetData": maker_asset_data,
|
||||
... "takerAssetData": taker_asset_data,
|
||||
... "salt": 123456789,
|
||||
... "makerFee": 0,
|
||||
... "takerFee": 0,
|
||||
... "makerAssetAmount": 1 * 10 ** 18, # Converting token amount to base unit with 18 decimals
|
||||
... "takerAssetAmount": 500 * 10 ** 18, # Converting token amount to base unit with 18 decimals
|
||||
... "expirationTimeSeconds": 1553553429,
|
||||
... }
|
||||
>>> pprint.pprint(example_order)
|
||||
{'exchangeAddress': '0x4f833a24e1f95d70f028921e27040ca56e09ab0b',
|
||||
'expirationTimeSeconds': 1553553429,
|
||||
'feeRecipientAddress': '0x0000000000000000000000000000000000000000',
|
||||
'makerAddress': '0x5409ed021d9299bf6814279a6a1411a7e866a631',
|
||||
'makerAssetAmount': 1000000000000000000,
|
||||
'makerAssetData': '0xf47261b0000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
|
||||
'makerFee': 0,
|
||||
'salt': 123456789,
|
||||
'senderAddress': '0x0000000000000000000000000000000000000000',
|
||||
'takerAddress': '0x0000000000000000000000000000000000000000',
|
||||
'takerAssetAmount': 500000000000000000000,
|
||||
'takerAssetData': '0xf47261b0000000000000000000000000e41d2489571d322189246dafa5ebde1f4699f498',
|
||||
'takerFee': 0}
|
||||
""" # noqa E501
|
||||
|
||||
from copy import copy
|
||||
from enum import auto, Enum
|
||||
|
4
python-packages/sra_client/.pylintrc
Normal file
4
python-packages/sra_client/.pylintrc
Normal file
@ -0,0 +1,4 @@
|
||||
[MESSAGES CONTROL]
|
||||
disable=C0330,line-too-long,fixme,too-few-public-methods,too-many-ancestors
|
||||
# C0330 is "bad hanging indent". we use indents per `black`.
|
||||
|
@ -1,11 +1,22 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1553491629,
|
||||
"version": "1.0.1",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Fix regex validation on numeric values"
|
||||
}
|
||||
]
|
||||
"note": "Fix regex validation on numeric values",
|
||||
"pr": 1731
|
||||
}
|
||||
],
|
||||
"timestamp": 1553491629
|
||||
},
|
||||
{
|
||||
"version": "1.0.1",
|
||||
"changes": [
|
||||
{
|
||||
"note": "Added new devdependencies, and linting commands to `setup.py`. Added sphinx docs to demonstrate how to use sra_client.",
|
||||
"pr": 1734
|
||||
}
|
||||
],
|
||||
"timestamp": 1553183790
|
||||
}
|
||||
]
|
||||
|
@ -9,11 +9,11 @@ import pkg_resources
|
||||
# pylint: disable=invalid-name
|
||||
# because these variables are not named in upper case, as globals should be.
|
||||
|
||||
project = "0x-contract-demo"
|
||||
project = "0x-sra-client"
|
||||
# pylint: disable=redefined-builtin
|
||||
copyright = "2018, ZeroEx, Intl."
|
||||
author = "F. Eugene Aumson"
|
||||
version = pkg_resources.get_distribution("0x-contract-demo").version
|
||||
version = pkg_resources.get_distribution("0x-sra-client").version
|
||||
release = "" # The full version, including alpha/beta/rc tags
|
||||
|
||||
extensions = [
|
||||
@ -22,6 +22,7 @@ extensions = [
|
||||
"sphinx.ext.intersphinx",
|
||||
"sphinx.ext.coverage",
|
||||
"sphinx.ext.viewcode",
|
||||
"sphinx_autodoc_typehints",
|
||||
]
|
||||
|
||||
templates_path = ["doc_templates"]
|
||||
@ -46,7 +47,7 @@ html_static_path = ["doc_static"]
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = "contract_demopydoc"
|
||||
htmlhelp_basename = "sraclientpydoc"
|
||||
|
||||
# -- Extension configuration:
|
||||
|
@ -1,13 +1,20 @@
|
||||
.. source for the sphinx-generated build/docs/web/index.html
|
||||
|
||||
Python demo of 0x Smart Contracts
|
||||
=================================
|
||||
Python zero_ex.sra_client.api_client
|
||||
====================================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
:caption: Contents:
|
||||
|
||||
.. automodule:: test.test_exchange
|
||||
.. automodule:: sra_client
|
||||
|
||||
----
|
||||
|
||||
API
|
||||
---
|
||||
|
||||
.. automodule:: sra_client.api.default_api
|
||||
:members:
|
||||
|
||||
Indices and tables
|
@ -1,14 +1,16 @@
|
||||
#!/usr/bin/env python
|
||||
# coding: utf-8
|
||||
|
||||
"""setuptools module for sra_client package."""
|
||||
|
||||
import subprocess
|
||||
import subprocess # nosec
|
||||
import distutils.command.build_py
|
||||
|
||||
from setuptools import setup, find_packages # noqa: H301
|
||||
from setuptools.command.test import test as TestCommand
|
||||
|
||||
NAME = "0x-sra-client"
|
||||
VERSION = "1.0.0"
|
||||
VERSION = "1.0.1"
|
||||
# To install the library, run the following
|
||||
#
|
||||
# python setup.py install
|
||||
@ -21,6 +23,17 @@ with open("README.md", "r") as file_handle:
|
||||
|
||||
REQUIRES = ["urllib3 >= 1.15", "six >= 1.10", "certifi", "python-dateutil"]
|
||||
|
||||
|
||||
class TestCommandExtension(TestCommand):
|
||||
"""Run pytest tests."""
|
||||
|
||||
def run_tests(self):
|
||||
"""Invoke pytest."""
|
||||
import pytest
|
||||
|
||||
exit(pytest.main(["--doctest-modules"]))
|
||||
|
||||
|
||||
class TestPublishCommand(distutils.command.build_py.build_py):
|
||||
"""Custom command to publish to test.pypi.org."""
|
||||
|
||||
@ -38,6 +51,59 @@ class TestPublishCommand(distutils.command.build_py.build_py):
|
||||
)
|
||||
|
||||
|
||||
class GanacheCommand(distutils.command.build_py.build_py):
|
||||
"""Custom command to publish to pypi.org."""
|
||||
|
||||
description = "Run ganache daemon to support tests."
|
||||
|
||||
def run(self):
|
||||
"""Run ganache."""
|
||||
cmd_line = (
|
||||
"docker run -d -p 8545:8545 0xorg/ganache-cli:2.2.2"
|
||||
).split()
|
||||
subprocess.call(cmd_line) # nosec
|
||||
|
||||
|
||||
class LaunchKitCommand(distutils.command.build_py.build_py):
|
||||
"""Custom command to boot up a local 0x-launch-kit in docker."""
|
||||
|
||||
description = "Run launch-kit daemon to support sra_client demos."
|
||||
|
||||
def run(self):
|
||||
"""Run 0x-launch-kit."""
|
||||
cmd_line = ("docker run -d -p 3000:3000 0xorg/launch-kit-ci").split()
|
||||
subprocess.call(cmd_line) # nosec
|
||||
|
||||
|
||||
class LintCommand(distutils.command.build_py.build_py):
|
||||
"""Custom setuptools command class for running linters."""
|
||||
|
||||
description = "Run linters"
|
||||
|
||||
def run(self):
|
||||
"""Run linter shell commands."""
|
||||
lint_commands = [
|
||||
# formatter:
|
||||
"black --line-length 79 --check --diff test sra_client/__init__.py setup.py".split(), # noqa: E501 (line too long)
|
||||
# style guide checker (formerly pep8):
|
||||
"pycodestyle test sra_client/__init__.py setup.py".split(),
|
||||
# docstring style checker:
|
||||
"pydocstyle src test sra_client/__init__.py setup.py".split(),
|
||||
# static type checker:
|
||||
"bandit -r test sra_client/__init__.py setup.py".split(),
|
||||
# general linter:
|
||||
"pylint test sra_client/__init__.py setup.py".split(),
|
||||
# pylint takes relatively long to run, so it runs last, to enable
|
||||
# fast failures.
|
||||
]
|
||||
|
||||
for lint_command in lint_commands:
|
||||
print(
|
||||
"Running lint command `", " ".join(lint_command).strip(), "`"
|
||||
)
|
||||
subprocess.check_call(lint_command) # nosec
|
||||
|
||||
|
||||
class PublishCommand(distutils.command.build_py.build_py):
|
||||
"""Custom command to publish to pypi.org."""
|
||||
|
||||
@ -48,13 +114,17 @@ class PublishCommand(distutils.command.build_py.build_py):
|
||||
subprocess.check_call("twine upload dist/*".split()) # nosec
|
||||
|
||||
|
||||
class LintCommand(distutils.command.build_py.build_py):
|
||||
"""No-op lint command to support top-level lint script."""
|
||||
class PublishDocsCommand(distutils.command.build_py.build_py):
|
||||
"""Custom command to publish docs to S3."""
|
||||
|
||||
description = "No-op"
|
||||
description = (
|
||||
"Publish docs to "
|
||||
+ "http://0x-sra-demos-py.s3-website-us-east-1.amazonaws.com/"
|
||||
)
|
||||
|
||||
def run(self):
|
||||
pass
|
||||
"""Run npm package `discharge` to build & upload docs."""
|
||||
subprocess.check_call("discharge deploy".split()) # nosec
|
||||
|
||||
|
||||
setup(
|
||||
@ -72,6 +142,33 @@ setup(
|
||||
cmdclass={
|
||||
"test_publish": TestPublishCommand,
|
||||
"publish": PublishCommand,
|
||||
"launch_kit": LaunchKitCommand,
|
||||
"lint": LintCommand,
|
||||
"publish_docs": PublishDocsCommand,
|
||||
"test": TestCommandExtension,
|
||||
"ganache": GanacheCommand,
|
||||
},
|
||||
extras_require={
|
||||
"dev": [
|
||||
"0x-contract-addresses",
|
||||
"0x-order-utils",
|
||||
"0x-web3",
|
||||
"bandit",
|
||||
"black",
|
||||
"coverage",
|
||||
"coveralls",
|
||||
"pycodestyle",
|
||||
"pydocstyle",
|
||||
"pylint",
|
||||
"pytest",
|
||||
"sphinx",
|
||||
"sphinx-autodoc-typehints",
|
||||
]
|
||||
},
|
||||
command_options={
|
||||
"build_sphinx": {
|
||||
"source_dir": ("setup.py", "."),
|
||||
"build_dir": ("setup.py", "build/docs"),
|
||||
}
|
||||
},
|
||||
)
|
||||
|
@ -2,6 +2,167 @@
|
||||
|
||||
# flake8: noqa
|
||||
|
||||
"""Python api client to interact with SRA compatible 0x relayers.
|
||||
|
||||
0x Protocol is an open standard. Many relayers opt-in to implementing a set of
|
||||
`standard relayer API endpoints <http://sra-spec.s3-website-us-east-1.amazonaws.com/>`_
|
||||
to make it easier for anyone to source liquidity that conforms to the 0x order format.
|
||||
Here, we will show you how you can use our `sra_client
|
||||
<https://github.com/0xProject/0x-monorepo/tree/development/python-packages/sra_client#0x-sra-client>`_
|
||||
module to interact with 0x relayers that implements the Standard Relayer API.
|
||||
|
||||
Setup
|
||||
=====
|
||||
Install the sra-client package with pip:
|
||||
|
||||
`pip install 0x-sra-client`:code:
|
||||
|
||||
To interact with a 0x Relayer, you need the HTTP endpoint of the Relayer you'd like to
|
||||
connect to (i.e. https://api.radarrelay.com/0x/v2).
|
||||
|
||||
For local testing one can use the `0x-launch-kit
|
||||
<https://github.com/0xProject/0x-launch-kit#table-of-contents/>`_
|
||||
to host orders locally. For convenience, a docker container is provided
|
||||
for just this purpose. To start it:
|
||||
|
||||
`docker run -d -p 3000:3000 0xorg/launch-kit-ci`:code:
|
||||
|
||||
and then connect to the http server running at http://localhost:3000.
|
||||
|
||||
----
|
||||
|
||||
Configure and create an API client instance
|
||||
--------------------------------------------
|
||||
|
||||
>>> from sra_client import ApiClient, Configuration
|
||||
>>> from sra_client.api import DefaultApi
|
||||
>>> config = Configuration()
|
||||
>>> config.host = "http://localhost:3000"
|
||||
>>> relayer_api = DefaultApi(ApiClient(config))
|
||||
|
||||
Post Order
|
||||
-----------
|
||||
Post an order to an SRA-compliant Relayer.
|
||||
|
||||
>>> from web3 import HTTPProvider, Web3
|
||||
>>> from zero_ex.contract_addresses import (
|
||||
... NETWORK_TO_ADDRESSES, NetworkId)
|
||||
>>> from zero_ex.order_utils import (
|
||||
... asset_data_utils,
|
||||
... generate_order_hash_hex,
|
||||
... jsdict_order_to_struct,
|
||||
... sign_hash)
|
||||
>>> provider = HTTPProvider("http://localhost:8545")
|
||||
>>> maker_address = "0x5409ed021d9299bf6814279a6a1411a7e866a631"
|
||||
>>> exchange_address = NETWORK_TO_ADDRESSES[NetworkId.KOVAN].exchange
|
||||
>>> weth_address = NETWORK_TO_ADDRESSES[NetworkId.KOVAN].ether_token
|
||||
>>> zrx_address = NETWORK_TO_ADDRESSES[NetworkId.KOVAN].zrx_token
|
||||
>>> weth_asset_data = asset_data_utils.encode_erc20_asset_data(weth_address)
|
||||
>>> zrx_asset_data = asset_data_utils.encode_erc20_asset_data(zrx_address)
|
||||
>>> example_order = {
|
||||
... "makerAddress": maker_address,
|
||||
... "takerAddress": "0x0000000000000000000000000000000000000000",
|
||||
... "senderAddress": "0x0000000000000000000000000000000000000000",
|
||||
... "exchangeAddress": exchange_address,
|
||||
... "feeRecipientAddress":
|
||||
... "0x0000000000000000000000000000000000000000",
|
||||
... "makerAssetData": weth_asset_data,
|
||||
... "takerAssetData": zrx_asset_data,
|
||||
... "salt": "2362734632784682376287462",
|
||||
... "makerFee": "0",
|
||||
... "takerFee": "0",
|
||||
... "makerAssetAmount": "1000000000000000000",
|
||||
... "takerAssetAmount": "500000000000000000000",
|
||||
... "expirationTimeSeconds": "999999999999999999999"}
|
||||
>>> order_hash = generate_order_hash_hex(
|
||||
... jsdict_order_to_struct(example_order), exchange_address)
|
||||
>>> example_order["signature"] = sign_hash(
|
||||
... provider, Web3.toChecksumAddress(maker_address), order_hash)
|
||||
>>> relayer_api.post_order_with_http_info(
|
||||
... network_id=42, signed_order_schema=example_order)[1]
|
||||
200
|
||||
|
||||
Get Orders
|
||||
-----------
|
||||
Get orders from an SRA-compliant Relayer.
|
||||
|
||||
>>> relayer_api.get_orders()
|
||||
{'records': [{'meta_data': {},
|
||||
'order': {'exchange_address': '0x35dd2932454449b14cee11a94d3674a936d5d7b2',
|
||||
'expiration_time_seconds': '1000000000000000000000',
|
||||
'fee_recipient_address': '0x0000000000000000000000000000000000000000',
|
||||
'maker_address': '0x5409ed021d9299bf6814279a6a1411a7e866a631',
|
||||
'maker_asset_amount': '1000000000000000000',
|
||||
'maker_asset_data': '0xf47261b0000000000000000000000000d0a1e359811322d97991e03f863a0c30c2cf029c',
|
||||
'maker_fee': '0',
|
||||
'salt': '2362734632784682376287462',
|
||||
'sender_address': '0x0000000000000000000000000000000000000000',
|
||||
'taker_address': '0x0000000000000000000000000000000000000000',
|
||||
'taker_asset_amount': '500000000000000000000',
|
||||
'taker_asset_data': '0xf47261b00000000000000000000000002002d3812f58e35f0ea1ffbf80a75a38c32175fa',
|
||||
'taker_fee': '0'}}]}
|
||||
|
||||
Get Order
|
||||
---------
|
||||
Get an order by hash from an SRA-compliant Relayer.
|
||||
|
||||
>>> relayer_api.get_order(order_hash) # doctest: +SKIP
|
||||
{'meta_data': {},
|
||||
'order': {'exchange_address': '0x35dd2932454449b14cee11a94d3674a936d5d7b2',
|
||||
'expiration_time_seconds': '1000000000000000000000',
|
||||
'fee_recipient_address': '0x0000000000000000000000000000000000000000',
|
||||
'maker_address': '0x5409ed021d9299bf6814279a6a1411a7e866a631',
|
||||
'maker_asset_amount': '1000000000000000000',
|
||||
'maker_asset_data': '0xf47261b0000000000000000000000000d0a1e359811322d97991e03f863a0c30c2cf029c',
|
||||
'maker_fee': '0',
|
||||
'salt': '2362734632784682376287462',
|
||||
'sender_address': '0x0000000000000000000000000000000000000000',
|
||||
'taker_address': '0x0000000000000000000000000000000000000000',
|
||||
'taker_asset_amount': '500000000000000000000',
|
||||
'taker_asset_data': '0xf47261b00000000000000000000000002002d3812f58e35f0ea1ffbf80a75a38c32175fa',
|
||||
'taker_fee': '0'}},
|
||||
|
||||
Get Asset Pair
|
||||
---------------
|
||||
Get available asset pairs from an SRA-compliant Relayer.
|
||||
|
||||
>>> relayer_api.get_asset_pairs()
|
||||
{'records': [{'assetDataA': {'assetData': '0xf47261b0000000000000000000000000d0a1e359811322d97991e03f863a0c30c2cf029c',
|
||||
'maxAmount': '115792089237316195423570985008687907853269984665640564039457584007913129639936',
|
||||
'minAmount': '0',
|
||||
'precision': 18},
|
||||
'assetDataB': {'assetData': '0xf47261b00000000000000000000000002002d3812f58e35f0ea1ffbf80a75a38c32175fa',
|
||||
'maxAmount': '115792089237316195423570985008687907853269984665640564039457584007913129639936',
|
||||
'minAmount': '0',
|
||||
'precision': 18}}]}
|
||||
|
||||
Get Orderbook
|
||||
-------------
|
||||
Get the orderbook for the WETH/ZRX asset pair from an SRA-compliant Relayer.
|
||||
|
||||
>>> relayer_api.get_orderbook(
|
||||
... base_asset_data=weth_asset_data,
|
||||
... quote_asset_data=zrx_asset_data)
|
||||
{'asks': {'records': [{'meta_data': {},
|
||||
'order': {'exchange_address': '0x35dd2932454449b14cee11a94d3674a936d5d7b2',
|
||||
'expiration_time_seconds': '1000000000000000000000',
|
||||
'fee_recipient_address': '0x0000000000000000000000000000000000000000',
|
||||
'maker_address': '0x5409ed021d9299bf6814279a6a1411a7e866a631',
|
||||
'maker_asset_amount': '1000000000000000000',
|
||||
'maker_asset_data': '0xf47261b0000000000000000000000000d0a1e359811322d97991e03f863a0c30c2cf029c',
|
||||
'maker_fee': '0',
|
||||
'salt': '2362734632784682376287462',
|
||||
'sender_address': '0x0000000000000000000000000000000000000000',
|
||||
'taker_address': '0x0000000000000000000000000000000000000000',
|
||||
'taker_asset_amount': '500000000000000000000',
|
||||
'taker_asset_data': '0xf47261b00000000000000000000000002002d3812f58e35f0ea1ffbf80a75a38c32175fa',
|
||||
'taker_fee': '0'}}]},
|
||||
'bids': {'records': []}}
|
||||
""" # noqa: E501 (line too long)
|
||||
|
||||
# NOTE: Bug in get_order method.
|
||||
# Sra_client not deserialzing order from server properly, need fix!
|
||||
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
@ -28,7 +189,7 @@ from sra_client.models.relayer_api_asset_data_trade_info_schema import (
|
||||
from sra_client.models.relayer_api_error_response_schema import (
|
||||
RelayerApiErrorResponseSchema,
|
||||
)
|
||||
from sra_client.models.relayer_api_error_response_schema_validation_errors import (
|
||||
from sra_client.models.relayer_api_error_response_schema_validation_errors import ( # noqa: E501 (line too long)
|
||||
RelayerApiErrorResponseSchemaValidationErrors,
|
||||
)
|
||||
from sra_client.models.relayer_api_fee_recipients_response_schema import (
|
||||
@ -44,7 +205,7 @@ from sra_client.models.relayer_api_order_schema import RelayerApiOrderSchema
|
||||
from sra_client.models.relayer_api_orderbook_response_schema import (
|
||||
RelayerApiOrderbookResponseSchema,
|
||||
)
|
||||
from sra_client.models.relayer_api_orders_channel_subscribe_payload_schema import (
|
||||
from sra_client.models.relayer_api_orders_channel_subscribe_payload_schema import ( # noqa: E501 (line too long)
|
||||
RelayerApiOrdersChannelSubscribePayloadSchema,
|
||||
)
|
||||
from sra_client.models.relayer_api_orders_channel_subscribe_schema import (
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -207,8 +207,7 @@ class ApiClient(object):
|
||||
|
||||
If obj is None, return None.
|
||||
If obj is str, int, long, float, bool, return directly.
|
||||
If obj is datetime.datetime, datetime.date
|
||||
convert to string in iso8601 format.
|
||||
If obj is datetime.datetime, datetime.date convert to string in iso8601 format.
|
||||
If obj is list, sanitize each element in the list.
|
||||
If obj is dict, return the dict.
|
||||
If obj is OpenAPI model, return the properties dict.
|
||||
|
@ -0,0 +1 @@
|
||||
"""Test for sra_client."""
|
@ -1,3 +1,4 @@
|
||||
"""Test the default api client"""
|
||||
# coding: utf-8
|
||||
|
||||
|
||||
@ -5,30 +6,92 @@ from __future__ import absolute_import
|
||||
|
||||
import unittest
|
||||
|
||||
import sra_client
|
||||
from sra_client.api.default_api import DefaultApi # noqa: E501
|
||||
from sra_client.models.relayer_api_asset_data_pairs_response_schema import (
|
||||
RelayerApiAssetDataPairsResponseSchema
|
||||
)
|
||||
from sra_client.rest import ApiException
|
||||
from sra_client import ApiClient, Configuration
|
||||
from sra_client.api import DefaultApi
|
||||
|
||||
|
||||
class TestDefaultApi(unittest.TestCase):
|
||||
"""DefaultApi unit test stubs"""
|
||||
|
||||
def setUp(self):
|
||||
self.api = sra_client.api.default_api.DefaultApi() # noqa: E501
|
||||
config = Configuration()
|
||||
config.host = "http://localhost:3000"
|
||||
self.api = DefaultApi(ApiClient(config))
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
# pylint: disable=too-many-locals
|
||||
def test_get_asset_pairs(self):
|
||||
"""Test case for get_asset_pairs
|
||||
|
||||
"""
|
||||
expected = RelayerApiAssetDataPairsResponseSchema([])
|
||||
expected = {
|
||||
"records": [
|
||||
{
|
||||
"assetDataA": {
|
||||
"assetData": "0xf47261b0000000000000000000000000"
|
||||
"d0a1e359811322d97991e03f863a0c30c2cf029c",
|
||||
"maxAmount": "115792089237316195423570985008687907853"
|
||||
"269984665640564039457584007913129639936",
|
||||
"minAmount": "0",
|
||||
"precision": 18,
|
||||
},
|
||||
"assetDataB": {
|
||||
"assetData": "0xf47261b0000000000000000000000000"
|
||||
"2002d3812f58e35f0ea1ffbf80a75a38c32175fa",
|
||||
"maxAmount": "115792089237316195423570985008687907853"
|
||||
"269984665640564039457584007913129639936",
|
||||
"minAmount": "0",
|
||||
"precision": 18,
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
actual = self.api.get_asset_pairs()
|
||||
self.assertEqual(actual, expected)
|
||||
|
||||
acutal_asset_data_a = actual.records[0]["assetDataA"]["assetData"]
|
||||
expected_asset_data_a = expected["records"][0]["assetDataA"][
|
||||
"assetData"
|
||||
]
|
||||
self.assertEqual(acutal_asset_data_a, expected_asset_data_a)
|
||||
acutal_max_amount_a = actual.records[0]["assetDataA"]["maxAmount"]
|
||||
expected_max_amount_a = expected["records"][0]["assetDataA"][
|
||||
"maxAmount"
|
||||
]
|
||||
self.assertEqual(acutal_max_amount_a, expected_max_amount_a)
|
||||
acutal_min_amount_a = actual.records[0]["assetDataA"]["minAmount"]
|
||||
expected_min_amount_a = expected["records"][0]["assetDataA"][
|
||||
"minAmount"
|
||||
]
|
||||
self.assertEqual(acutal_min_amount_a, expected_min_amount_a)
|
||||
acutal_precision_a = actual.records[0]["assetDataA"]["precision"]
|
||||
expected_precision_a = expected["records"][0]["assetDataA"][
|
||||
"precision"
|
||||
]
|
||||
self.assertEqual(acutal_precision_a, expected_precision_a)
|
||||
|
||||
acutal_asset_data_b = actual.records[0]["assetDataB"]["assetData"]
|
||||
expected_asset_data_b = expected["records"][0]["assetDataB"][
|
||||
"assetData"
|
||||
]
|
||||
self.assertEqual(acutal_asset_data_b, expected_asset_data_b)
|
||||
acutal_max_amount_b = actual.records[0]["assetDataB"]["maxAmount"]
|
||||
expected_max_amount_b = expected["records"][0]["assetDataB"][
|
||||
"maxAmount"
|
||||
]
|
||||
self.assertEqual(acutal_max_amount_b, expected_max_amount_b)
|
||||
acutal_min_amount_b = actual.records[0]["assetDataB"]["minAmount"]
|
||||
expected_min_amount_b = expected["records"][0]["assetDataB"][
|
||||
"minAmount"
|
||||
]
|
||||
self.assertEqual(acutal_min_amount_b, expected_min_amount_b)
|
||||
acutal_precision_b = actual.records[0]["assetDataB"]["precision"]
|
||||
expected_precision_b = expected["records"][0]["assetDataB"][
|
||||
"precision"
|
||||
]
|
||||
self.assertEqual(acutal_precision_b, expected_precision_b)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
Loading…
x
Reference in New Issue
Block a user