├── .flake8 ├── .github └── workflows │ ├── ci-cd.yml │ └── publish-docs.yml ├── .gitignore ├── .pre-commit-config.yaml ├── README.md ├── docs ├── Makefile ├── make.bat └── source │ ├── api-reference.rst │ ├── conf.py │ ├── getting-started.rst │ ├── index.rst │ ├── user-guides.rst │ ├── user-reference.rst │ └── vertex.ico ├── poetry.lock ├── pyproject.toml ├── sanity ├── __init__.py ├── contracts.py ├── engine_client.py ├── indexer_client.py ├── isolated.py ├── rewards.py ├── signing.py ├── trigger_client.py └── vertex_client.py ├── tests ├── __init__.py ├── conftest.py ├── contracts │ ├── test_eip712_signing.py │ └── test_vertex_contracts.py ├── engine_client │ ├── __init__.py │ ├── test_burn_lp.py │ ├── test_cancel_and_place.py │ ├── test_cancel_orders.py │ ├── test_cancel_product_orders.py │ ├── test_create_client.py │ ├── test_execute_client.py │ ├── test_expiration.py │ ├── test_link_signer.py │ ├── test_liquidate_subaccount.py │ ├── test_mint_lp.py │ ├── test_nonce.py │ ├── test_place_isolated_order.py │ ├── test_place_order.py │ └── test_withdraw_collateral.py ├── indexer_client │ ├── __init__.py │ └── test_query_params.py ├── trigger_client │ ├── __init__.py │ ├── test_cancel_trigger_orders.py │ ├── test_cancel_trigger_product_orders.py │ ├── test_create_client.py │ ├── test_execute_client.py │ └── test_place_trigger_order.py ├── utils │ ├── __init__.py │ └── test_math.py └── vertex_client │ ├── __init__.py │ ├── test_create_vertex_client.py │ ├── test_market_api.py │ ├── test_spot_api.py │ └── test_subaccount_api.py └── vertex_protocol ├── __init__.py ├── client ├── __init__.py ├── apis │ ├── __init__.py │ ├── base.py │ ├── market │ │ ├── __init__.py │ │ ├── execute.py │ │ └── query.py │ ├── perp │ │ ├── __init__.py │ │ └── query.py │ ├── rewards │ │ ├── __init__.py │ │ ├── execute.py │ │ └── query.py │ ├── spot │ │ ├── __init__.py │ │ ├── base.py │ │ ├── execute.py │ │ └── query.py │ └── subaccount │ │ ├── __init__.py │ │ ├── execute.py │ │ └── query.py └── context.py ├── contracts ├── __init__.py ├── abis │ ├── Endpoint.json │ ├── FQuerier.json │ ├── IClearinghouse.json │ ├── IERC20.json │ ├── IEndpoint.json │ ├── IFoundationRewardsAirdrop.json │ ├── IOffchainBook.json │ ├── IPerpEngine.json │ ├── IProductEngine.json │ ├── ISpotEngine.json │ ├── IStaking.json │ ├── IVrtxAirdrop.json │ └── MockERC20.json ├── deployments │ ├── deployment.abstractMainnet.json │ ├── deployment.abstractTestnet.json │ ├── deployment.arbitrumOne.json │ ├── deployment.arbitrumSepolia.json │ ├── deployment.avaxMainnet.json │ ├── deployment.avaxTestnet.json │ ├── deployment.baseMainnet.json │ ├── deployment.baseTestnet.json │ ├── deployment.beraMainnet.json │ ├── deployment.blastMainnet.json │ ├── deployment.blastTestnet.json │ ├── deployment.mantleMainnet.json │ ├── deployment.mantleTestnet.json │ ├── deployment.seiMainnet.json │ ├── deployment.seiTestnet.json │ ├── deployment.sonicMainnet.json │ ├── deployment.sonicTestnet.json │ ├── deployment.test.json │ └── deployment.xrplTestnet.json ├── eip712 │ ├── __init__.py │ ├── domain.py │ ├── sign.py │ └── types.py ├── loader.py └── types.py ├── engine_client ├── __init__.py ├── execute.py ├── query.py └── types │ ├── __init__.py │ ├── execute.py │ ├── models.py │ ├── query.py │ └── stream.py ├── indexer_client ├── __init__.py ├── query.py └── types │ ├── __init__.py │ ├── models.py │ └── query.py ├── trigger_client ├── __init__.py ├── execute.py ├── query.py └── types │ ├── __init__.py │ ├── execute.py │ ├── models.py │ └── query.py └── utils ├── __init__.py ├── backend.py ├── bytes32.py ├── enum.py ├── exceptions.py ├── execute.py ├── expiration.py ├── interest.py ├── math.py ├── model.py ├── nonce.py ├── subaccount.py └── time.py /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | ignore = E203, E266, E501, W503, F403, F401, F405 3 | max-line-length = 79 4 | max-complexity = 18 5 | select = B,C,E,F,W,T4,B9 6 | exclude = docs/source/conf.py -------------------------------------------------------------------------------- /.github/workflows/ci-cd.yml: -------------------------------------------------------------------------------- 1 | name: CI/CD 2 | 3 | on: 4 | push: 5 | release: 6 | types: [created] 7 | 8 | jobs: 9 | test: 10 | runs-on: ${{ matrix.os }} 11 | strategy: 12 | matrix: 13 | os: [ubuntu-latest, windows-latest] 14 | python-version: ["3.9", "3.10", "3.11"] 15 | 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: actions/setup-python@v4 19 | with: 20 | python-version: ${{ matrix.python-version }} 21 | - name: Set up cache 22 | uses: actions/cache@v4 23 | with: 24 | path: .venv 25 | key: cache-python-packages 26 | - name: Set up poetry 27 | run: | 28 | pip install poetry safety 29 | poetry config virtualenvs.in-project true 30 | - name: Install dependencies # install all dependencies 31 | run: poetry install 32 | - name: Pytest 33 | run: | 34 | poetry run coverage run -m --source=vertex_protocol pytest tests 35 | poetry run coverage report 36 | - name: MyPy 37 | run: | 38 | poetry run mypy vertex_protocol 39 | 40 | publish: 41 | # Our publish job will only run on release creation events, 42 | # and only if the test job has passed 43 | if: github.event_name == 'release' && github.event.action == 'created' 44 | needs: test 45 | runs-on: ubuntu-latest 46 | steps: 47 | - name: Checkout 48 | uses: actions/checkout@v4 49 | - uses: actions/setup-python@v4 50 | with: 51 | python-version: 3.11 52 | - name: Set up cache 53 | uses: actions/cache@v4 54 | with: 55 | path: .venv 56 | key: cache-python-packages 57 | - name: Set up poetry 58 | run: | 59 | pip install poetry safety 60 | poetry config virtualenvs.in-project true 61 | - name: Install dependencies # install all dependencies 62 | run: poetry install 63 | - name: Build and publish 64 | env: 65 | PYPI_USERNAME: ${{ secrets.PYPI_USERNAME }} 66 | PYPI_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 67 | run: poetry publish -u $PYPI_USERNAME -p $PYPI_PASSWORD --build 68 | -------------------------------------------------------------------------------- /.github/workflows/publish-docs.yml: -------------------------------------------------------------------------------- 1 | name: Publish docs to GH pages 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | workflow_dispatch: 8 | 9 | permissions: 10 | contents: read 11 | pages: write 12 | id-token: write 13 | 14 | # Allow one concurrent deployment 15 | concurrency: 16 | group: "pages" 17 | cancel-in-progress: true 18 | 19 | jobs: 20 | deploy: 21 | environment: 22 | name: github-pages 23 | url: ${{ steps.deployment.outputs.page_url }} 24 | runs-on: ubuntu-latest 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v4 28 | - uses: actions/setup-python@v4 29 | with: 30 | python-version: 3.11 31 | - name: Set up cache 32 | uses: actions/cache@v4 33 | with: 34 | path: .venv 35 | key: cache-python-packages 36 | - name: Set up poetry 37 | run: | 38 | pip install poetry safety 39 | poetry config virtualenvs.in-project true 40 | - name: Install dependencies # install all dependencies 41 | run: poetry install 42 | - name: Build docs 43 | run : | 44 | poetry run sphinx-build docs/source docs/build 45 | - name: Setup Pages 46 | uses: actions/configure-pages@v4 47 | - name: Upload artifact 48 | uses: actions/upload-pages-artifact@v3 49 | with: 50 | # Upload entire repository 51 | path: './docs/build' 52 | - name: Deploy to GitHub Pages 53 | id: deployment 54 | uses: actions/deploy-pages@v4 55 | -------------------------------------------------------------------------------- /.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 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | venv3.9/ 128 | venv3.10/ 129 | venv3.11/ 130 | ENV/ 131 | env.bak/ 132 | venv.bak/ 133 | 134 | # Spyder project settings 135 | .spyderproject 136 | .spyproject 137 | 138 | # Rope project settings 139 | .ropeproject 140 | 141 | # mkdocs documentation 142 | /site 143 | 144 | # mypy 145 | .mypy_cache/ 146 | .dmypy.json 147 | dmypy.json 148 | 149 | # Pyre type checker 150 | .pyre/ 151 | 152 | # pytype static type analyzer 153 | .pytype/ 154 | 155 | # Cython debug symbols 156 | cython_debug/ 157 | 158 | # Jetbrains 159 | .idea/ 160 | 161 | .vscode 162 | 163 | .ruff_cache 164 | 165 | **/deployment.localhost.json 166 | **/deployment.local.json 167 | 168 | .DS_Store 169 | 170 | docs/.DS_Store -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/ambv/black 3 | rev: 23.3.0 4 | hooks: 5 | - id: black 6 | - repo: https://github.com/pycqa/flake8.git 7 | rev: 3.9.2 8 | hooks: 9 | - id: flake8 10 | - repo: https://github.com/pre-commit/mirrors-mypy 11 | rev: v1.3.0 12 | hooks: 13 | - id: mypy 14 | args: [--ignore-missing-imports] 15 | exclude: ^(tests/|docs/|sanity/) 16 | additional_dependencies: ['types-requests'] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vertex Protocol Python SDK 2 | 3 | This is the Python SDK for the [Vertex Protocol API](https://vertex-protocol.gitbook.io/docs/developer-resources/api). 4 | 5 | See [SDK docs](https://vertex-protocol.github.io/vertex-python-sdk/index.html) to get started. 6 | 7 | ## Requirements 8 | 9 | - Python 3.9 or above 10 | 11 | ## Installation 12 | 13 | You can install the SDK via pip: 14 | 15 | ```bash 16 | pip install vertex-protocol 17 | ``` 18 | 19 | ## Basic usage 20 | 21 | ### Import the necessary utilities: 22 | 23 | ```python 24 | from vertex_protocol.client import create_vertex_client, VertexClientMode 25 | from vertex_protocol.contracts.types import DepositCollateralParams 26 | from vertex_protocol.engine_client.types.execute import ( 27 | OrderParams, 28 | PlaceOrderParams, 29 | SubaccountParams 30 | ) 31 | from vertex_protocol.utils.expiration import OrderType, get_expiration_timestamp 32 | from vertex_protocol.utils.math import to_pow_10, to_x18 33 | from vertex_protocol.utils.nonce import gen_order_nonce 34 | ``` 35 | 36 | ### Create the VertexClient providing your private key: 37 | 38 | ```python 39 | print("setting up vertex client...") 40 | private_key = "xxx" 41 | client = create_vertex_client(VertexClientMode.MAINNET, private_key) 42 | ``` 43 | 44 | ### Perform basic operations: 45 | 46 | ```python 47 | # Depositing collaterals 48 | print("approving allowance...") 49 | approve_allowance_tx_hash = client.spot.approve_allowance(0, to_pow_10(100000, 6)) 50 | print("approve allowance tx hash:", approve_allowance_tx_hash) 51 | 52 | print("querying my allowance...") 53 | token_allowance = client.spot.get_token_allowance(0, client.context.signer.address) 54 | print("token allowance:", token_allowance) 55 | 56 | print("depositing collateral...") 57 | deposit_tx_hash = client.spot.deposit( 58 | DepositCollateralParams( 59 | subaccount_name="default", product_id=0, amount=to_pow_10(100000, 6) 60 | ) 61 | ) 62 | print("deposit collateral tx hash:", deposit_tx_hash) 63 | 64 | # Placing orders 65 | print("placing order...") 66 | owner = client.context.engine_client.signer.address 67 | product_id = 1 68 | order = OrderParams( 69 | sender=SubaccountParams( 70 | subaccount_owner=owner, 71 | subaccount_name="default", 72 | ), 73 | priceX18=to_x18(20000), 74 | amount=to_pow_10(1, 17), 75 | expiration=get_expiration_timestamp(OrderType.POST_ONLY, int(time.time()) + 40), 76 | nonce=gen_order_nonce(), 77 | ) 78 | res = client.market.place_order({"product_id": product_id, "order": order}) 79 | print("order result:", res.json(indent=2)) 80 | ``` 81 | 82 | See [Getting Started](https://vertex-protocol.github.io/vertex-python-sdk/getting-started.html) for more. 83 | 84 | ## Running locally 85 | 86 | 1. Clone [github repo](https://github.com/vertex-protocol/vertex-python-sdk) 87 | 88 | 2. Install poetry 89 | 90 | ``` 91 | 92 | $ curl -sSL https://install.python-poetry.org | python3 - 93 | 94 | ``` 95 | 96 | 3. Setup a virtual environment and activate it 97 | 98 | ``` 99 | 100 | $ python3 -m venv venv 101 | $ source ./venv/bin/activate 102 | 103 | ``` 104 | 105 | 4. Install dependencies via `poetry install` 106 | 5. Setup an `.env` file and set the following envvars 107 | 108 | ```shell 109 | CLIENT_MODE='mainnet|sepolia-testnet|devnet' 110 | SIGNER_PRIVATE_KEY="0x..." 111 | LINKED_SIGNER_PRIVATE_KEY="0x..." # not required 112 | ``` 113 | 114 | ### Run tests 115 | 116 | ``` 117 | $ poetry run test 118 | ``` 119 | 120 | ### Run sanity checks 121 | 122 | - `poetry run client-sanity`: runs sanity checks for the top-level client. 123 | - `poetry run engine-sanity`: runs sanity checks for the `engine-client`. 124 | - `poetry run indexer-sanity`: runs sanity checks for the `indexer-client`. 125 | - `poetry run contracts-sanity`: runs sanity checks for the contracts module. 126 | 127 | ### Build Docs 128 | 129 | To build the docs locally run: 130 | 131 | ``` 132 | $ poetry run sphinx-build docs/source docs/build 133 | ``` 134 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = source 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) 21 | -------------------------------------------------------------------------------- /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=source 11 | set BUILDDIR=build 12 | 13 | %SPHINXBUILD% >NUL 2>NUL 14 | if errorlevel 9009 ( 15 | echo. 16 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 17 | echo.installed, then set the SPHINXBUILD environment variable to point 18 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 19 | echo.may add the Sphinx directory to PATH. 20 | echo. 21 | echo.If you don't have Sphinx installed, grab it from 22 | echo.https://www.sphinx-doc.org/ 23 | exit /b 1 24 | ) 25 | 26 | if "%1" == "" goto help 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /docs/source/api-reference.rst: -------------------------------------------------------------------------------- 1 | API Reference 2 | ============= 3 | 4 | Detailed API Reference for Vertex Protocol SDK. 5 | 6 | vertex_protocol.client 7 | ---------------------- 8 | .. automodule:: vertex_protocol.client 9 | :members: 10 | :undoc-members: 11 | :special-members: __init__ 12 | :show-inheritance: 13 | 14 | 15 | vertex_protocol.client.apis 16 | --------------------------- 17 | .. automodule:: vertex_protocol.client.apis 18 | :members: 19 | :undoc-members: 20 | :special-members: __init__ 21 | :show-inheritance: 22 | 23 | 24 | vertex-protocol.engine_client 25 | ----------------------------- 26 | .. automodule:: vertex_protocol.engine_client 27 | :members: 28 | :undoc-members: 29 | :special-members: __init__ 30 | :show-inheritance: 31 | 32 | 33 | vertex-protocol.engine_client.types 34 | ----------------------------------- 35 | .. automodule:: vertex_protocol.engine_client.types 36 | :members: 37 | :undoc-members: 38 | :special-members: __init__ 39 | :show-inheritance: 40 | 41 | 42 | vertex-protocol.indexer_client 43 | ------------------------------ 44 | .. automodule:: vertex_protocol.indexer_client 45 | :members: 46 | :undoc-members: 47 | :special-members: __init__ 48 | :show-inheritance: 49 | 50 | 51 | vertex-protocol.indexer_client.types 52 | ------------------------------------ 53 | .. automodule:: vertex_protocol.indexer_client.types 54 | :members: 55 | :undoc-members: 56 | :special-members: __init__ 57 | :show-inheritance: 58 | 59 | 60 | vertex-protocol.contracts 61 | ------------------------- 62 | .. automodule:: vertex_protocol.contracts 63 | :members: 64 | :undoc-members: 65 | :special-members: __init__ 66 | :show-inheritance: 67 | 68 | .. _eip-712: 69 | 70 | vertex-protocol.contracts.eip712 71 | -------------------------------- 72 | .. automodule:: vertex_protocol.contracts.eip712 73 | :members: 74 | :undoc-members: 75 | :special-members: __init__ 76 | :show-inheritance: 77 | 78 | 79 | vertex-protocol.utils 80 | --------------------- 81 | .. automodule:: vertex_protocol.utils 82 | :members: 83 | :undoc-members: 84 | :special-members: __init__ 85 | :show-inheritance: -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | # import os 14 | # import sys 15 | # sys.path.insert(0, os.path.abspath('.')) 16 | 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | project = "Vertex Protocol SDK" 21 | copyright = "2023, Vertex Protocol Team" 22 | author = "Vertex Protocol Team" 23 | 24 | # The full version, including alpha/beta/rc tags 25 | release = "0.1.0" 26 | 27 | 28 | # -- General configuration --------------------------------------------------- 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | import os 34 | import sys 35 | 36 | sys.path.insert(0, os.path.abspath("..")) 37 | 38 | extensions = [ 39 | "sphinx.ext.autodoc", 40 | "sphinx_autodoc_typehints", 41 | "sphinx.ext.coverage", 42 | "sphinx.ext.intersphinx", 43 | "sphinx.ext.viewcode", 44 | ] 45 | 46 | # Add any paths that contain templates here, relative to this directory. 47 | templates_path = ["_templates"] 48 | 49 | # List of patterns, relative to source directory, that match files and 50 | # directories to ignore when looking for source files. 51 | # This pattern also affects html_static_path and html_extra_path. 52 | exclude_patterns = [] 53 | 54 | # -- Options for HTML output ------------------------------------------------- 55 | 56 | # The theme to use for HTML and HTML Help pages. See the documentation for 57 | # a list of builtin themes. 58 | # 59 | import sphinx_rtd_theme 60 | 61 | html_theme = "sphinx_rtd_theme" 62 | 63 | autodoc_member_order = "bysource" 64 | 65 | autosummary_generate = True 66 | 67 | html_favicon = "vertex.ico" 68 | 69 | pygments_style = "sphinx" 70 | -------------------------------------------------------------------------------- /docs/source/getting-started.rst: -------------------------------------------------------------------------------- 1 | .. _getting-started: 2 | 3 | Getting started 4 | =============== 5 | 6 | Introduction 7 | ------------ 8 | 9 | This SDK offers methods to perform all operations on Vertex such as trading, managing your collaterals, etc. 10 | 11 | Basic usage 12 | ----------- 13 | 14 | Before you start, import the necessary utilities: 15 | 16 | .. code-block:: python 17 | 18 | import time 19 | from vertex_protocol.client import create_vertex_client 20 | from vertex_protocol.engine_client.types.execute import ( 21 | OrderParams, 22 | PlaceOrderParams, 23 | WithdrawCollateralParams, 24 | CancelOrdersParams 25 | ) 26 | from vertex_protocol.contracts.types import DepositCollateralParams 27 | from vertex_protocol.utils.bytes32 import subaccount_to_bytes32, subaccount_to_hex 28 | from vertex_protocol.utils.expiration import OrderType, get_expiration_timestamp 29 | from vertex_protocol.utils.math import to_pow_10, to_x18 30 | from vertex_protocol.utils.nonce import gen_order_nonce 31 | from vertex_protocol.utils.subaccount import SubaccountParams 32 | 33 | The following sections outline the main functionalities: 34 | 35 | Making a deposit 36 | ---------------- 37 | .. note:: 38 | 39 | Remember to always keep your signer's private key securely stored and never expose it to the public. 40 | 41 | The core client supports two modes: 42 | 43 | - `sepolia-testnet`: Connects to our test environment on Arbitrum Sepolia. 44 | - `mainnet`: Connects to our production environment on Arbitrum One. 45 | 46 | The primary entry point of the SDK is via `create_vertex_client`, which allows you to create an instance of `VertexClient`. 47 | See :doc:`api-reference` for more details. 48 | 49 | .. code-block:: python 50 | 51 | >>> private_key = "xxx" 52 | >>> print("setting up vertex client...") 53 | >>> client = create_vertex_client("sepolia-testnet", private_key) 54 | >>> # You must first approve allowance for the amount you want to deposit. 55 | >>> print("approving allowance...") 56 | >>> approve_allowance_tx_hash = client.spot.approve_allowance(0, to_pow_10(100000, 6)) 57 | >>> print("approve allowance tx hash:", approve_allowance_tx_hash) 58 | >>> # Now, you can make the actual deposit. 59 | >>> print("depositing collateral...") 60 | >>> deposit_tx_hash = client.spot.deposit( 61 | DepositCollateralParams( 62 | subaccount_name="default", product_id=0, amount=to_pow_10(100000, 6) 63 | ) 64 | ) 65 | >>> print("deposit collateral tx hash:", deposit_tx_hash) 66 | 67 | Placing an order 68 | ---------------- 69 | 70 | Places an order via `execute:place_order `_. 71 | 72 | .. code-block:: python 73 | 74 | >>> owner = client.context.engine_client.signer.address 75 | >>> print("placing order...") 76 | >>> product_id = 1 77 | >>> order = OrderParams( 78 | sender=SubaccountParams( 79 | subaccount_owner=owner, 80 | subaccount_name="default", 81 | ), 82 | priceX18=to_x18(20000), 83 | amount=to_pow_10(1, 17), 84 | expiration=get_expiration_timestamp(OrderType.POST_ONLY, int(time.time()) + 40), 85 | nonce=gen_order_nonce(), 86 | ) 87 | >>> res = client.market.place_order(PlaceOrderParams(product_id=1, order=order)) 88 | >>> print("order result:", res.json(indent=2)) 89 | 90 | Viewing open orders 91 | ------------------- 92 | 93 | Queries your open orders via `query:subaccount_orders `_. 94 | 95 | .. code-block:: python 96 | 97 | >>> sender = subaccount_to_hex(order.sender) 98 | >>> print("querying open orders...") 99 | >>> open_orders = client.market.get_subaccount_open_orders(1, sender) 100 | >>> print("open orders:", open_orders.json(indent=2)) 101 | 102 | Retrieving an order digest 103 | -------------------------- 104 | 105 | .. note:: 106 | 107 | The order digest is necessary to perform order cancellation via `client.market.cancel_orders` 108 | 109 | .. code-block:: python 110 | 111 | >>> order.sender = subaccount_to_bytes32(order.sender) 112 | >>> order_digest = client.context.engine_client.get_order_digest(order, product_id) 113 | >>> print("order digest:", order_digest) 114 | 115 | Cancelling an order 116 | ------------------- 117 | 118 | Cancels open orders via `execute:cancel_orders `_. 119 | 120 | .. code-block:: python 121 | 122 | >>> print("cancelling order...") 123 | >>> res = client.market.cancel_orders( 124 | CancelOrdersParams(productIds=[product_id], digests=[order_digest], sender=sender) 125 | ) 126 | >>> print("cancel order result:", res.json(indent=2)) 127 | 128 | Withdrawing collateral 129 | ---------------------- 130 | 131 | Withdraw spot collaterals from Vertex via `execute:withdraw_collateral `_. 132 | 133 | .. code-block:: python 134 | 135 | >>> print("withdrawing collateral...") 136 | >>> withdraw_collateral_params = WithdrawCollateralParams( 137 | productId=0, amount=to_pow_10(10000, 6), sender=sender 138 | ) 139 | >>> res = client.spot.withdraw(withdraw_collateral_params) 140 | >>> print("withdraw result:", res.json(indent=2)) -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | Vertex Protocol Python SDK 2 | ========================== 3 | 4 | This is the Python SDK for the `Vertex Protocol API `_. 5 | 6 | The latest version of the SDK can be found on `PyPI `_, and the source code is on `GitHub `_. 7 | 8 | .. NOTE:: 9 | 10 | 👋 Brand new to Vertex? 11 | 12 | 0. Join the Vertex community via `Discord `_, `Twitter `_. 13 | 1. Read our `docs `_ to learn the basic concepts. 14 | 2. Visit us at `Vertex Protocol `_. 15 | 3. The :ref:`getting-started` section will give you a quick idea of the core things you can do via the SDK! 16 | 17 | Requirements 18 | ------------ 19 | 20 | - Python 3.9 or above 21 | 22 | Installation 23 | ------------ 24 | 25 | You can install the SDK via pip: 26 | 27 | .. code-block:: bash 28 | 29 | pip install vertex-protocol 30 | 31 | You might want to use a virtual environment to isolate your packages. 32 | 33 | .. toctree:: 34 | :maxdepth: 2 35 | :caption: Contents: 36 | 37 | getting-started 38 | user-reference 39 | user-guides 40 | api-reference -------------------------------------------------------------------------------- /docs/source/user-guides.rst: -------------------------------------------------------------------------------- 1 | User guides 2 | =========== 3 | 4 | Signing 5 | ------- 6 | 7 | Signing is handled internally when you instantiate the `VertexClient` (:mod:`vertex_protocol.client.VertexClient`) with a `signer`. Alternatively, 8 | you can construct the requisite signatures for each execute using a set utils provided by the SDK (see :mod:`vertex_protocol.contracts.eip712` for details). 9 | 10 | .. note:: 11 | 12 | Check out our docs to learn more about `signing requests `_ in Vertex. 13 | 14 | EIP-712 15 | ^^^^^^^ 16 | 17 | Vertex executes are signed using `EIP-712 `_ signatures. The following components are needed: 18 | 19 | - **types**: The solidity object name and field types of the message being signed. 20 | - **primaryType**: The name of the solidity object being signed. 21 | - **domain**: A protocol-specific object that includes the verifying contract and `chain-id` of the network. 22 | - **message**: The actual message being signed. 23 | 24 | You can build the expected EIP-712 typed data for each execute via :mod:`vertex_protocol.contracts.eip712.build_eip712_typed_data()` 25 | 26 | **Example:** 27 | 28 | .. code-block:: python 29 | 30 | >>> import time 31 | >>> from vertex_protocol.contracts.types import VertexExecuteType 32 | >>> from vertex_protocol.engine_client.types import OrderParams, SubaccountParams 33 | >>> from vertex_protocol.utils import subaccount_to_bytes32, to_x18, to_pow_10, get_expiration_timestamp, gen_order_nonce, OrderType 34 | >>> from vertex_protocol.contracts.eip712 import build_eip712_typed_data 35 | >>> verifying_contract = "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" 36 | >>> chain_id = 421613 37 | >>> sender = SubaccountParams(subaccount_owner="0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", subaccount_name="default") 38 | >>> order_nonce = gen_order_nonce() 39 | >>> order_expiration = get_expiration_timestamp(OrderType.DEFAULT, int(time.time()) + 40) 40 | >>> order = OrderParams(amount=to_x18(20000), priceX18=to_pow_10(1, 17), expiration=order_expiration, nonce=order_nonce, sender=sender) 41 | >>> order_typed_data = build_eip712_typed_data(VertexExecuteType.PLACE_ORDER, order.dict(), verifying_contract, chain_id) 42 | 43 | The following object is generated and can be signed via :mod:`vertex_protocol.contracts.eip712.sign_eip712_typed_data()`: 44 | 45 | .. code-block:: python 46 | 47 | { 48 | 'types': { 49 | 'EIP712Domain': [ 50 | {'name': 'name', 'type': 'string'}, 51 | {'name': 'version', 'type': 'string'}, 52 | {'name': 'chainId', 'type': 'uint256'}, 53 | {'name': 'verifyingContract', 'type': 'address'} 54 | ], 55 | 'Order': [ 56 | {'name': 'sender', 'type': 'bytes32'}, 57 | {'name': 'priceX18', 'type': 'int128'}, 58 | {'name': 'amount', 'type': 'int128'}, 59 | {'name': 'expiration', 'type': 'uint64'}, 60 | {'name': 'nonce', 'type': 'uint64'} 61 | ] 62 | }, 63 | 'primaryType': 'Order', 64 | 'domain': { 65 | 'name': 'Vertex', 66 | 'version': '0.0.1', 67 | 'chainId': 421613, 68 | 'verifyingContract': '0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6' 69 | }, 70 | 'message': { 71 | 'sender': b'\xf3\x9f\xd6\xe5\x1a\xad\x88\xf6\xf4\xcej\xb8\x82ry\xcf\xff\xb9"fdefault\x00\x00\x00\x00\x00', 72 | 'nonce': 1768628938411606731, 73 | 'priceX18': 100000000000000000, 74 | 'amount': 20000000000000000000000, 75 | 'expiration': 1686695965 76 | } 77 | } 78 | 79 | .. note:: 80 | 81 | - You can retrieve the verifying contracts using :mod:`vertex_protocol.engine_client.EngineQueryClient.get_contracts()`. Provided via **client.context.engine_client.get_contracts()** on a `VertexClient` instance. 82 | - You can also just use the engine client's sign utility :mod:`vertex_protocol.engine_client.EngineExecuteClient.sign()`. Provided via **client.context.engine_client.sign()** on a `VertexClient` instance. -------------------------------------------------------------------------------- /docs/source/vertex.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertex-protocol/vertex-python-sdk/86ca107b5d25ce1f666c7a1fd4a6d2e87a565ebe/docs/source/vertex.ico -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "vertex-protocol" 3 | version = "3.3.1" 4 | description = "Vertex Protocol SDK" 5 | authors = ["Jeury Mejia "] 6 | homepage = "https://vertexprotocol.com/" 7 | maintainers = [ 8 | "Frank Jia ", 9 | "Clark Oh-Willeke " 10 | ] 11 | documentation = "https://vertex-protocol.github.io/vertex-python-sdk/" 12 | readme = "README.md" 13 | packages = [{include = "vertex_protocol"}] 14 | include = ["vertex_protocol/*.json"] 15 | keywords = ["vertex protocol", "vertex sdk", "vertex protocol api"] 16 | 17 | [tool.poetry.dependencies] 18 | python = "^3.9" 19 | pydantic = "^1.10.7" 20 | web3 = "^6.4.0" 21 | eth-account = "^0.8.0" 22 | 23 | [tool.poetry.group.dev.dependencies] 24 | ruff = "*" 25 | black = "*" 26 | pytest = "^7.3.1" 27 | pre-commit = "^3.3.2" 28 | python-dotenv = "^1.0.0" 29 | sphinx = "^6.2.1" 30 | sphinx-rtd-theme = "^1.2.1" 31 | sphinx-autodoc-typehints = "^1.12.0" 32 | mypy = "^1.3.0" 33 | types-requests = "^2.31.0.1" 34 | coverage = "^7.2.7" 35 | 36 | [tool.poetry.scripts] 37 | test = "pytest:main" 38 | engine-sanity = "sanity.engine_client:run" 39 | indexer-sanity = "sanity.indexer_client:run" 40 | trigger-sanity = "sanity.trigger_client:run" 41 | contracts-sanity = "sanity.contracts:run" 42 | client-sanity = "sanity.vertex_client:run" 43 | rewards-sanity = "sanity.rewards:run" 44 | signing-sanity = "sanity.signing:run" 45 | isolated-sanity = "sanity.isolated:run" 46 | 47 | [[tool.poetry.source]] 48 | name = "private" 49 | url = "https://github.com/vertex-protocol/vertex-python-sdk" 50 | priority = "primary" 51 | 52 | [[tool.poetry.source]] 53 | name = "PyPI" 54 | priority = "primary" 55 | 56 | [build-system] 57 | requires = ["poetry-core"] 58 | build-backend = "poetry.core.masonry.api" 59 | 60 | [tool.black] 61 | line-length = 88 62 | target-version = ['py37'] 63 | include = '\.pyi?$' 64 | exclude = ''' 65 | /( 66 | \.git 67 | | \.hg 68 | | \.mypy_cache 69 | | \.tox 70 | | \.venv 71 | | _build 72 | | buck-out 73 | | build 74 | | dist 75 | | venv 76 | | venv3.9 77 | | venv3.10 78 | | venv3.11 79 | )/ 80 | ''' -------------------------------------------------------------------------------- /sanity/__init__.py: -------------------------------------------------------------------------------- 1 | import os 2 | from dotenv import load_dotenv 3 | from vertex_protocol.client import VertexClientMode, client_mode_to_setup 4 | 5 | load_dotenv() 6 | 7 | CLIENT_MODE: VertexClientMode = os.getenv("CLIENT_MODE") 8 | SIGNER_PRIVATE_KEY = os.getenv("SIGNER_PRIVATE_KEY") 9 | LINKED_SIGNER_PRIVATE_KEY = os.getenv("LINKED_SIGNER_PRIVATE_KEY") 10 | 11 | assert CLIENT_MODE, "CLIENT_MODE not set! set via .env file" 12 | assert SIGNER_PRIVATE_KEY, "SIGNER_PRIVATE_KEY not set! set via .env file" 13 | 14 | ( 15 | ENGINE_BACKEND_URL, 16 | INDEXER_BACKEND_URL, 17 | TRIGGER_BACKEND_URL, 18 | NETWORK, 19 | ) = client_mode_to_setup(CLIENT_MODE) 20 | -------------------------------------------------------------------------------- /sanity/contracts.py: -------------------------------------------------------------------------------- 1 | from sanity import NETWORK 2 | from vertex_protocol.contracts import VertexContracts, VertexContractsContext 3 | from vertex_protocol.contracts.loader import load_deployment 4 | 5 | 6 | def run(): 7 | print("setting up vertex contracts") 8 | deployment = load_deployment(NETWORK) 9 | vertex_contracts = VertexContracts( 10 | node_url=deployment.node_url, 11 | contracts_context=VertexContractsContext(**deployment.dict()), 12 | ) 13 | 14 | print("node url:", deployment.node_url) 15 | print("endpoint:", vertex_contracts.endpoint.address) 16 | print("querier:", vertex_contracts.querier.address) 17 | print("clearinghouse:", vertex_contracts.clearinghouse.address) 18 | print("spot_engine:", vertex_contracts.spot_engine.address) 19 | print("perp_engine:", vertex_contracts.perp_engine.address) 20 | print("n-submissions", vertex_contracts.endpoint.functions.nSubmissions().call()) 21 | 22 | # wallet = vertex_contracts.w3.to_checksum_address( 23 | # "0xcb60ca32b25b4e11cd1959514d77356d58d3e138" 24 | # ) 25 | # print( 26 | # "getClaimed", vertex_contracts.vrtx_airdrop.functions.getClaimed(wallet).call() 27 | # ) 28 | -------------------------------------------------------------------------------- /sanity/isolated.py: -------------------------------------------------------------------------------- 1 | import time 2 | from sanity import CLIENT_MODE, SIGNER_PRIVATE_KEY 3 | 4 | from vertex_protocol.client import VertexClient, create_vertex_client 5 | from vertex_protocol.contracts.types import DepositCollateralParams 6 | from vertex_protocol.engine_client.types.models import SpotProductBalance 7 | from vertex_protocol.utils.bytes32 import subaccount_to_hex 8 | from vertex_protocol.utils.execute import IsolatedOrderParams 9 | from vertex_protocol.utils.expiration import OrderType, get_expiration_timestamp 10 | from vertex_protocol.utils.math import to_pow_10, to_x18 11 | from vertex_protocol.utils.nonce import gen_order_nonce 12 | from vertex_protocol.utils.subaccount import SubaccountParams 13 | 14 | 15 | def run(): 16 | print("setting up vertex client...") 17 | client: VertexClient = create_vertex_client(CLIENT_MODE, SIGNER_PRIVATE_KEY) 18 | 19 | print("chain_id:", client.context.engine_client.get_contracts().chain_id) 20 | 21 | print("minting test tokens...") 22 | mint_tx_hash = client.spot._mint_mock_erc20(0, to_pow_10(100000, 6)) 23 | print("mint tx hash:", mint_tx_hash) 24 | 25 | print("approving allowance...") 26 | approve_allowance_tx_hash = client.spot.approve_allowance(0, to_pow_10(100000, 6)) 27 | print("approve allowance tx hash:", approve_allowance_tx_hash) 28 | 29 | print("querying my allowance...") 30 | token_allowance = client.spot.get_token_allowance(0, client.context.signer.address) 31 | print("token allowance:", token_allowance) 32 | 33 | print("depositing collateral...") 34 | deposit_tx_hash = client.spot.deposit( 35 | DepositCollateralParams( 36 | subaccount_name="default", product_id=0, amount=to_pow_10(100000, 6) 37 | ) 38 | ) 39 | print("deposit collateral tx hash:", deposit_tx_hash) 40 | 41 | subaccount = subaccount_to_hex(client.context.signer.address, "default") 42 | 43 | usdc_balance: SpotProductBalance = client.subaccount.get_engine_subaccount_summary( 44 | subaccount 45 | ).parse_subaccount_balance(0) 46 | while int(usdc_balance.balance.amount) == 0: 47 | print("waiting for deposit...") 48 | usdc_balance: SpotProductBalance = ( 49 | client.subaccount.get_engine_subaccount_summary( 50 | subaccount 51 | ).parse_subaccount_balance(0) 52 | ) 53 | time.sleep(1) 54 | 55 | order_price = 95_000 56 | 57 | owner = client.context.engine_client.signer.address 58 | print("placing isolated order...") 59 | product_id = 2 60 | isolated_order = IsolatedOrderParams( 61 | sender=SubaccountParams( 62 | subaccount_owner=owner, 63 | subaccount_name="default", 64 | ), 65 | priceX18=to_x18(order_price), 66 | amount=to_pow_10(1, 17), 67 | expiration=get_expiration_timestamp(OrderType.IOC, int(time.time()) + 40), 68 | nonce=gen_order_nonce(), 69 | margin=to_pow_10(1000, 18), 70 | ) 71 | res = client.market.place_isolated_order( 72 | {"product_id": product_id, "isolated_order": isolated_order} 73 | ) 74 | print("order result:", res.json(indent=2)) 75 | 76 | print("querying isolated positions...") 77 | isolated_positions = client.market.get_isolated_positions(subaccount) 78 | print("isolated positions:", isolated_positions.json(indent=2)) 79 | 80 | print("querying historical isolated orders...") 81 | historical_isolated_orders = client.market.get_subaccount_historical_orders( 82 | {"subaccount": subaccount, "isolated": True} 83 | ) 84 | print("historical isolated orders:", historical_isolated_orders.json(indent=2)) 85 | 86 | print("querying isolated matches...") 87 | isolated_matches = client.context.indexer_client.get_matches( 88 | {"subaccount": subaccount, "isolated": True} 89 | ) 90 | print("isolated matches:", isolated_matches.json(indent=2)) 91 | 92 | print("querying isolated events...") 93 | isolated_events = client.context.indexer_client.get_events( 94 | {"subaccount": subaccount, "limit": {"raw": 5}, "isolated": True} 95 | ) 96 | print("isolated events:", isolated_events.json(indent=2)) 97 | -------------------------------------------------------------------------------- /sanity/rewards.py: -------------------------------------------------------------------------------- 1 | from sanity import CLIENT_MODE, SIGNER_PRIVATE_KEY 2 | from vertex_protocol.client import VertexClient, create_vertex_client 3 | from vertex_protocol.utils.math import to_x18 4 | from vertex_protocol.contracts.types import ClaimVrtxParams 5 | 6 | 7 | def run(): 8 | print("setting up vertex client...") 9 | client: VertexClient = create_vertex_client(CLIENT_MODE, SIGNER_PRIVATE_KEY) 10 | signer = client.context.signer 11 | 12 | print("network:", client.context.contracts.network) 13 | print("signer:", signer.address) 14 | 15 | claim_vrtx_contract_params = client.rewards._get_claim_vrtx_contract_params( 16 | ClaimVrtxParams(epoch=10, amount=to_x18(100)), signer 17 | ) 18 | 19 | print("claim vrtx params:", claim_vrtx_contract_params) 20 | 21 | vrtx = client.context.contracts.get_token_contract_for_product(41) 22 | vrtx_balance = vrtx.functions.balanceOf(signer.address).call() 23 | 24 | print("vrtx balance (pre-claim):", vrtx_balance) 25 | 26 | print("claiming vrtx...") 27 | tx = client.rewards.claim_vrtx(ClaimVrtxParams(epoch=10, amount=to_x18(100))) 28 | print("tx:", tx) 29 | 30 | vrtx_balance = vrtx.functions.balanceOf(signer.address).call() 31 | print("vrtx balance (post-claim):", vrtx_balance) 32 | 33 | claim_and_stake_vrtx_contract_params = ( 34 | client.rewards._get_claim_vrtx_contract_params( 35 | ClaimVrtxParams(epoch=10, amount=to_x18(100)), signer 36 | ) 37 | ) 38 | 39 | print("claim and stake vrtx params:", claim_and_stake_vrtx_contract_params) 40 | 41 | print("claiming and staking vrtx...") 42 | tx = client.rewards.claim_and_stake_vrtx( 43 | ClaimVrtxParams(epoch=10, amount=to_x18(100)) 44 | ) 45 | print("tx:", tx) 46 | 47 | vrtx_balance = vrtx.functions.balanceOf(signer.address).call() 48 | print("vrtx balance (post-claim-and-stake):", vrtx_balance) 49 | 50 | print("approving allowance to staking contract...") 51 | tx = client.context.contracts.approve_allowance( 52 | vrtx, to_x18(100), signer, to=client.context.contracts.vrtx_staking.address 53 | ) 54 | print("tx:", tx) 55 | 56 | print("staking vrtx...") 57 | tx = client.rewards.stake_vrtx(to_x18(100)) 58 | print("tx:", tx) 59 | 60 | vrtx_balance = vrtx.functions.balanceOf(signer.address).call() 61 | print("vrtx balance (post-stake):", vrtx_balance) 62 | 63 | print("unstaking vrtx...") 64 | tx = client.rewards.unstake_vrtx(to_x18(100)) 65 | print(tx) 66 | 67 | print("withdrawing unstaked vrtx...") 68 | tx = client.rewards.withdraw_unstaked_vrtx() 69 | print(tx) 70 | 71 | print("claiming usdc rewards...") 72 | tx = client.rewards.claim_usdc_rewards() 73 | print(tx) 74 | 75 | print("claiming and staking usdc rewards...") 76 | tx = client.rewards.claim_and_stake_usdc_rewards() 77 | print(tx) 78 | 79 | print( 80 | "claim and stake estimated vrtx...", 81 | client.rewards.get_claim_and_stake_estimated_vrtx(signer.address), 82 | ) 83 | 84 | claim_foundation_rewards_contract_params = ( 85 | client.rewards._get_claim_foundation_rewards_contract_params(signer) 86 | ) 87 | 88 | print( 89 | "foundation rewards contract params:", 90 | claim_foundation_rewards_contract_params.json(indent=2), 91 | ) 92 | 93 | print("claiming foundation rewards...") 94 | tx = client.rewards.claim_foundation_rewards() 95 | print(tx) 96 | -------------------------------------------------------------------------------- /sanity/signing.py: -------------------------------------------------------------------------------- 1 | from sanity import CLIENT_MODE, SIGNER_PRIVATE_KEY 2 | from vertex_protocol.contracts.types import VertexTxType 3 | from vertex_protocol.utils.bytes32 import subaccount_to_hex, subaccount_to_bytes32 4 | from vertex_protocol.contracts.eip712.sign import ( 5 | build_eip712_typed_data, 6 | sign_eip712_typed_data, 7 | ) 8 | from vertex_protocol.client import VertexClient, create_vertex_client 9 | 10 | 11 | import time 12 | 13 | from vertex_protocol.engine_client.types.execute import ( 14 | OrderParams, 15 | ) 16 | 17 | from vertex_protocol.utils.expiration import OrderType, get_expiration_timestamp 18 | from vertex_protocol.utils.math import to_pow_10, to_x18 19 | from vertex_protocol.utils.nonce import gen_order_nonce 20 | from vertex_protocol.utils.subaccount import SubaccountParams 21 | from vertex_protocol.utils.time import now_in_millis 22 | 23 | 24 | def run(): 25 | print("setting up vertex client...") 26 | client: VertexClient = create_vertex_client(CLIENT_MODE, SIGNER_PRIVATE_KEY) 27 | 28 | print("chain_id:", client.context.engine_client.get_contracts().chain_id) 29 | 30 | subaccount = subaccount_to_hex(client.context.signer.address, "default") 31 | 32 | print("subaccount:", subaccount) 33 | 34 | print("building StreamAuthentication signature...") 35 | authenticate_stream_typed_data = build_eip712_typed_data( 36 | tx=VertexTxType.AUTHENTICATE_STREAM, 37 | msg={ 38 | "sender": subaccount_to_bytes32(subaccount), 39 | "expiration": now_in_millis(90), 40 | }, 41 | verifying_contract=client.context.contracts.endpoint.address, 42 | chain_id=client.context.engine_client.chain_id, 43 | ) 44 | authenticate_stream_signature = sign_eip712_typed_data( 45 | typed_data=authenticate_stream_typed_data, signer=client.context.signer 46 | ) 47 | print("authenticate stream signature:", authenticate_stream_signature) 48 | 49 | print("building order signature...") 50 | order = OrderParams( 51 | sender=SubaccountParams( 52 | subaccount_owner=client.context.signer.address, subaccount_name="default" 53 | ), 54 | priceX18=to_x18(60000), 55 | amount=to_pow_10(1, 17), 56 | expiration=get_expiration_timestamp(OrderType.DEFAULT, int(time.time()) + 40), 57 | nonce=gen_order_nonce(), 58 | ) 59 | now = time.time() 60 | signature = client.context.engine_client._sign( 61 | "place_order", order.dict(), product_id=1 62 | ) 63 | elapsed_time = (time.time() - now) * 1000 64 | print("place order signature:", signature, "elapsed_time:", elapsed_time) 65 | -------------------------------------------------------------------------------- /sanity/trigger_client.py: -------------------------------------------------------------------------------- 1 | import time 2 | from sanity import ENGINE_BACKEND_URL, SIGNER_PRIVATE_KEY, TRIGGER_BACKEND_URL 3 | from vertex_protocol.engine_client import EngineClient 4 | from vertex_protocol.engine_client.types import EngineClientOpts 5 | from vertex_protocol.engine_client.types.execute import OrderParams 6 | from vertex_protocol.trigger_client import TriggerClient 7 | from vertex_protocol.trigger_client.types import TriggerClientOpts 8 | from vertex_protocol.trigger_client.types.execute import ( 9 | PlaceTriggerOrderParams, 10 | CancelTriggerOrdersParams, 11 | ) 12 | from vertex_protocol.trigger_client.types.models import ( 13 | LastPriceAboveTrigger, 14 | PriceAboveTrigger, 15 | ) 16 | from vertex_protocol.trigger_client.types.query import ( 17 | ListTriggerOrdersParams, 18 | ListTriggerOrdersTx, 19 | ) 20 | from vertex_protocol.utils.bytes32 import subaccount_to_hex 21 | from vertex_protocol.utils.expiration import OrderType, get_expiration_timestamp 22 | from vertex_protocol.utils.math import to_pow_10, to_x18 23 | from vertex_protocol.utils.subaccount import SubaccountParams 24 | from vertex_protocol.utils.time import now_in_millis 25 | 26 | 27 | def run(): 28 | print("setting up trigger client...") 29 | client = TriggerClient( 30 | opts=TriggerClientOpts(url=TRIGGER_BACKEND_URL, signer=SIGNER_PRIVATE_KEY) 31 | ) 32 | 33 | engine_client = EngineClient( 34 | opts=EngineClientOpts(url=ENGINE_BACKEND_URL, signer=SIGNER_PRIVATE_KEY) 35 | ) 36 | 37 | contracts_data = engine_client.get_contracts() 38 | client.endpoint_addr = contracts_data.endpoint_addr 39 | client.chain_id = contracts_data.chain_id 40 | client.book_addrs = contracts_data.book_addrs 41 | 42 | print("placing trigger order...") 43 | order_price = 100_000 44 | 45 | product_id = 1 46 | order = OrderParams( 47 | sender=SubaccountParams( 48 | subaccount_owner=client.signer.address, subaccount_name="default" 49 | ), 50 | priceX18=to_x18(order_price), 51 | amount=to_pow_10(1, 17), 52 | expiration=get_expiration_timestamp(OrderType.DEFAULT, int(time.time()) + 40), 53 | nonce=client.order_nonce(is_trigger_order=True), 54 | ) 55 | order_digest = client.get_order_digest(order, product_id) 56 | print("order digest:", order_digest) 57 | 58 | place_order = PlaceTriggerOrderParams( 59 | product_id=product_id, 60 | order=order, 61 | trigger=PriceAboveTrigger(price_above=to_x18(120_000)), 62 | ) 63 | res = client.place_trigger_order(place_order) 64 | print("trigger order result:", res.json(indent=2)) 65 | 66 | sender = subaccount_to_hex(order.sender) 67 | 68 | cancel_orders = CancelTriggerOrdersParams( 69 | sender=sender, productIds=[product_id], digests=[order_digest] 70 | ) 71 | res = client.cancel_trigger_orders(cancel_orders) 72 | print("cancel trigger order result:", res.json(indent=2)) 73 | 74 | product_id = 2 75 | order = OrderParams( 76 | sender=SubaccountParams( 77 | subaccount_owner=client.signer.address, subaccount_name="default" 78 | ), 79 | priceX18=to_x18(order_price), 80 | amount=to_pow_10(1, 17), 81 | expiration=get_expiration_timestamp(OrderType.DEFAULT, int(time.time()) + 40), 82 | nonce=client.order_nonce(is_trigger_order=True), 83 | ) 84 | order_digest = client.get_order_digest(order, product_id) 85 | print("order digest:", order_digest) 86 | 87 | place_order = PlaceTriggerOrderParams( 88 | product_id=product_id, 89 | order=order, 90 | trigger=LastPriceAboveTrigger(last_price_above=to_x18(120_000)), 91 | ) 92 | res = client.place_trigger_order(place_order) 93 | 94 | print("listing trigger orders...") 95 | trigger_orders = client.list_trigger_orders( 96 | ListTriggerOrdersParams( 97 | tx=ListTriggerOrdersTx( 98 | sender=SubaccountParams( 99 | subaccount_owner=client.signer.address, subaccount_name="default" 100 | ), 101 | recvTime=now_in_millis(90), 102 | ), 103 | pending=True, 104 | product_id=2, 105 | ) 106 | ) 107 | print("trigger orders:", trigger_orders.json(indent=2)) 108 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertex-protocol/vertex-python-sdk/86ca107b5d25ce1f666c7a1fd4a6d2e87a565ebe/tests/__init__.py -------------------------------------------------------------------------------- /tests/contracts/test_vertex_contracts.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import MagicMock 2 | 3 | from vertex_protocol.contracts import VertexContracts, VertexContractsContext 4 | 5 | 6 | def test_vertex_contracts( 7 | url: str, 8 | mock_web3: MagicMock, 9 | mock_load_abi: MagicMock, 10 | contracts_context: VertexContractsContext, 11 | ): 12 | contracts = VertexContracts(node_url=url, contracts_context=contracts_context) 13 | 14 | assert contracts.endpoint 15 | assert contracts.querier 16 | assert not contracts.clearinghouse 17 | assert not contracts.perp_engine 18 | assert not contracts.spot_engine 19 | -------------------------------------------------------------------------------- /tests/engine_client/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertex-protocol/vertex-python-sdk/86ca107b5d25ce1f666c7a1fd4a6d2e87a565ebe/tests/engine_client/__init__.py -------------------------------------------------------------------------------- /tests/engine_client/test_burn_lp.py: -------------------------------------------------------------------------------- 1 | from vertex_protocol.engine_client.types.execute import ( 2 | BurnLpParams, 3 | BurnLpRequest, 4 | to_execute_request, 5 | ) 6 | from vertex_protocol.utils.bytes32 import hex_to_bytes32 7 | 8 | 9 | def test_burn_lp_params(senders: list[str], owners: list[str], burn_lp_params: dict): 10 | sender = senders[0] 11 | product_id = burn_lp_params["productId"] 12 | amount = burn_lp_params["amount"] 13 | params_from_dict = BurnLpParams( 14 | **{"sender": sender, "productId": product_id, "amount": amount} 15 | ) 16 | params_from_obj = BurnLpParams( 17 | sender=sender, 18 | productId=product_id, 19 | amount=amount, 20 | ) 21 | bytes32_sender = BurnLpParams( 22 | sender=hex_to_bytes32(sender), productId=product_id, amount=amount 23 | ) 24 | subaccount_params_sender = BurnLpParams( 25 | sender={"subaccount_owner": owners[0], "subaccount_name": "default"}, 26 | productId=product_id, 27 | amount=amount, 28 | ) 29 | 30 | assert ( 31 | params_from_dict 32 | == params_from_obj 33 | == bytes32_sender 34 | == subaccount_params_sender 35 | ) 36 | params_from_dict.signature = ( 37 | "0x51ba8762bc5f77957a4e896dba34e17b553b872c618ffb83dba54878796f2821" 38 | ) 39 | params_from_dict.nonce = 100000 40 | req_from_params = BurnLpRequest(burn_lp=params_from_dict) 41 | assert req_from_params == to_execute_request(params_from_dict) 42 | assert req_from_params.dict() == { 43 | "burn_lp": { 44 | "tx": { 45 | "sender": sender.lower(), 46 | "productId": product_id, 47 | "amount": str(amount), 48 | "nonce": str(params_from_dict.nonce), 49 | }, 50 | "signature": params_from_dict.signature, 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /tests/engine_client/test_cancel_and_place.py: -------------------------------------------------------------------------------- 1 | from vertex_protocol.engine_client.types.execute import ( 2 | CancelOrdersParams, 3 | CancelOrdersRequest, 4 | CancelAndPlaceParams, 5 | CancelAndPlaceRequest, 6 | ) 7 | from vertex_protocol.utils.bytes32 import hex_to_bytes32 8 | from vertex_protocol.engine_client.types.execute import ( 9 | OrderParams, 10 | PlaceOrderParams, 11 | PlaceOrderRequest, 12 | to_execute_request, 13 | ) 14 | from vertex_protocol.utils.nonce import gen_order_nonce 15 | from vertex_protocol.utils.subaccount import SubaccountParams 16 | 17 | 18 | def test_cancel_and_place_params( 19 | senders: list[str], owners: list[str], order_params: dict 20 | ): 21 | product_ids = [4] 22 | digests = ["0x51ba8762bc5f77957a4e896dba34e17b553b872c618ffb83dba54878796f2821"] 23 | sender = hex_to_bytes32(senders[0]) 24 | cancel_params_from_dict = CancelOrdersParams( 25 | **{"productIds": product_ids, "sender": sender, "digests": digests} 26 | ) 27 | cancel_params_from_obj = CancelOrdersParams( 28 | sender=senders[0], 29 | productIds=product_ids, 30 | digests=digests, 31 | ) 32 | cancel_bytes32_digests = CancelOrdersParams( 33 | sender=sender, 34 | productIds=product_ids, 35 | digests=[hex_to_bytes32(digest) for digest in digests], 36 | ) 37 | assert cancel_params_from_dict == cancel_params_from_obj == cancel_bytes32_digests 38 | cancel_params_from_dict.signature = ( 39 | "0x51ba8762bc5f77957a4e896dba34e17b553b872c618ffb83dba54878796f2821" 40 | ) 41 | cancel_params_from_dict.nonce = 100000 42 | 43 | product_id = 1 44 | sender = hex_to_bytes32(senders[0]) 45 | place_params_from_dict = PlaceOrderParams( 46 | **{ 47 | "product_id": product_id, 48 | "order": { 49 | "sender": senders[0], 50 | "priceX18": order_params["priceX18"], 51 | "amount": order_params["amount"], 52 | "expiration": order_params["expiration"], 53 | }, 54 | } 55 | ) 56 | place_params_from_obj = PlaceOrderParams( 57 | product_id=product_id, 58 | order=OrderParams( 59 | sender=senders[0], 60 | priceX18=order_params["priceX18"], 61 | amount=order_params["amount"], 62 | expiration=order_params["expiration"], 63 | ), 64 | ) 65 | place_bytes32_sender = PlaceOrderParams( 66 | product_id=product_id, 67 | order=OrderParams( 68 | sender=hex_to_bytes32(senders[0]), 69 | priceX18=order_params["priceX18"], 70 | amount=order_params["amount"], 71 | expiration=order_params["expiration"], 72 | ), 73 | ) 74 | place_subaccount_params_sender = PlaceOrderParams( 75 | product_id=product_id, 76 | order=OrderParams( 77 | sender=SubaccountParams( 78 | subaccount_owner=owners[0], subaccount_name="default" 79 | ), 80 | priceX18=order_params["priceX18"], 81 | amount=order_params["amount"], 82 | expiration=order_params["expiration"], 83 | ), 84 | ) 85 | 86 | assert ( 87 | place_params_from_dict 88 | == place_params_from_obj 89 | == place_bytes32_sender 90 | == place_subaccount_params_sender 91 | ) 92 | 93 | assert place_params_from_dict.product_id == product_id 94 | assert place_params_from_dict.order.sender == sender 95 | assert place_params_from_dict.order.amount == order_params["amount"] 96 | assert place_params_from_dict.order.priceX18 == order_params["priceX18"] 97 | assert place_params_from_dict.order.expiration == order_params["expiration"] 98 | assert place_params_from_dict.signature is None 99 | 100 | place_params_from_dict.signature = ( 101 | "0x51ba8762bc5f77957a4e896dba34e17b553b872c618ffb83dba54878796f2821" 102 | ) 103 | place_params_from_dict.order.nonce = gen_order_nonce() 104 | place_order_req = PlaceOrderRequest(place_order=place_params_from_dict) 105 | assert place_order_req == to_execute_request(place_params_from_dict) 106 | 107 | cancel_and_place_params = CancelAndPlaceParams( 108 | cancel_orders=cancel_params_from_dict, place_order=place_params_from_dict 109 | ) 110 | cancel_and_place_req = CancelAndPlaceRequest( 111 | cancel_and_place=cancel_and_place_params 112 | ) 113 | assert cancel_and_place_req.dict() == { 114 | "cancel_and_place": { 115 | "cancel_tx": { 116 | "productIds": product_ids, 117 | "digests": digests, 118 | "sender": senders[0].lower(), 119 | "nonce": str(cancel_params_from_dict.nonce), 120 | }, 121 | "cancel_signature": cancel_params_from_dict.signature, 122 | "place_order": { 123 | "product_id": product_id, 124 | "order": { 125 | "sender": senders[0].lower(), 126 | "priceX18": str(order_params["priceX18"]), 127 | "amount": str(order_params["amount"]), 128 | "expiration": str(order_params["expiration"]), 129 | "nonce": str(place_params_from_dict.order.nonce), 130 | }, 131 | "signature": place_params_from_dict.signature, 132 | }, 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /tests/engine_client/test_cancel_orders.py: -------------------------------------------------------------------------------- 1 | from vertex_protocol.engine_client.types.execute import ( 2 | CancelOrdersParams, 3 | CancelOrdersRequest, 4 | ) 5 | from vertex_protocol.utils.bytes32 import hex_to_bytes32 6 | 7 | 8 | def test_cancel_orders_params(senders: list[str]): 9 | product_ids = [4] 10 | digests = ["0x51ba8762bc5f77957a4e896dba34e17b553b872c618ffb83dba54878796f2821"] 11 | sender = hex_to_bytes32(senders[0]) 12 | params_from_dict = CancelOrdersParams( 13 | **{"productIds": product_ids, "sender": sender, "digests": digests} 14 | ) 15 | params_from_obj = CancelOrdersParams( 16 | sender=senders[0], 17 | productIds=product_ids, 18 | digests=digests, 19 | ) 20 | bytes32_digests = CancelOrdersParams( 21 | sender=sender, 22 | productIds=product_ids, 23 | digests=[hex_to_bytes32(digest) for digest in digests], 24 | ) 25 | assert params_from_dict == params_from_obj == bytes32_digests 26 | params_from_dict.signature = ( 27 | "0x51ba8762bc5f77957a4e896dba34e17b553b872c618ffb83dba54878796f2821" 28 | ) 29 | params_from_dict.nonce = 100000 30 | request_from_params = CancelOrdersRequest(cancel_orders=params_from_dict) 31 | assert request_from_params.dict() == { 32 | "cancel_orders": { 33 | "tx": { 34 | "productIds": product_ids, 35 | "digests": digests, 36 | "sender": senders[0].lower(), 37 | "nonce": str(params_from_dict.nonce), 38 | }, 39 | "signature": params_from_dict.signature, 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/engine_client/test_cancel_product_orders.py: -------------------------------------------------------------------------------- 1 | from vertex_protocol.engine_client.types.execute import ( 2 | CancelProductOrdersParams, 3 | CancelProductOrdersRequest, 4 | to_execute_request, 5 | ) 6 | from vertex_protocol.utils.bytes32 import hex_to_bytes32 7 | 8 | 9 | def test_cancel_product_orders_params(senders: list[str]): 10 | product_ids = [4] 11 | digest = "0x51ba8762bc5f77957a4e896dba34e17b553b872c618ffb83dba54878796f2821" 12 | sender = senders[0] 13 | params_from_dict = CancelProductOrdersParams( 14 | **{ 15 | "sender": sender, 16 | "productIds": product_ids, 17 | } 18 | ) 19 | params_from_obj = CancelProductOrdersParams(sender=sender, productIds=product_ids) 20 | bytes32_sender = CancelProductOrdersParams( 21 | sender=hex_to_bytes32(sender), productIds=product_ids 22 | ) 23 | 24 | assert params_from_dict == params_from_obj == bytes32_sender 25 | params_from_dict.signature = ( 26 | "0x51ba8762bc5f77957a4e896dba34e17b553b872c618ffb83dba54878796f2821" 27 | ) 28 | params_from_dict.nonce = 100000 29 | params_from_dict.digest = digest 30 | req_from_params = CancelProductOrdersRequest(cancel_product_orders=params_from_dict) 31 | assert req_from_params == to_execute_request(params_from_dict) 32 | assert req_from_params.dict() == { 33 | "cancel_product_orders": { 34 | "tx": { 35 | "sender": sender.lower(), 36 | "productIds": product_ids, 37 | "nonce": str(params_from_dict.nonce), 38 | }, 39 | "digest": params_from_dict.digest, 40 | "signature": params_from_dict.signature, 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/engine_client/test_create_client.py: -------------------------------------------------------------------------------- 1 | from eth_account import Account 2 | from vertex_protocol.engine_client import EngineClient, EngineClientOpts 3 | from eth_account.signers.local import LocalAccount 4 | from pydantic import ValidationError 5 | import pytest 6 | 7 | 8 | def test_create_client_url_validation(): 9 | with pytest.raises(ValidationError): 10 | _ = EngineClient({"url": "invalid_url"}) 11 | 12 | ws_url = "ws://example.com" 13 | ws_client = EngineClient({"url": ws_url}) 14 | assert ws_client.url == ws_url 15 | 16 | http_url = "http://example.com" 17 | http_client = EngineClient({"url": http_url}) 18 | assert http_client.url == http_url 19 | 20 | opts = EngineClientOpts(url=http_url) 21 | engine_client = EngineClient(opts) 22 | 23 | assert engine_client.url == http_url 24 | 25 | 26 | def test_create_client_signer_validation(url: str, private_keys: list[str]): 27 | opts_signer_from_private_key = EngineClientOpts(url=url, signer=private_keys[0]) 28 | assert isinstance(opts_signer_from_private_key.signer, LocalAccount) 29 | assert opts_signer_from_private_key.signer.key.hex() == private_keys[0] 30 | assert opts_signer_from_private_key.linked_signer is None 31 | 32 | with pytest.raises( 33 | ValidationError, match="linked_signer cannot be set if signer is not set" 34 | ): 35 | EngineClientOpts(url=url, linked_signer=private_keys[1]) 36 | 37 | signer = Account.from_key(private_keys[0]) 38 | opts_linked_signer_from_private_key = EngineClientOpts( 39 | url=url, linked_signer=private_keys[1], signer=signer 40 | ) 41 | assert isinstance(opts_linked_signer_from_private_key.linked_signer, LocalAccount) 42 | assert ( 43 | opts_linked_signer_from_private_key.linked_signer.key.hex() == private_keys[1] 44 | ) 45 | assert opts_linked_signer_from_private_key.signer is not None 46 | 47 | opts_signer_from_account = EngineClientOpts(url=url, signer=signer) 48 | assert isinstance(opts_signer_from_account.signer, LocalAccount) 49 | assert opts_signer_from_account.signer.key.hex() == private_keys[0] 50 | 51 | linked_signer = Account.from_key(private_keys[1]) 52 | opts_linked_signer_from_account = EngineClientOpts( 53 | url=url, signer=signer, linked_signer=linked_signer 54 | ) 55 | assert isinstance(opts_linked_signer_from_account.linked_signer, LocalAccount) 56 | assert opts_linked_signer_from_account.linked_signer.key.hex() == private_keys[1] 57 | 58 | 59 | def test_create_client_all_opts( 60 | url: str, 61 | private_keys: list[str], 62 | chain_id: int, 63 | endpoint_addr: str, 64 | book_addrs: list[str], 65 | ): 66 | client_from_dict = EngineClient( 67 | { 68 | "url": url, 69 | "signer": private_keys[0], 70 | "linked_signer": private_keys[1], 71 | "chain_id": chain_id, 72 | "endpoint_addr": endpoint_addr, 73 | "book_addrs": book_addrs, 74 | } 75 | ) 76 | client_from_opts = EngineClient( 77 | opts=EngineClientOpts( 78 | url=url, 79 | signer=private_keys[0], 80 | linked_signer=private_keys[1], 81 | chain_id=chain_id, 82 | endpoint_addr=endpoint_addr, 83 | book_addrs=book_addrs, 84 | ) 85 | ) 86 | 87 | assert ( 88 | client_from_dict.signer 89 | == client_from_opts.signer 90 | == Account.from_key(private_keys[0]) 91 | ) 92 | assert ( 93 | client_from_dict.linked_signer 94 | == client_from_opts.linked_signer 95 | == Account.from_key(private_keys[1]) 96 | ) 97 | assert client_from_dict.chain_id == client_from_opts.chain_id == chain_id 98 | assert ( 99 | client_from_dict.endpoint_addr 100 | == client_from_opts.endpoint_addr 101 | == endpoint_addr 102 | ) 103 | assert client_from_dict.book_addrs == client_from_opts.book_addrs == book_addrs 104 | -------------------------------------------------------------------------------- /tests/engine_client/test_execute_client.py: -------------------------------------------------------------------------------- 1 | from eth_account import Account 2 | from vertex_protocol.engine_client import EngineClient 3 | import pytest 4 | 5 | 6 | def test_execute_client_properties( 7 | url: str, 8 | chain_id: int, 9 | endpoint_addr: str, 10 | book_addrs: list[str], 11 | private_keys: list[str], 12 | ): 13 | engine_client = EngineClient(opts={"url": url}) 14 | with pytest.raises(AttributeError, match="Endpoint address not set"): 15 | engine_client.endpoint_addr 16 | 17 | with pytest.raises(AttributeError, match="Book addresses are not set"): 18 | engine_client.book_addrs 19 | 20 | with pytest.raises(AttributeError, match="Chain ID is not set"): 21 | engine_client.chain_id 22 | 23 | with pytest.raises(AttributeError, match="Signer is not set"): 24 | engine_client.signer 25 | 26 | with pytest.raises(AttributeError, match="Signer is not set"): 27 | engine_client.linked_signer 28 | 29 | engine_client.endpoint_addr = endpoint_addr 30 | engine_client.book_addrs = book_addrs 31 | engine_client.chain_id = chain_id 32 | 33 | signer = Account.from_key(private_keys[0]) 34 | linked_signer = Account.from_key(private_keys[1]) 35 | 36 | assert signer != linked_signer 37 | 38 | with pytest.raises( 39 | AttributeError, 40 | match="Must set a `signer` first before setting `linked_signer`.", 41 | ): 42 | engine_client.linked_signer = linked_signer 43 | 44 | engine_client.signer = signer 45 | 46 | assert engine_client.signer == engine_client.linked_signer 47 | 48 | engine_client.linked_signer = linked_signer 49 | 50 | assert engine_client.endpoint_addr == endpoint_addr 51 | assert engine_client.book_addrs == book_addrs 52 | assert engine_client.chain_id == chain_id 53 | assert engine_client.signer == signer 54 | assert engine_client.linked_signer == linked_signer 55 | 56 | engine_client.linked_signer = None 57 | 58 | assert engine_client.linked_signer == engine_client.signer 59 | -------------------------------------------------------------------------------- /tests/engine_client/test_expiration.py: -------------------------------------------------------------------------------- 1 | from vertex_protocol.utils.expiration import ( 2 | OrderType, 3 | decode_expiration, 4 | get_expiration_timestamp, 5 | ) 6 | 7 | 8 | def test_expiration_encoding(): 9 | unix_epoch = 1685939478 10 | 11 | for order_type in [ 12 | OrderType.DEFAULT, 13 | OrderType.IOC, 14 | OrderType.FOK, 15 | OrderType.POST_ONLY, 16 | ]: 17 | encoded_expiration = get_expiration_timestamp(order_type, unix_epoch) 18 | decoded_order_type, decoded_unix_epoch = decode_expiration(encoded_expiration) 19 | 20 | assert decoded_unix_epoch == unix_epoch 21 | assert decoded_order_type == order_type 22 | 23 | 24 | def test_reduce_only(): 25 | unix_epoch = 1685939478 26 | 27 | def is_reduce_only(expiration: int): 28 | return (expiration & (1 << 61)) != 0 29 | 30 | reduced_only_expiration = get_expiration_timestamp( 31 | OrderType.FOK, unix_epoch, reduce_only=True 32 | ) 33 | non_reduced_only_expiration = get_expiration_timestamp(OrderType.FOK, unix_epoch) 34 | 35 | assert is_reduce_only(reduced_only_expiration) 36 | assert not is_reduce_only(non_reduced_only_expiration) 37 | assert not is_reduce_only( 38 | get_expiration_timestamp(OrderType.FOK, unix_epoch, bool(None)) 39 | ) 40 | -------------------------------------------------------------------------------- /tests/engine_client/test_link_signer.py: -------------------------------------------------------------------------------- 1 | from vertex_protocol.engine_client.types.execute import ( 2 | LinkSignerParams, 3 | LinkSignerRequest, 4 | to_execute_request, 5 | ) 6 | from vertex_protocol.utils.bytes32 import hex_to_bytes32 7 | 8 | 9 | def test_link_signer_params(senders: list[str], owners: list[str]): 10 | sender = senders[0] 11 | signer = senders[1] 12 | params_from_dict = LinkSignerParams( 13 | **{ 14 | "sender": sender, 15 | "signer": signer, 16 | } 17 | ) 18 | params_from_obj = LinkSignerParams(sender=sender, signer=signer) 19 | bytes32_sender = LinkSignerParams( 20 | sender=hex_to_bytes32(sender), 21 | signer=hex_to_bytes32(signer), 22 | ) 23 | subaccount_params_sender = LinkSignerParams( 24 | sender={"subaccount_owner": owners[0], "subaccount_name": "default"}, 25 | signer={"subaccount_owner": owners[1], "subaccount_name": "default"}, 26 | ) 27 | 28 | assert ( 29 | params_from_dict 30 | == params_from_obj 31 | == bytes32_sender 32 | == subaccount_params_sender 33 | ) 34 | params_from_dict.signature = ( 35 | "0x51ba8762bc5f77957a4e896dba34e17b553b872c618ffb83dba54878796f2821" 36 | ) 37 | params_from_dict.nonce = 100000 38 | req_from_params = LinkSignerRequest(link_signer=params_from_dict) 39 | assert req_from_params == to_execute_request(params_from_dict) 40 | assert req_from_params.dict() == { 41 | "link_signer": { 42 | "tx": { 43 | "sender": sender.lower(), 44 | "signer": signer.lower(), 45 | "nonce": str(params_from_dict.nonce), 46 | }, 47 | "signature": params_from_dict.signature, 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /tests/engine_client/test_liquidate_subaccount.py: -------------------------------------------------------------------------------- 1 | from vertex_protocol.engine_client.types.execute import ( 2 | LiquidateSubaccountParams, 3 | LiquidateSubaccountRequest, 4 | to_execute_request, 5 | ) 6 | from vertex_protocol.utils.bytes32 import hex_to_bytes32 7 | 8 | 9 | def test_liquidate_subaccount_params( 10 | senders: list[str], owners: list[str], liquidate_subaccount_params: dict 11 | ): 12 | sender = senders[0] 13 | liquidatee = senders[1] 14 | product_id = liquidate_subaccount_params["productId"] 15 | is_encoded_spread = liquidate_subaccount_params["isEncodedSpread"] 16 | amount = liquidate_subaccount_params["amount"] 17 | params_from_dict = LiquidateSubaccountParams( 18 | **{ 19 | "sender": sender, 20 | "liquidatee": liquidatee, 21 | "productId": product_id, 22 | "isEncodedSpread": is_encoded_spread, 23 | "amount": amount, 24 | } 25 | ) 26 | params_from_obj = LiquidateSubaccountParams( 27 | sender=sender, 28 | liquidatee=liquidatee, 29 | productId=product_id, 30 | isEncodedSpread=is_encoded_spread, 31 | amount=amount, 32 | ) 33 | bytes32_sender = LiquidateSubaccountParams( 34 | sender=hex_to_bytes32(senders[0]), 35 | liquidatee=hex_to_bytes32(senders[1]), 36 | productId=product_id, 37 | isEncodedSpread=is_encoded_spread, 38 | amount=amount, 39 | ) 40 | subaccount_params_sender = LiquidateSubaccountParams( 41 | sender={"subaccount_owner": owners[0], "subaccount_name": "default"}, 42 | liquidatee={"subaccount_owner": owners[1], "subaccount_name": "default"}, 43 | productId=product_id, 44 | isEncodedSpread=is_encoded_spread, 45 | amount=amount, 46 | ) 47 | 48 | assert ( 49 | params_from_dict 50 | == params_from_obj 51 | == bytes32_sender 52 | == subaccount_params_sender 53 | ) 54 | params_from_dict.signature = ( 55 | "0x51ba8762bc5f77957a4e896dba34e17b553b872c618ffb83dba54878796f2821" 56 | ) 57 | params_from_dict.nonce = 100000 58 | req_from_params = LiquidateSubaccountRequest(liquidate_subaccount=params_from_dict) 59 | assert req_from_params == to_execute_request(params_from_dict) 60 | assert req_from_params.dict() == { 61 | "liquidate_subaccount": { 62 | "tx": { 63 | "sender": sender.lower(), 64 | "liquidatee": liquidatee.lower(), 65 | "productId": product_id, 66 | "isEncodedSpread": is_encoded_spread, 67 | "amount": str(amount), 68 | "nonce": str(params_from_dict.nonce), 69 | }, 70 | "signature": params_from_dict.signature, 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /tests/engine_client/test_mint_lp.py: -------------------------------------------------------------------------------- 1 | from vertex_protocol.engine_client.types.execute import ( 2 | MintLpParams, 3 | MintLpRequest, 4 | to_execute_request, 5 | ) 6 | from vertex_protocol.utils.bytes32 import hex_to_bytes32 7 | 8 | 9 | def test_mint_lp_params(senders: list[str], owners: list[str], mint_lp_params: dict): 10 | sender = senders[0] 11 | product_id = mint_lp_params["productId"] 12 | amount_base = mint_lp_params["amountBase"] 13 | quote_amount_low = mint_lp_params["quoteAmountLow"] 14 | quote_amount_high = mint_lp_params["quoteAmountHigh"] 15 | params_from_dict = MintLpParams( 16 | **{ 17 | "sender": sender, 18 | "productId": product_id, 19 | "amountBase": amount_base, 20 | "quoteAmountLow": quote_amount_low, 21 | "quoteAmountHigh": quote_amount_high, 22 | } 23 | ) 24 | params_from_obj = MintLpParams( 25 | sender=sender, 26 | productId=mint_lp_params["productId"], 27 | amountBase=amount_base, 28 | quoteAmountLow=quote_amount_low, 29 | quoteAmountHigh=quote_amount_high, 30 | ) 31 | bytes32_sender = MintLpParams( 32 | sender=hex_to_bytes32(sender), 33 | productId=product_id, 34 | amountBase=amount_base, 35 | quoteAmountLow=quote_amount_low, 36 | quoteAmountHigh=quote_amount_high, 37 | ) 38 | subaccount_params_sender = MintLpParams( 39 | sender={"subaccount_owner": owners[0], "subaccount_name": "default"}, 40 | productId=product_id, 41 | amountBase=amount_base, 42 | quoteAmountLow=quote_amount_low, 43 | quoteAmountHigh=quote_amount_high, 44 | ) 45 | 46 | assert ( 47 | params_from_dict 48 | == params_from_obj 49 | == bytes32_sender 50 | == subaccount_params_sender 51 | ) 52 | params_from_dict.signature = ( 53 | "0x51ba8762bc5f77957a4e896dba34e17b553b872c618ffb83dba54878796f2821" 54 | ) 55 | params_from_dict.nonce = 100000 56 | req_from_params = MintLpRequest(mint_lp=params_from_dict) 57 | assert req_from_params == to_execute_request(params_from_dict) 58 | assert req_from_params.dict() == { 59 | "mint_lp": { 60 | "tx": { 61 | "sender": sender.lower(), 62 | "productId": product_id, 63 | "amountBase": str(amount_base), 64 | "quoteAmountLow": str(quote_amount_low), 65 | "quoteAmountHigh": str(quote_amount_high), 66 | "nonce": str(params_from_dict.nonce), 67 | }, 68 | "signature": params_from_dict.signature, 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /tests/engine_client/test_nonce.py: -------------------------------------------------------------------------------- 1 | import time 2 | from vertex_protocol.utils.nonce import gen_order_nonce 3 | 4 | 5 | def test_nonce(): 6 | nonce = gen_order_nonce() 7 | time_now = int(time.time()) * 1000 8 | 9 | assert (nonce >> 20) >= time_now and (nonce >> 20) <= time_now + 99 * 1000 10 | -------------------------------------------------------------------------------- /tests/engine_client/test_withdraw_collateral.py: -------------------------------------------------------------------------------- 1 | from vertex_protocol.engine_client.types.execute import ( 2 | WithdrawCollateralParams, 3 | WithdrawCollateralRequest, 4 | to_execute_request, 5 | ) 6 | from vertex_protocol.utils.bytes32 import hex_to_bytes32 7 | 8 | 9 | def test_withdraw_collateral_params(senders: list[str], owners: list[str]): 10 | product_id = 1 11 | amount = 10000 12 | sender = senders[0] 13 | params_from_dict = WithdrawCollateralParams( 14 | **{ 15 | "sender": sender, 16 | "productId": product_id, 17 | "amount": amount, 18 | "spot_leverage": False, 19 | } 20 | ) 21 | params_from_obj = WithdrawCollateralParams( 22 | sender=sender, productId=product_id, amount=amount, spot_leverage=False 23 | ) 24 | bytes32_sender = WithdrawCollateralParams( 25 | sender=hex_to_bytes32(sender), 26 | productId=product_id, 27 | amount=amount, 28 | spot_leverage=False, 29 | ) 30 | subaccount_params_sender = WithdrawCollateralParams( 31 | sender={"subaccount_owner": owners[0], "subaccount_name": "default"}, 32 | productId=product_id, 33 | amount=amount, 34 | spot_leverage=False, 35 | ) 36 | 37 | assert ( 38 | params_from_dict 39 | == params_from_obj 40 | == bytes32_sender 41 | == subaccount_params_sender 42 | ) 43 | params_from_dict.signature = ( 44 | "0x51ba8762bc5f77957a4e896dba34e17b553b872c618ffb83dba54878796f2821" 45 | ) 46 | params_from_dict.nonce = 100000 47 | req_from_params = WithdrawCollateralRequest(withdraw_collateral=params_from_dict) 48 | assert req_from_params == to_execute_request(params_from_dict) 49 | assert req_from_params.dict() == { 50 | "withdraw_collateral": { 51 | "tx": { 52 | "sender": sender.lower(), 53 | "productId": product_id, 54 | "amount": str(amount), 55 | "nonce": str(params_from_dict.nonce), 56 | }, 57 | "spot_leverage": False, 58 | "signature": params_from_dict.signature, 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/indexer_client/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertex-protocol/vertex-python-sdk/86ca107b5d25ce1f666c7a1fd4a6d2e87a565ebe/tests/indexer_client/__init__.py -------------------------------------------------------------------------------- /tests/trigger_client/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertex-protocol/vertex-python-sdk/86ca107b5d25ce1f666c7a1fd4a6d2e87a565ebe/tests/trigger_client/__init__.py -------------------------------------------------------------------------------- /tests/trigger_client/test_cancel_trigger_orders.py: -------------------------------------------------------------------------------- 1 | from vertex_protocol.trigger_client.types.execute import ( 2 | CancelTriggerOrdersParams, 3 | CancelTriggerOrdersRequest, 4 | ) 5 | from vertex_protocol.utils.bytes32 import hex_to_bytes32 6 | 7 | 8 | def test_cancel_trigger_orders_params(senders: list[str]): 9 | product_ids = [4] 10 | digests = ["0x51ba8762bc5f77957a4e896dba34e17b553b872c618ffb83dba54878796f2821"] 11 | sender = hex_to_bytes32(senders[0]) 12 | params_from_dict = CancelTriggerOrdersParams( 13 | **{"productIds": product_ids, "sender": sender, "digests": digests} 14 | ) 15 | params_from_obj = CancelTriggerOrdersParams( 16 | sender=senders[0], 17 | productIds=product_ids, 18 | digests=digests, 19 | ) 20 | bytes32_digests = CancelTriggerOrdersParams( 21 | sender=sender, 22 | productIds=product_ids, 23 | digests=[hex_to_bytes32(digest) for digest in digests], 24 | ) 25 | assert params_from_dict == params_from_obj == bytes32_digests 26 | params_from_dict.signature = ( 27 | "0x51ba8762bc5f77957a4e896dba34e17b553b872c618ffb83dba54878796f2821" 28 | ) 29 | params_from_dict.nonce = 100000 30 | request_from_params = CancelTriggerOrdersRequest(cancel_orders=params_from_dict) 31 | assert request_from_params.dict() == { 32 | "cancel_orders": { 33 | "tx": { 34 | "productIds": product_ids, 35 | "digests": digests, 36 | "sender": senders[0].lower(), 37 | "nonce": str(params_from_dict.nonce), 38 | }, 39 | "signature": params_from_dict.signature, 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/trigger_client/test_cancel_trigger_product_orders.py: -------------------------------------------------------------------------------- 1 | from vertex_protocol.trigger_client.types.execute import ( 2 | CancelProductTriggerOrdersParams, 3 | CancelProductTriggerOrdersRequest, 4 | to_trigger_execute_request, 5 | ) 6 | from vertex_protocol.utils.bytes32 import hex_to_bytes32 7 | 8 | 9 | def test_cancel_product_trigger_orders_params(senders: list[str]): 10 | product_ids = [4] 11 | digest = "0x51ba8762bc5f77957a4e896dba34e17b553b872c618ffb83dba54878796f2821" 12 | sender = senders[0] 13 | params_from_dict = CancelProductTriggerOrdersParams( 14 | **{ 15 | "sender": sender, 16 | "productIds": product_ids, 17 | } 18 | ) 19 | params_from_obj = CancelProductTriggerOrdersParams( 20 | sender=sender, productIds=product_ids 21 | ) 22 | bytes32_sender = CancelProductTriggerOrdersParams( 23 | sender=hex_to_bytes32(sender), productIds=product_ids 24 | ) 25 | 26 | assert params_from_dict == params_from_obj == bytes32_sender 27 | params_from_dict.signature = ( 28 | "0x51ba8762bc5f77957a4e896dba34e17b553b872c618ffb83dba54878796f2821" 29 | ) 30 | params_from_dict.nonce = 100000 31 | params_from_dict.digest = digest 32 | req_from_params = CancelProductTriggerOrdersRequest( 33 | cancel_product_orders=params_from_dict 34 | ) 35 | assert req_from_params == to_trigger_execute_request(params_from_dict) 36 | assert req_from_params.dict() == { 37 | "cancel_product_orders": { 38 | "tx": { 39 | "sender": sender.lower(), 40 | "productIds": product_ids, 41 | "nonce": str(params_from_dict.nonce), 42 | }, 43 | "digest": params_from_dict.digest, 44 | "signature": params_from_dict.signature, 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /tests/trigger_client/test_create_client.py: -------------------------------------------------------------------------------- 1 | from eth_account import Account 2 | from vertex_protocol.trigger_client import TriggerClient, TriggerClientOpts 3 | from eth_account.signers.local import LocalAccount 4 | from pydantic import ValidationError 5 | import pytest 6 | 7 | 8 | def test_create_client_url_validation(): 9 | with pytest.raises(ValidationError): 10 | _ = TriggerClient({"url": "invalid_url"}) 11 | 12 | ws_url = "ws://example.com" 13 | ws_client = TriggerClient({"url": ws_url}) 14 | assert ws_client.url == ws_url 15 | 16 | http_url = "http://example.com" 17 | http_client = TriggerClient({"url": http_url}) 18 | assert http_client.url == http_url 19 | 20 | opts = TriggerClientOpts(url=http_url) 21 | trigger_client = TriggerClient(opts) 22 | 23 | assert trigger_client.url == http_url 24 | 25 | 26 | def test_create_client_signer_validation(url: str, private_keys: list[str]): 27 | opts_signer_from_private_key = TriggerClientOpts(url=url, signer=private_keys[0]) 28 | assert isinstance(opts_signer_from_private_key.signer, LocalAccount) 29 | assert opts_signer_from_private_key.signer.key.hex() == private_keys[0] 30 | assert opts_signer_from_private_key.linked_signer is None 31 | 32 | with pytest.raises( 33 | ValidationError, match="linked_signer cannot be set if signer is not set" 34 | ): 35 | TriggerClientOpts(url=url, linked_signer=private_keys[1]) 36 | 37 | signer = Account.from_key(private_keys[0]) 38 | opts_linked_signer_from_private_key = TriggerClientOpts( 39 | url=url, linked_signer=private_keys[1], signer=signer 40 | ) 41 | assert isinstance(opts_linked_signer_from_private_key.linked_signer, LocalAccount) 42 | assert ( 43 | opts_linked_signer_from_private_key.linked_signer.key.hex() == private_keys[1] 44 | ) 45 | assert opts_linked_signer_from_private_key.signer is not None 46 | 47 | opts_signer_from_account = TriggerClientOpts(url=url, signer=signer) 48 | assert isinstance(opts_signer_from_account.signer, LocalAccount) 49 | assert opts_signer_from_account.signer.key.hex() == private_keys[0] 50 | 51 | linked_signer = Account.from_key(private_keys[1]) 52 | opts_linked_signer_from_account = TriggerClientOpts( 53 | url=url, signer=signer, linked_signer=linked_signer 54 | ) 55 | assert isinstance(opts_linked_signer_from_account.linked_signer, LocalAccount) 56 | assert opts_linked_signer_from_account.linked_signer.key.hex() == private_keys[1] 57 | 58 | 59 | def test_create_client_all_opts( 60 | url: str, 61 | private_keys: list[str], 62 | chain_id: int, 63 | endpoint_addr: str, 64 | book_addrs: list[str], 65 | ): 66 | client_from_dict = TriggerClient( 67 | { 68 | "url": url, 69 | "signer": private_keys[0], 70 | "linked_signer": private_keys[1], 71 | "chain_id": chain_id, 72 | "endpoint_addr": endpoint_addr, 73 | "book_addrs": book_addrs, 74 | } 75 | ) 76 | client_from_opts = TriggerClient( 77 | opts=TriggerClientOpts( 78 | url=url, 79 | signer=private_keys[0], 80 | linked_signer=private_keys[1], 81 | chain_id=chain_id, 82 | endpoint_addr=endpoint_addr, 83 | book_addrs=book_addrs, 84 | ) 85 | ) 86 | 87 | assert ( 88 | client_from_dict.signer 89 | == client_from_opts.signer 90 | == Account.from_key(private_keys[0]) 91 | ) 92 | assert ( 93 | client_from_dict.linked_signer 94 | == client_from_opts.linked_signer 95 | == Account.from_key(private_keys[1]) 96 | ) 97 | assert client_from_dict.chain_id == client_from_opts.chain_id == chain_id 98 | assert ( 99 | client_from_dict.endpoint_addr 100 | == client_from_opts.endpoint_addr 101 | == endpoint_addr 102 | ) 103 | assert client_from_dict.book_addrs == client_from_opts.book_addrs == book_addrs 104 | -------------------------------------------------------------------------------- /tests/trigger_client/test_execute_client.py: -------------------------------------------------------------------------------- 1 | from eth_account import Account 2 | from vertex_protocol.trigger_client import TriggerClient 3 | import pytest 4 | 5 | 6 | def test_execute_client_properties( 7 | url: str, 8 | chain_id: int, 9 | endpoint_addr: str, 10 | book_addrs: list[str], 11 | private_keys: list[str], 12 | ): 13 | trigger_client = TriggerClient(opts={"url": url}) 14 | with pytest.raises(AttributeError, match="Endpoint address not set"): 15 | trigger_client.endpoint_addr 16 | 17 | with pytest.raises(AttributeError, match="Book addresses are not set"): 18 | trigger_client.book_addrs 19 | 20 | with pytest.raises(AttributeError, match="Chain ID is not set"): 21 | trigger_client.chain_id 22 | 23 | with pytest.raises(AttributeError, match="Signer is not set"): 24 | trigger_client.signer 25 | 26 | with pytest.raises(AttributeError, match="Signer is not set"): 27 | trigger_client.linked_signer 28 | 29 | trigger_client.endpoint_addr = endpoint_addr 30 | trigger_client.book_addrs = book_addrs 31 | trigger_client.chain_id = chain_id 32 | 33 | signer = Account.from_key(private_keys[0]) 34 | linked_signer = Account.from_key(private_keys[1]) 35 | 36 | assert signer != linked_signer 37 | 38 | with pytest.raises( 39 | AttributeError, 40 | match="Must set a `signer` first before setting `linked_signer`.", 41 | ): 42 | trigger_client.linked_signer = linked_signer 43 | 44 | trigger_client.signer = signer 45 | 46 | assert trigger_client.signer == trigger_client.linked_signer 47 | 48 | trigger_client.linked_signer = linked_signer 49 | 50 | assert trigger_client.endpoint_addr == endpoint_addr 51 | assert trigger_client.book_addrs == book_addrs 52 | assert trigger_client.chain_id == chain_id 53 | assert trigger_client.signer == signer 54 | assert trigger_client.linked_signer == linked_signer 55 | 56 | trigger_client.linked_signer = None 57 | 58 | assert trigger_client.linked_signer == trigger_client.signer 59 | -------------------------------------------------------------------------------- /tests/utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertex-protocol/vertex-python-sdk/86ca107b5d25ce1f666c7a1fd4a6d2e87a565ebe/tests/utils/__init__.py -------------------------------------------------------------------------------- /tests/utils/test_math.py: -------------------------------------------------------------------------------- 1 | from vertex_protocol.utils.math import to_x18 2 | 3 | 4 | def test_to_x18(): 5 | assert to_x18(10.15) == 10150000000000000000 6 | assert to_x18(10) == 10000000000000000000 7 | assert to_x18(2000.150) == 2000150000000000000000 8 | -------------------------------------------------------------------------------- /tests/vertex_client/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertex-protocol/vertex-python-sdk/86ca107b5d25ce1f666c7a1fd4a6d2e87a565ebe/tests/vertex_client/__init__.py -------------------------------------------------------------------------------- /tests/vertex_client/test_spot_api.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import MagicMock 2 | 3 | from vertex_protocol.client import VertexClient 4 | from vertex_protocol.contracts.types import VertexExecuteType 5 | 6 | from vertex_protocol.engine_client.types.execute import ( 7 | WithdrawCollateralParams, 8 | ) 9 | from vertex_protocol.utils.bytes32 import subaccount_to_bytes32 10 | 11 | 12 | def test_withdraw( 13 | vertex_client: VertexClient, 14 | senders: list[str], 15 | mock_execute_response: MagicMock, 16 | mock_tx_nonce: MagicMock, 17 | ): 18 | params = WithdrawCollateralParams( 19 | sender=senders[0], 20 | productId=1, 21 | amount=10, 22 | nonce=2, 23 | ) 24 | res = vertex_client.spot.withdraw(params) 25 | params.sender = subaccount_to_bytes32(senders[0]) 26 | signature = vertex_client.context.engine_client.sign( 27 | VertexExecuteType.WITHDRAW_COLLATERAL, 28 | params.dict(), 29 | vertex_client.context.engine_client.endpoint_addr, 30 | vertex_client.context.engine_client.chain_id, 31 | vertex_client.context.engine_client.signer, 32 | ) 33 | assert res.req == { 34 | "withdraw_collateral": { 35 | "tx": { 36 | "sender": senders[0].lower(), 37 | "productId": 1, 38 | "amount": str(10), 39 | "nonce": str(2), 40 | }, 41 | "signature": signature, 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/vertex_client/test_subaccount_api.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import MagicMock 2 | 3 | from vertex_protocol.client import VertexClient 4 | from vertex_protocol.contracts.types import VertexExecuteType 5 | 6 | from vertex_protocol.engine_client.types.execute import ( 7 | LinkSignerParams, 8 | LiquidateSubaccountParams, 9 | ) 10 | from vertex_protocol.utils.bytes32 import subaccount_to_bytes32 11 | 12 | 13 | def test_liquidate_subaccount( 14 | vertex_client: VertexClient, 15 | senders: list[str], 16 | mock_execute_response: MagicMock, 17 | mock_tx_nonce: MagicMock, 18 | ): 19 | params = LiquidateSubaccountParams( 20 | sender=senders[0], 21 | liquidatee=senders[1], 22 | productId=1, 23 | isEncodedSpread=False, 24 | amount=10, 25 | nonce=2, 26 | ) 27 | res = vertex_client.subaccount.liquidate_subaccount(params) 28 | params.sender = subaccount_to_bytes32(senders[0]) 29 | signature = vertex_client.context.engine_client.sign( 30 | VertexExecuteType.LIQUIDATE_SUBACCOUNT, 31 | params.dict(), 32 | vertex_client.context.engine_client.endpoint_addr, 33 | vertex_client.context.engine_client.chain_id, 34 | vertex_client.context.engine_client.signer, 35 | ) 36 | assert res.req == { 37 | "liquidate_subaccount": { 38 | "tx": { 39 | "sender": senders[0].lower(), 40 | "liquidatee": senders[1].lower(), 41 | "productId": 1, 42 | "isEncodedSpread": False, 43 | "amount": str(10), 44 | "nonce": str(2), 45 | }, 46 | "signature": signature, 47 | } 48 | } 49 | 50 | 51 | def test_link_signer( 52 | vertex_client: VertexClient, 53 | senders: list[str], 54 | mock_execute_response: MagicMock, 55 | mock_tx_nonce: MagicMock, 56 | ): 57 | params = LinkSignerParams( 58 | sender=senders[0], 59 | signer=senders[1], 60 | nonce=2, 61 | ) 62 | res = vertex_client.subaccount.link_signer(params) 63 | params.sender = subaccount_to_bytes32(senders[0]) 64 | signature = vertex_client.context.engine_client.sign( 65 | VertexExecuteType.LINK_SIGNER, 66 | params.dict(), 67 | vertex_client.context.engine_client.endpoint_addr, 68 | vertex_client.context.engine_client.chain_id, 69 | vertex_client.context.engine_client.signer, 70 | ) 71 | assert res.req == { 72 | "link_signer": { 73 | "tx": { 74 | "sender": senders[0].lower(), 75 | "signer": senders[1].lower(), 76 | "nonce": str(2), 77 | }, 78 | "signature": signature, 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /vertex_protocol/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vertex-protocol/vertex-python-sdk/86ca107b5d25ce1f666c7a1fd4a6d2e87a565ebe/vertex_protocol/__init__.py -------------------------------------------------------------------------------- /vertex_protocol/client/apis/__init__.py: -------------------------------------------------------------------------------- 1 | from vertex_protocol.client.apis.base import * 2 | from vertex_protocol.client.apis.market import * 3 | from vertex_protocol.client.apis.perp import * 4 | from vertex_protocol.client.apis.spot import * 5 | from vertex_protocol.client.apis.spot.base import * 6 | from vertex_protocol.client.apis.subaccount import * 7 | from vertex_protocol.client.apis.rewards import * 8 | 9 | __all__ = [ 10 | "VertexBaseAPI", 11 | "MarketAPI", 12 | "MarketExecuteAPI", 13 | "MarketQueryAPI", 14 | "SpotAPI", 15 | "BaseSpotAPI", 16 | "SpotExecuteAPI", 17 | "SpotQueryAPI", 18 | "SubaccountAPI", 19 | "SubaccountExecuteAPI", 20 | "SubaccountQueryAPI", 21 | "PerpAPI", 22 | "PerpQueryAPI", 23 | "RewardsAPI", 24 | "RewardsExecuteAPI", 25 | "RewardsQueryAPI", 26 | ] 27 | -------------------------------------------------------------------------------- /vertex_protocol/client/apis/base.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from vertex_protocol.client.context import VertexClientContext 3 | from vertex_protocol.utils.exceptions import MissingSignerException 4 | from eth_account.signers.local import LocalAccount 5 | 6 | 7 | class VertexBaseAPI: 8 | """ 9 | The base class for all Vertex API classes, providing the foundation for API-specific classes in the Vertex client. 10 | 11 | VertexBaseAPI serves as a foundation for the hierarchical structure of the Vertex API classes. This structure allows for better 12 | organization and separation of concerns, with each API-specific subclass handling a different aspect of the Vertex client's functionality. 13 | 14 | Attributes: 15 | context (VertexClientContext): The context in which the API operates, providing access to the client's state and services. 16 | 17 | Note: 18 | This class is not meant to be used directly. It provides base functionality for other API classes in the Vertex client. 19 | """ 20 | 21 | context: VertexClientContext 22 | 23 | def __init__(self, context: VertexClientContext): 24 | """ 25 | Initialize an instance of VertexBaseAPI. 26 | 27 | VertexBaseAPI requires a context during instantiation, which should be an instance of VertexClientContext. This context 28 | provides access to the state and services of the Vertex client and allows the API to interact with these. 29 | 30 | Args: 31 | context (VertexClientContext): The context in which this API operates. Provides access to the state and services 32 | of the Vertex client. 33 | """ 34 | self.context = context 35 | 36 | def _get_signer(self, signer: Optional[LocalAccount]) -> LocalAccount: 37 | signer = signer if signer else self.context.signer 38 | if not signer: 39 | raise MissingSignerException( 40 | "A signer must be provided or set via the context." 41 | ) 42 | return signer 43 | -------------------------------------------------------------------------------- /vertex_protocol/client/apis/market/__init__.py: -------------------------------------------------------------------------------- 1 | from vertex_protocol.client.apis.market.execute import MarketExecuteAPI 2 | from vertex_protocol.client.apis.market.query import MarketQueryAPI 3 | 4 | 5 | class MarketAPI(MarketExecuteAPI, MarketQueryAPI): 6 | """ 7 | A unified interface for market operations in the Vertex Protocol. 8 | 9 | This class combines functionalities from both MarketExecuteAPI and MarketQueryAPI 10 | into a single interface, providing a simpler and more consistent way to perform market operations. 11 | It allows for both query (data retrieval) and execution (transaction) operations for market. 12 | 13 | Inheritance: 14 | MarketExecuteAPI: This provides functionalities to execute various operations related to market. 15 | These include actions like placing an order, canceling an order, minting and burning LP tokens. 16 | 17 | MarketQueryAPI: This provides functionalities to retrieve various kinds of information related to market. 18 | These include operations like retrieving order books, historical orders, market matches, and others. 19 | 20 | Attributes and Methods: Inherited from MarketExecuteAPI and MarketQueryAPI. 21 | """ 22 | 23 | pass 24 | -------------------------------------------------------------------------------- /vertex_protocol/client/apis/perp/__init__.py: -------------------------------------------------------------------------------- 1 | from vertex_protocol.client.apis.perp.query import PerpQueryAPI 2 | 3 | 4 | class PerpAPI(PerpQueryAPI): 5 | """ 6 | A unified interface for Perpetual (Perp) operations in the Vertex Protocol. 7 | 8 | This class extends functionalities from PerpQueryAPI into a single interface, providing a simpler and more consistent way to perform Perp operations. 9 | Currently, it allows for querying (data retrieval) operations for Perp products. 10 | 11 | Inheritance: 12 | PerpQueryAPI: This provides functionalities to retrieve various kinds of information related to Perp products. 13 | These include operations like retrieving the latest index and mark price for a specific Perp product. 14 | 15 | Attributes and Methods: Inherited from PerpQueryAPI. 16 | """ 17 | 18 | pass 19 | -------------------------------------------------------------------------------- /vertex_protocol/client/apis/perp/query.py: -------------------------------------------------------------------------------- 1 | from vertex_protocol.client.apis.base import VertexBaseAPI 2 | from vertex_protocol.indexer_client.types.query import IndexerPerpPricesData 3 | 4 | 5 | class PerpQueryAPI(VertexBaseAPI): 6 | """ 7 | Provides functionalities for querying data related to Perpetual (Perp) products in the Vertex Protocol. 8 | 9 | Inherits from VertexBaseAPI, which provides a basic context setup for accessing Vertex. 10 | This class extends the base class to provide specific functionalities for querying data related to Perp products. 11 | 12 | Attributes: 13 | context (VertexClientContext): Provides connectivity details for accessing Vertex APIs. 14 | """ 15 | 16 | def get_prices(self, product_id: int) -> IndexerPerpPricesData: 17 | """ 18 | Retrieves the latest index and mark price for a specific perp product from the indexer. 19 | 20 | Args: 21 | product_id (int): The identifier for the perp product. 22 | 23 | Returns: 24 | IndexerPerpPricesData: An object containing the latest index and mark price for the specified product. 25 | - product_id (int): The identifier for the perp product. 26 | - index_price_x18 (str): The latest index price for the product, scaled by 1e18. 27 | - mark_price_x18 (str): The latest mark price for the product, scaled by 1e18. 28 | - update_time (str): The timestamp of the last price update. 29 | """ 30 | return self.context.indexer_client.get_perp_prices(product_id) 31 | -------------------------------------------------------------------------------- /vertex_protocol/client/apis/rewards/__init__.py: -------------------------------------------------------------------------------- 1 | from vertex_protocol.client.apis.rewards.execute import RewardsExecuteAPI 2 | from vertex_protocol.client.apis.rewards.query import RewardsQueryAPI 3 | 4 | 5 | class RewardsAPI(RewardsExecuteAPI, RewardsQueryAPI): 6 | pass 7 | -------------------------------------------------------------------------------- /vertex_protocol/client/apis/rewards/execute.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from vertex_protocol.contracts.types import ( 3 | ClaimFoundationRewardsContractParams, 4 | ClaimFoundationRewardsProofStruct, 5 | ClaimVrtxContractParams, 6 | ClaimVrtxParams, 7 | ) 8 | from vertex_protocol.client.apis.base import VertexBaseAPI 9 | from eth_account.signers.local import LocalAccount 10 | 11 | from vertex_protocol.utils.exceptions import InvalidVrtxClaimParams 12 | 13 | 14 | class RewardsExecuteAPI(VertexBaseAPI): 15 | def _validate_claim_params(self, params: ClaimVrtxParams): 16 | p = ClaimVrtxParams.parse_obj(params) 17 | if p.amount is None and p.claim_all is None: 18 | raise InvalidVrtxClaimParams() 19 | 20 | def claim_vrtx( 21 | self, params: ClaimVrtxParams, signer: Optional[LocalAccount] = None 22 | ) -> str: 23 | self._validate_claim_params(params) 24 | signer = self._get_signer(signer) 25 | claim_params = self._get_claim_vrtx_contract_params(params, signer) 26 | return self.context.contracts.claim_vrtx( 27 | claim_params.epoch, 28 | claim_params.amount_to_claim, 29 | claim_params.total_claimable_amount, 30 | claim_params.merkle_proof, 31 | signer, 32 | ) 33 | 34 | def claim_and_stake_vrtx( 35 | self, params: ClaimVrtxParams, signer: Optional[LocalAccount] = None 36 | ) -> str: 37 | self._validate_claim_params(params) 38 | signer = self._get_signer(signer) 39 | claim_params = self._get_claim_vrtx_contract_params(params, signer) 40 | return self.context.contracts.claim_and_stake_vrtx( 41 | claim_params.epoch, 42 | claim_params.amount_to_claim, 43 | claim_params.total_claimable_amount, 44 | claim_params.merkle_proof, 45 | signer, 46 | ) 47 | 48 | def stake_vrtx(self, amount: int, signer: Optional[LocalAccount] = None) -> str: 49 | signer = self._get_signer(signer) 50 | return self.context.contracts.stake_vrtx(amount, signer) 51 | 52 | def unstake_vrtx(self, amount: int, signer: Optional[LocalAccount] = None) -> str: 53 | signer = self._get_signer(signer) 54 | return self.context.contracts.unstake_vrtx(amount, signer) 55 | 56 | def withdraw_unstaked_vrtx(self, signer: Optional[LocalAccount] = None): 57 | signer = self._get_signer(signer) 58 | return self.context.contracts.withdraw_unstaked_vrtx(signer) 59 | 60 | def claim_usdc_rewards(self, signer: Optional[LocalAccount] = None): 61 | signer = self._get_signer(signer) 62 | return self.context.contracts.claim_usdc_rewards(signer) 63 | 64 | def claim_and_stake_usdc_rewards(self, signer: Optional[LocalAccount] = None): 65 | signer = self._get_signer(signer) 66 | return self.context.contracts.claim_and_stake_usdc_rewards(signer) 67 | 68 | def claim_foundation_rewards(self, signer: Optional[LocalAccount] = None): 69 | """ 70 | Claims all available foundation rewards. Foundation rewards are tokens associated with the chain. For example, ARB on Arbitrum. 71 | """ 72 | signer = self._get_signer(signer) 73 | claim_params = self._get_claim_foundation_rewards_contract_params(signer) 74 | return self.context.contracts.claim_foundation_rewards( 75 | claim_params.claim_proofs, signer 76 | ) 77 | 78 | def _get_claim_vrtx_contract_params( 79 | self, params: ClaimVrtxParams, signer: LocalAccount 80 | ) -> ClaimVrtxContractParams: 81 | epoch_merkle_proofs = self.context.indexer_client.get_vrtx_merkle_proofs( 82 | signer.address 83 | ).merkle_proofs[params.epoch] 84 | total_claimable_amount = int(epoch_merkle_proofs.total_amount) 85 | if params.amount is not None: 86 | amount_to_claim = params.amount 87 | else: 88 | assert self.context.contracts.vrtx_airdrop is not None 89 | amount_claimed = self.context.contracts.vrtx_airdrop.functions.getClaimed( 90 | signer.address 91 | ).call() 92 | amount_to_claim = total_claimable_amount - amount_claimed[params.epoch] 93 | return ClaimVrtxContractParams( 94 | epoch=params.epoch, 95 | amount_to_claim=amount_to_claim, 96 | total_claimable_amount=total_claimable_amount, 97 | merkle_proof=epoch_merkle_proofs.proof, 98 | ) 99 | 100 | def _get_claim_foundation_rewards_contract_params( 101 | self, signer: LocalAccount 102 | ) -> ClaimFoundationRewardsContractParams: 103 | assert self.context.contracts.foundation_rewards_airdrop is not None 104 | claimed = ( 105 | self.context.contracts.foundation_rewards_airdrop.functions.getClaimed( 106 | signer.address 107 | ).call() 108 | ) 109 | merkle_proofs = ( 110 | self.context.indexer_client.get_foundation_rewards_merkle_proofs( 111 | signer.address 112 | ) 113 | ) 114 | claim_proofs = [] 115 | 116 | for idx, proof in enumerate(merkle_proofs.merkle_proofs): 117 | if idx == 0: 118 | # week 0 is invalid 119 | continue 120 | 121 | total_amount = int(proof.total_amount) 122 | 123 | # There's no partial claim, so find weeks where there's a claimable amount and amt claimed is zero 124 | if total_amount > 0 and int(claimed[idx]) == 0: 125 | claim_proofs.append( 126 | ClaimFoundationRewardsProofStruct( 127 | totalAmount=total_amount, week=idx, proof=proof.proof 128 | ) 129 | ) 130 | 131 | return ClaimFoundationRewardsContractParams(claim_proofs=claim_proofs) 132 | -------------------------------------------------------------------------------- /vertex_protocol/client/apis/rewards/query.py: -------------------------------------------------------------------------------- 1 | from vertex_protocol.client.apis.base import VertexBaseAPI 2 | 3 | 4 | class RewardsQueryAPI(VertexBaseAPI): 5 | def get_claim_and_stake_estimated_vrtx(self, wallet: str) -> int: 6 | """ 7 | Estimates the amount of USDC -> VRTX swap when claiming + staking USDC rewards 8 | """ 9 | assert self.context.contracts.vrtx_staking is not None 10 | return self.context.contracts.vrtx_staking.functions.getEstimatedVrtxToStake( 11 | wallet 12 | ).call() 13 | -------------------------------------------------------------------------------- /vertex_protocol/client/apis/spot/__init__.py: -------------------------------------------------------------------------------- 1 | from vertex_protocol.client.apis.spot.execute import SpotExecuteAPI 2 | from vertex_protocol.client.apis.spot.query import SpotQueryAPI 3 | 4 | 5 | class SpotAPI(SpotExecuteAPI, SpotQueryAPI): 6 | """ 7 | A unified interface for spot operations in the Vertex Protocol. 8 | 9 | This class combines functionalities from both SpotExecuteAPI and SpotQueryAPI 10 | into a single interface, providing a simpler and more consistent way to perform spot operations. 11 | It allows for both query (data retrieval) and execution (transaction) operations for spot products. 12 | 13 | Inheritance: 14 | SpotExecuteAPI: This provides functionalities to execute various operations related to spot products, 15 | such as depositing a specified amount into a spot product. 16 | 17 | SpotQueryAPI: This provides functionalities to retrieve various kinds of information related to spot products, 18 | such as getting the wallet token balance of a given spot product. 19 | 20 | Attributes and Methods: Inherited from SpotExecuteAPI and SpotQueryAPI. 21 | """ 22 | 23 | pass 24 | -------------------------------------------------------------------------------- /vertex_protocol/client/apis/spot/base.py: -------------------------------------------------------------------------------- 1 | from web3.contract import Contract 2 | from vertex_protocol.client.apis.base import VertexBaseAPI 3 | 4 | 5 | class BaseSpotAPI(VertexBaseAPI): 6 | """ 7 | Base class for Spot operations in the Vertex Protocol. 8 | 9 | This class provides basic functionality for retrieving product-specific information 10 | from the spot market of the Vertex Protocol, such as the associated ERC20 token contract for a given spot product. 11 | 12 | Attributes: 13 | context (VertexClientContext): Provides connectivity details for accessing Vertex APIs. 14 | 15 | Methods: 16 | get_token_contract_for_product: Retrieves the associated ERC20 token contract for a given spot product. 17 | """ 18 | 19 | def get_token_contract_for_product(self, product_id: int) -> Contract: 20 | """ 21 | Retrieves the associated ERC20 token contract for a given spot product. 22 | 23 | Args: 24 | product_id (int): The identifier for the spot product. 25 | 26 | Returns: 27 | Contract: The associated ERC20 token contract for the specified spot product. 28 | 29 | Raises: 30 | InvalidProductId: If the provided product ID is not valid. 31 | """ 32 | return self.context.contracts.get_token_contract_for_product(product_id) 33 | -------------------------------------------------------------------------------- /vertex_protocol/client/apis/spot/execute.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from vertex_protocol.contracts.types import DepositCollateralParams 3 | from eth_account.signers.local import LocalAccount 4 | from vertex_protocol.client.apis.spot.base import BaseSpotAPI 5 | from vertex_protocol.engine_client.types.execute import ( 6 | ExecuteResponse, 7 | WithdrawCollateralParams, 8 | ) 9 | from vertex_protocol.utils.exceptions import MissingSignerException 10 | 11 | 12 | class SpotExecuteAPI(BaseSpotAPI): 13 | """ 14 | Class providing execution operations for the spot market in the Vertex Protocol. 15 | 16 | This class provides functionality for executing transactions related to spot products, 17 | such as depositing a specified amount into a spot product. 18 | 19 | Inheritance: 20 | BaseSpotAPI: Base class for Spot operations. Inherits connectivity context and base functionalities. 21 | """ 22 | 23 | def deposit( 24 | self, params: DepositCollateralParams, signer: Optional[LocalAccount] = None 25 | ) -> str: 26 | """ 27 | Executes the operation of depositing a specified amount into a spot product. 28 | 29 | Args: 30 | params (DepositCollateralParams): Parameters required for depositing collateral. 31 | 32 | signer (LocalAccount, optional): The account that will sign the deposit transaction. If no signer is provided, the signer set in the client context will be used. 33 | 34 | Raises: 35 | MissingSignerException: Raised when there is no signer provided and no signer set in the client context. 36 | 37 | Returns: 38 | str: The deposit collateral transaction hash. 39 | """ 40 | signer = signer if signer else self.context.signer 41 | if not signer: 42 | raise MissingSignerException( 43 | "A signer must be provided or set via the context." 44 | ) 45 | return self.context.contracts.deposit_collateral(params, signer) 46 | 47 | def withdraw(self, params: WithdrawCollateralParams) -> ExecuteResponse: 48 | """ 49 | Executes a withdrawal for the specified spot product via the off-chain engine. 50 | 51 | Args: 52 | params (WithdrawCollateralParams): Parameters needed to execute the withdrawal. 53 | 54 | Returns: 55 | ExecuteResponse: The response from the engine execution. 56 | 57 | Raises: 58 | Exception: If there is an error during the execution or the response status is not "success". 59 | """ 60 | return self.context.engine_client.withdraw_collateral(params) 61 | 62 | def approve_allowance( 63 | self, product_id: int, amount: int, signer: Optional[LocalAccount] = None 64 | ) -> str: 65 | """ 66 | Approves an allowance for a certain amount of tokens for a spot product. 67 | 68 | Args: 69 | product_id (int): The identifier of the spot product for which to approve an allowance. 70 | 71 | amount (int): The amount of the tokens to be approved. 72 | 73 | signer (LocalAccount, optional): The account that will sign the approval transaction. If no signer is provided, the signer set in the client context will be used. 74 | 75 | Returns: 76 | str: The approve allowance transaction hash. 77 | 78 | Raises: 79 | MissingSignerException: Raised when there is no signer provided and no signer set in the client context. 80 | InvalidProductId: If the provided product ID is not valid. 81 | """ 82 | signer = signer if signer else self.context.signer 83 | if not signer: 84 | raise MissingSignerException( 85 | "A signer must be provided or set via the context." 86 | ) 87 | token = self.get_token_contract_for_product(product_id) 88 | return self.context.contracts.approve_allowance(token, amount, signer) 89 | 90 | def _mint_mock_erc20( 91 | self, product_id: int, amount: int, signer: Optional[LocalAccount] = None 92 | ): 93 | """ 94 | Mints a specified amount of mock ERC20 tokens for testing purposes. 95 | 96 | Args: 97 | product_id (int): The identifier for the spot product. 98 | 99 | amount (int): The amount of mock ERC20 tokens to mint. 100 | 101 | signer (LocalAccount, optional): The account that will sign the mint transaction. 102 | If no signer is provided, the signer set in the client context will be used. 103 | 104 | Returns: 105 | str: The mock ERC20 mint transaction hash. 106 | 107 | Raises: 108 | MissingSignerException: Raised when there is no signer provided and no signer set in the client context. 109 | InvalidProductId: If the provided product ID is not valid. 110 | """ 111 | signer = signer if signer else self.context.signer 112 | if not signer: 113 | raise MissingSignerException( 114 | "A signer must be provided or set via the context." 115 | ) 116 | token = self.get_token_contract_for_product(product_id) 117 | return self.context.contracts._mint_mock_erc20(token, amount, signer) 118 | -------------------------------------------------------------------------------- /vertex_protocol/client/apis/spot/query.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from vertex_protocol.client.apis.spot.base import BaseSpotAPI 3 | from vertex_protocol.engine_client.types.query import MaxWithdrawableData 4 | from vertex_protocol.utils.math import from_pow_10 5 | 6 | 7 | class SpotQueryAPI(BaseSpotAPI): 8 | """ 9 | Class providing querying operations for the spot market in the Vertex Protocol. 10 | 11 | This class allows for retrieval of various kinds of information related to spot products, 12 | such as getting wallet token balance of a given spot product. 13 | 14 | Inheritance: 15 | BaseSpotAPI: Base class for Spot operations. Inherits connectivity context and base functionalities. 16 | """ 17 | 18 | def get_max_withdrawable( 19 | self, product_id: int, sender: str, spot_leverage: Optional[bool] = None 20 | ) -> MaxWithdrawableData: 21 | """ 22 | Retrieves the estimated maximum withdrawable amount for a provided spot product. 23 | 24 | Args: 25 | product_id (int): The identifier for the spot product. 26 | 27 | sender (str): The address and subaccount identifier in a bytes32 hex string. 28 | 29 | spot_leverage (Optional[bool]): If False, calculates max amount without considering leverage. Defaults to True. 30 | 31 | Returns: 32 | MaxWithdrawableData: The maximum withdrawable amount for the spot product. 33 | """ 34 | return self.context.engine_client.get_max_withdrawable( 35 | product_id, sender, spot_leverage 36 | ) 37 | 38 | def get_token_wallet_balance(self, product_id: int, address: str) -> float: 39 | """ 40 | Retrieves the balance of a specific token in the user's wallet (i.e. not in a Vertex subaccount) 41 | 42 | Args: 43 | product_id (int): Identifier for the spot product. 44 | 45 | address (str): User's wallet address. 46 | 47 | Returns: 48 | float: The balance of the token in the user's wallet in decimal form. 49 | 50 | Raises: 51 | InvalidProductId: If the provided product ID is not valid. 52 | """ 53 | token = self.get_token_contract_for_product(product_id) 54 | decimals = token.functions.decimals().call() 55 | return from_pow_10(token.functions.balanceOf(address).call(), decimals) 56 | 57 | def get_token_allowance(self, product_id: int, address: str) -> float: 58 | """ 59 | Retrieves the current token allowance of a specified spot product. 60 | 61 | Args: 62 | product_id (int): Identifier for the spot product. 63 | 64 | address (str): The user's wallet address. 65 | 66 | Returns: 67 | float: The current token allowance of the user's wallet address to the associated spot product. 68 | 69 | Raises: 70 | InvalidProductId: If the provided product ID is not valid. 71 | """ 72 | token = self.get_token_contract_for_product(product_id) 73 | decimals = token.functions.decimals().call() 74 | return from_pow_10( 75 | token.functions.allowance( 76 | address, self.context.contracts.endpoint.address 77 | ).call(), 78 | decimals, 79 | ) 80 | -------------------------------------------------------------------------------- /vertex_protocol/client/apis/subaccount/__init__.py: -------------------------------------------------------------------------------- 1 | from vertex_protocol.client.apis.subaccount.execute import SubaccountExecuteAPI 2 | from vertex_protocol.client.apis.subaccount.query import SubaccountQueryAPI 3 | 4 | 5 | class SubaccountAPI(SubaccountExecuteAPI, SubaccountQueryAPI): 6 | """ 7 | A unified interface for subaccount operations in the Vertex Protocol. 8 | 9 | This class combines functionalities from both SubaccountExecuteAPI and SubaccountQueryAPI 10 | into a single interface, providing a simpler and more consistent way to perform subaccount operations. 11 | It allows for both query (data retrieval) and execution (transaction) operations for subaccounts. 12 | 13 | Inheritance: 14 | SubaccountExecuteAPI: This provides functionalities to execute various operations related to subaccounts. 15 | These include actions like liquidating a subaccount or linking a signer to a subaccount. 16 | 17 | SubaccountQueryAPI: This provides functionalities to retrieve various kinds of information related to subaccounts. 18 | These include operations like retrieving a summary of a subaccount's state, retrieving the fee rates associated with a 19 | subaccount, querying token rewards for a wallet, and getting linked signer rate limits for a subaccount. 20 | 21 | Attributes and Methods: Inherited from SubaccountExecuteAPI and SubaccountQueryAPI. 22 | """ 23 | 24 | pass 25 | -------------------------------------------------------------------------------- /vertex_protocol/client/apis/subaccount/execute.py: -------------------------------------------------------------------------------- 1 | from vertex_protocol.client.apis.base import VertexBaseAPI 2 | from vertex_protocol.engine_client.types.execute import ( 3 | ExecuteResponse, 4 | LinkSignerParams, 5 | LiquidateSubaccountParams, 6 | ) 7 | 8 | 9 | class SubaccountExecuteAPI(VertexBaseAPI): 10 | """ 11 | Provides functionalities for executing operations related to subaccounts in the Vertex Protocol. 12 | 13 | Inherits from VertexBaseAPI, which provides a basic context setup for accessing Vertex. 14 | This class extends the base class to provide specific functionalities for executing actions related to subaccounts. 15 | 16 | The provided methods include: 17 | - `liquidate_subaccount`: Performs the liquidation of a subaccount. 18 | - `link_signer`: Links a signer to a subaccount, granting them transaction signing permissions. 19 | 20 | Attributes: 21 | context (VertexClientContext): Provides connectivity details for accessing Vertex APIs. 22 | """ 23 | 24 | def liquidate_subaccount( 25 | self, params: LiquidateSubaccountParams 26 | ) -> ExecuteResponse: 27 | """ 28 | Liquidates a subaccount through the engine. 29 | 30 | Args: 31 | params (LiquidateSubaccountParams): Parameters for liquidating the subaccount. 32 | 33 | Returns: 34 | ExecuteResponse: Execution response from the engine. 35 | 36 | Raises: 37 | Exception: If there is an error during the execution or the response status is not "success". 38 | """ 39 | return self.context.engine_client.liquidate_subaccount(params) 40 | 41 | def link_signer(self, params: LinkSignerParams) -> ExecuteResponse: 42 | """ 43 | Links a signer to a subaccount to allow them to sign transactions on behalf of the subaccount. 44 | 45 | Args: 46 | params (LinkSignerParams): Parameters for linking a signer to a subaccount. 47 | 48 | Returns: 49 | ExecuteResponse: Execution response from the engine. 50 | 51 | Raises: 52 | Exception: If there is an error during the execution or the response status is not "success". 53 | """ 54 | return self.context.engine_client.link_signer(params) 55 | -------------------------------------------------------------------------------- /vertex_protocol/client/context.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from dataclasses import dataclass 3 | from typing import Optional 4 | from eth_account import Account 5 | 6 | from pydantic import AnyUrl, BaseModel 7 | from vertex_protocol.contracts import VertexContracts, VertexContractsContext 8 | from eth_account.signers.local import LocalAccount 9 | from vertex_protocol.engine_client import EngineClient 10 | from vertex_protocol.engine_client.types import EngineClientOpts 11 | from vertex_protocol.utils.backend import Signer 12 | from vertex_protocol.indexer_client import IndexerClient 13 | from vertex_protocol.trigger_client import TriggerClient 14 | from vertex_protocol.indexer_client.types import IndexerClientOpts 15 | from vertex_protocol.trigger_client.types import TriggerClientOpts 16 | 17 | 18 | @dataclass 19 | class VertexClientContext: 20 | """ 21 | Context required to use the Vertex client. 22 | """ 23 | 24 | signer: Optional[LocalAccount] 25 | engine_client: EngineClient 26 | indexer_client: IndexerClient 27 | trigger_client: Optional[TriggerClient] 28 | contracts: VertexContracts 29 | 30 | 31 | class VertexClientContextOpts(BaseModel): 32 | contracts_context: Optional[VertexContractsContext] 33 | rpc_node_url: Optional[AnyUrl] 34 | engine_endpoint_url: Optional[AnyUrl] 35 | indexer_endpoint_url: Optional[AnyUrl] 36 | trigger_endpoint_url: Optional[AnyUrl] 37 | 38 | 39 | def create_vertex_client_context( 40 | opts: VertexClientContextOpts, signer: Optional[Signer] = None 41 | ) -> VertexClientContext: 42 | """ 43 | Initializes a VertexClientContext instance with the provided signer and options. 44 | 45 | Args: 46 | opts (VertexClientContextOpts): Options including endpoints for the engine and indexer clients. 47 | 48 | signer (Signer, optional): An instance of LocalAccount or a private key string for signing transactions. 49 | 50 | Returns: 51 | VertexClientContext: The initialized Vertex client context. 52 | 53 | Note: 54 | This helper attempts to fully set up the engine, indexer and trigger clients, including the necessary verifying contracts 55 | to correctly sign executes. If this step fails, it is skipped and can be set up later, while logging the error. 56 | """ 57 | assert opts.contracts_context is not None, "Missing contracts context" 58 | assert opts.rpc_node_url is not None, "Missing RPC node URL" 59 | assert opts.engine_endpoint_url is not None, "Missing engine endpoint URL" 60 | assert opts.indexer_endpoint_url is not None, "Missing indexer endpoint URL" 61 | 62 | signer = Account.from_key(signer) if isinstance(signer, str) else signer 63 | engine_client = EngineClient( 64 | EngineClientOpts(url=opts.engine_endpoint_url, signer=signer) 65 | ) 66 | trigger_client = None 67 | try: 68 | contracts = engine_client.get_contracts() 69 | engine_client.endpoint_addr = contracts.endpoint_addr 70 | engine_client.book_addrs = contracts.book_addrs 71 | engine_client.chain_id = int(contracts.chain_id) 72 | 73 | if opts.trigger_endpoint_url is not None: 74 | trigger_client = TriggerClient( 75 | TriggerClientOpts(url=opts.trigger_endpoint_url, signer=signer) 76 | ) 77 | trigger_client.endpoint_addr = contracts.endpoint_addr 78 | trigger_client.book_addrs = contracts.book_addrs 79 | trigger_client.chain_id = int(contracts.chain_id) 80 | except Exception as e: 81 | logging.warning( 82 | f"Failed to setup engine client verifying contracts with error: {e}" 83 | ) 84 | return VertexClientContext( 85 | signer=signer, 86 | engine_client=engine_client, 87 | trigger_client=trigger_client, 88 | indexer_client=IndexerClient(IndexerClientOpts(url=opts.indexer_endpoint_url)), 89 | contracts=VertexContracts(opts.rpc_node_url, opts.contracts_context), 90 | ) 91 | -------------------------------------------------------------------------------- /vertex_protocol/contracts/abis/IERC20.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "indexed": true, 7 | "internalType": "address", 8 | "name": "owner", 9 | "type": "address" 10 | }, 11 | { 12 | "indexed": true, 13 | "internalType": "address", 14 | "name": "spender", 15 | "type": "address" 16 | }, 17 | { 18 | "indexed": false, 19 | "internalType": "uint256", 20 | "name": "value", 21 | "type": "uint256" 22 | } 23 | ], 24 | "name": "Approval", 25 | "type": "event" 26 | }, 27 | { 28 | "anonymous": false, 29 | "inputs": [ 30 | { 31 | "indexed": true, 32 | "internalType": "address", 33 | "name": "from", 34 | "type": "address" 35 | }, 36 | { 37 | "indexed": true, 38 | "internalType": "address", 39 | "name": "to", 40 | "type": "address" 41 | }, 42 | { 43 | "indexed": false, 44 | "internalType": "uint256", 45 | "name": "value", 46 | "type": "uint256" 47 | } 48 | ], 49 | "name": "Transfer", 50 | "type": "event" 51 | }, 52 | { 53 | "inputs": [ 54 | { 55 | "internalType": "address", 56 | "name": "owner", 57 | "type": "address" 58 | }, 59 | { 60 | "internalType": "address", 61 | "name": "spender", 62 | "type": "address" 63 | } 64 | ], 65 | "name": "allowance", 66 | "outputs": [ 67 | { 68 | "internalType": "uint256", 69 | "name": "", 70 | "type": "uint256" 71 | } 72 | ], 73 | "stateMutability": "view", 74 | "type": "function" 75 | }, 76 | { 77 | "inputs": [ 78 | { 79 | "internalType": "address", 80 | "name": "spender", 81 | "type": "address" 82 | }, 83 | { 84 | "internalType": "uint256", 85 | "name": "amount", 86 | "type": "uint256" 87 | } 88 | ], 89 | "name": "approve", 90 | "outputs": [ 91 | { 92 | "internalType": "bool", 93 | "name": "", 94 | "type": "bool" 95 | } 96 | ], 97 | "stateMutability": "nonpayable", 98 | "type": "function" 99 | }, 100 | { 101 | "inputs": [ 102 | { 103 | "internalType": "address", 104 | "name": "account", 105 | "type": "address" 106 | } 107 | ], 108 | "name": "balanceOf", 109 | "outputs": [ 110 | { 111 | "internalType": "uint256", 112 | "name": "", 113 | "type": "uint256" 114 | } 115 | ], 116 | "stateMutability": "view", 117 | "type": "function" 118 | }, 119 | { 120 | "inputs": [], 121 | "name": "totalSupply", 122 | "outputs": [ 123 | { 124 | "internalType": "uint256", 125 | "name": "", 126 | "type": "uint256" 127 | } 128 | ], 129 | "stateMutability": "view", 130 | "type": "function" 131 | }, 132 | { 133 | "inputs": [ 134 | { 135 | "internalType": "address", 136 | "name": "to", 137 | "type": "address" 138 | }, 139 | { 140 | "internalType": "uint256", 141 | "name": "amount", 142 | "type": "uint256" 143 | } 144 | ], 145 | "name": "transfer", 146 | "outputs": [ 147 | { 148 | "internalType": "bool", 149 | "name": "", 150 | "type": "bool" 151 | } 152 | ], 153 | "stateMutability": "nonpayable", 154 | "type": "function" 155 | }, 156 | { 157 | "inputs": [ 158 | { 159 | "internalType": "address", 160 | "name": "from", 161 | "type": "address" 162 | }, 163 | { 164 | "internalType": "address", 165 | "name": "to", 166 | "type": "address" 167 | }, 168 | { 169 | "internalType": "uint256", 170 | "name": "amount", 171 | "type": "uint256" 172 | } 173 | ], 174 | "name": "transferFrom", 175 | "outputs": [ 176 | { 177 | "internalType": "bool", 178 | "name": "", 179 | "type": "bool" 180 | } 181 | ], 182 | "stateMutability": "nonpayable", 183 | "type": "function" 184 | } 185 | ] 186 | -------------------------------------------------------------------------------- /vertex_protocol/contracts/abis/IEndpoint.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [], 5 | "name": "SubmitTransactions", 6 | "type": "event" 7 | }, 8 | { 9 | "inputs": [ 10 | { 11 | "internalType": "bytes12", 12 | "name": "subaccountName", 13 | "type": "bytes12" 14 | }, 15 | { 16 | "internalType": "uint32", 17 | "name": "productId", 18 | "type": "uint32" 19 | }, 20 | { 21 | "internalType": "uint128", 22 | "name": "amount", 23 | "type": "uint128" 24 | } 25 | ], 26 | "name": "depositCollateral", 27 | "outputs": [], 28 | "stateMutability": "nonpayable", 29 | "type": "function" 30 | }, 31 | { 32 | "inputs": [ 33 | { 34 | "internalType": "bytes32", 35 | "name": "subaccount", 36 | "type": "bytes32" 37 | }, 38 | { 39 | "internalType": "uint32", 40 | "name": "productId", 41 | "type": "uint32" 42 | }, 43 | { 44 | "internalType": "uint128", 45 | "name": "amount", 46 | "type": "uint128" 47 | }, 48 | { 49 | "internalType": "string", 50 | "name": "referralCode", 51 | "type": "string" 52 | } 53 | ], 54 | "name": "depositCollateralWithReferral", 55 | "outputs": [], 56 | "stateMutability": "nonpayable", 57 | "type": "function" 58 | }, 59 | { 60 | "inputs": [ 61 | { 62 | "internalType": "bytes12", 63 | "name": "subaccountName", 64 | "type": "bytes12" 65 | }, 66 | { 67 | "internalType": "uint32", 68 | "name": "productId", 69 | "type": "uint32" 70 | }, 71 | { 72 | "internalType": "uint128", 73 | "name": "amount", 74 | "type": "uint128" 75 | }, 76 | { 77 | "internalType": "string", 78 | "name": "referralCode", 79 | "type": "string" 80 | } 81 | ], 82 | "name": "depositCollateralWithReferral", 83 | "outputs": [], 84 | "stateMutability": "nonpayable", 85 | "type": "function" 86 | }, 87 | { 88 | "inputs": [ 89 | { 90 | "internalType": "uint32", 91 | "name": "productId", 92 | "type": "uint32" 93 | } 94 | ], 95 | "name": "getBook", 96 | "outputs": [ 97 | { 98 | "internalType": "address", 99 | "name": "", 100 | "type": "address" 101 | } 102 | ], 103 | "stateMutability": "view", 104 | "type": "function" 105 | }, 106 | { 107 | "inputs": [ 108 | { 109 | "internalType": "address", 110 | "name": "sender", 111 | "type": "address" 112 | } 113 | ], 114 | "name": "getNonce", 115 | "outputs": [ 116 | { 117 | "internalType": "uint64", 118 | "name": "", 119 | "type": "uint64" 120 | } 121 | ], 122 | "stateMutability": "view", 123 | "type": "function" 124 | }, 125 | { 126 | "inputs": [ 127 | { 128 | "internalType": "uint32", 129 | "name": "productId", 130 | "type": "uint32" 131 | } 132 | ], 133 | "name": "getPriceX18", 134 | "outputs": [ 135 | { 136 | "internalType": "int128", 137 | "name": "", 138 | "type": "int128" 139 | } 140 | ], 141 | "stateMutability": "view", 142 | "type": "function" 143 | }, 144 | { 145 | "inputs": [ 146 | { 147 | "internalType": "uint32", 148 | "name": "healthGroup", 149 | "type": "uint32" 150 | } 151 | ], 152 | "name": "getPricesX18", 153 | "outputs": [ 154 | { 155 | "components": [ 156 | { 157 | "internalType": "int128", 158 | "name": "spotPriceX18", 159 | "type": "int128" 160 | }, 161 | { 162 | "internalType": "int128", 163 | "name": "perpPriceX18", 164 | "type": "int128" 165 | } 166 | ], 167 | "internalType": "struct IEndpoint.Prices", 168 | "name": "", 169 | "type": "tuple" 170 | } 171 | ], 172 | "stateMutability": "view", 173 | "type": "function" 174 | }, 175 | { 176 | "inputs": [], 177 | "name": "getTime", 178 | "outputs": [ 179 | { 180 | "internalType": "uint128", 181 | "name": "", 182 | "type": "uint128" 183 | } 184 | ], 185 | "stateMutability": "view", 186 | "type": "function" 187 | }, 188 | { 189 | "inputs": [], 190 | "name": "getVersion", 191 | "outputs": [ 192 | { 193 | "internalType": "uint64", 194 | "name": "", 195 | "type": "uint64" 196 | } 197 | ], 198 | "stateMutability": "nonpayable", 199 | "type": "function" 200 | }, 201 | { 202 | "inputs": [ 203 | { 204 | "internalType": "uint32", 205 | "name": "productId", 206 | "type": "uint32" 207 | }, 208 | { 209 | "internalType": "address", 210 | "name": "book", 211 | "type": "address" 212 | } 213 | ], 214 | "name": "setBook", 215 | "outputs": [], 216 | "stateMutability": "nonpayable", 217 | "type": "function" 218 | }, 219 | { 220 | "inputs": [ 221 | { 222 | "internalType": "bytes", 223 | "name": "transaction", 224 | "type": "bytes" 225 | } 226 | ], 227 | "name": "submitSlowModeTransaction", 228 | "outputs": [], 229 | "stateMutability": "nonpayable", 230 | "type": "function" 231 | }, 232 | { 233 | "inputs": [ 234 | { 235 | "internalType": "uint64", 236 | "name": "idx", 237 | "type": "uint64" 238 | }, 239 | { 240 | "internalType": "bytes[]", 241 | "name": "transactions", 242 | "type": "bytes[]" 243 | } 244 | ], 245 | "name": "submitTransactionsChecked", 246 | "outputs": [], 247 | "stateMutability": "nonpayable", 248 | "type": "function" 249 | } 250 | ] 251 | -------------------------------------------------------------------------------- /vertex_protocol/contracts/abis/IFoundationRewardsAirdrop.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "indexed": true, 7 | "internalType": "address", 8 | "name": "account", 9 | "type": "address" 10 | }, 11 | { 12 | "indexed": false, 13 | "internalType": "uint32", 14 | "name": "week", 15 | "type": "uint32" 16 | }, 17 | { 18 | "indexed": false, 19 | "internalType": "uint256", 20 | "name": "amount", 21 | "type": "uint256" 22 | } 23 | ], 24 | "name": "ClaimArb", 25 | "type": "event" 26 | }, 27 | { 28 | "inputs": [ 29 | { 30 | "components": [ 31 | { 32 | "internalType": "uint32", 33 | "name": "week", 34 | "type": "uint32" 35 | }, 36 | { 37 | "internalType": "uint256", 38 | "name": "totalAmount", 39 | "type": "uint256" 40 | }, 41 | { 42 | "internalType": "bytes32[]", 43 | "name": "proof", 44 | "type": "bytes32[]" 45 | } 46 | ], 47 | "internalType": "struct IArbAirdrop.ClaimProof[]", 48 | "name": "claimProofs", 49 | "type": "tuple[]" 50 | } 51 | ], 52 | "name": "claim", 53 | "outputs": [], 54 | "stateMutability": "nonpayable", 55 | "type": "function" 56 | }, 57 | { 58 | "inputs": [ 59 | { 60 | "internalType": "address", 61 | "name": "account", 62 | "type": "address" 63 | } 64 | ], 65 | "name": "getClaimed", 66 | "outputs": [ 67 | { 68 | "internalType": "uint256[]", 69 | "name": "", 70 | "type": "uint256[]" 71 | } 72 | ], 73 | "stateMutability": "view", 74 | "type": "function" 75 | } 76 | ] 77 | -------------------------------------------------------------------------------- /vertex_protocol/contracts/abis/IVrtxAirdrop.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "anonymous": false, 4 | "inputs": [ 5 | { 6 | "indexed": true, 7 | "internalType": "address", 8 | "name": "account", 9 | "type": "address" 10 | }, 11 | { 12 | "indexed": false, 13 | "internalType": "uint32", 14 | "name": "epoch", 15 | "type": "uint32" 16 | }, 17 | { 18 | "indexed": false, 19 | "internalType": "uint256", 20 | "name": "amount", 21 | "type": "uint256" 22 | } 23 | ], 24 | "name": "ClaimVrtx", 25 | "type": "event" 26 | }, 27 | { 28 | "inputs": [ 29 | { 30 | "internalType": "uint32", 31 | "name": "epoch", 32 | "type": "uint32" 33 | }, 34 | { 35 | "internalType": "uint256", 36 | "name": "amount", 37 | "type": "uint256" 38 | }, 39 | { 40 | "internalType": "uint256", 41 | "name": "totalAmount", 42 | "type": "uint256" 43 | }, 44 | { 45 | "internalType": "bytes32[]", 46 | "name": "proof", 47 | "type": "bytes32[]" 48 | } 49 | ], 50 | "name": "claim", 51 | "outputs": [], 52 | "stateMutability": "nonpayable", 53 | "type": "function" 54 | }, 55 | { 56 | "inputs": [ 57 | { 58 | "internalType": "uint32", 59 | "name": "epoch", 60 | "type": "uint32" 61 | }, 62 | { 63 | "internalType": "uint256", 64 | "name": "amount", 65 | "type": "uint256" 66 | }, 67 | { 68 | "internalType": "uint256", 69 | "name": "totalAmount", 70 | "type": "uint256" 71 | }, 72 | { 73 | "internalType": "bytes32[]", 74 | "name": "proof", 75 | "type": "bytes32[]" 76 | } 77 | ], 78 | "name": "claimAndStake", 79 | "outputs": [], 80 | "stateMutability": "nonpayable", 81 | "type": "function" 82 | }, 83 | { 84 | "inputs": [ 85 | { 86 | "internalType": "uint256", 87 | "name": "amount", 88 | "type": "uint256" 89 | }, 90 | { 91 | "internalType": "uint256", 92 | "name": "totalAmount", 93 | "type": "uint256" 94 | }, 95 | { 96 | "internalType": "bytes32[]", 97 | "name": "proof", 98 | "type": "bytes32[]" 99 | } 100 | ], 101 | "name": "claimToLBA", 102 | "outputs": [], 103 | "stateMutability": "nonpayable", 104 | "type": "function" 105 | }, 106 | { 107 | "inputs": [ 108 | { 109 | "internalType": "address", 110 | "name": "account", 111 | "type": "address" 112 | } 113 | ], 114 | "name": "getClaimed", 115 | "outputs": [ 116 | { 117 | "internalType": "uint256[]", 118 | "name": "", 119 | "type": "uint256[]" 120 | } 121 | ], 122 | "stateMutability": "view", 123 | "type": "function" 124 | }, 125 | { 126 | "inputs": [], 127 | "name": "getClaimingDeadlines", 128 | "outputs": [ 129 | { 130 | "internalType": "uint64[]", 131 | "name": "", 132 | "type": "uint64[]" 133 | } 134 | ], 135 | "stateMutability": "view", 136 | "type": "function" 137 | } 138 | ] 139 | -------------------------------------------------------------------------------- /vertex_protocol/contracts/deployments/deployment.abstractMainnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "publicNodeUrl": "https://api.mainnet.abs.xyz", 3 | "explorerUrl": "https://abscan.org", 4 | "startBlock": 410000, 5 | "deployer": "0x184d0842a2C19fB003D0d12C7dFcCc52dBD608F5", 6 | "quote": "0x84A71ccD554Cc1b02749b35d22F684CC8ec987e1", 7 | "querier": "0xC155f48b8212a7Dd16B336f1891c8E26D5DFE093", 8 | "clearinghouse": "0x1385bF2f06165cA0621aF047cF8666c256e1B1C2", 9 | "endpoint": "0x6B104c78D384D1C25CcEe2CA0698541e22eC60b2", 10 | "spotEngine": "0xA65B7Ae7A3a17B93dc382fA1487b4bc3BCEB6e3D", 11 | "perpEngine": "0x6950DD3d2da0cdc217ad56714c6BA0011171bcC4", 12 | "vrtxAirdrop": "0x0000000000000000000000000000000000000000", 13 | "vrtxStaking": "0x0000000000000000000000000000000000000000", 14 | "foundationRewardsAirdrop": "0x0000000000000000000000000000000000000000" 15 | } 16 | -------------------------------------------------------------------------------- /vertex_protocol/contracts/deployments/deployment.abstractTestnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "publicNodeUrl": "https://api.testnet.abs.xyz", 3 | "explorerUrl": "https://explorer.testnet.abs.xyz", 4 | "startBlock": 3899928, 5 | "deployer": "0x3c06e307BA6Ab81E8Ff6661c1559ce8027744AE5", 6 | "quote": "0xbf9ac1Bff961F513fc26ba2734cB0aB371FD27e7", 7 | "querier": "0xfE8942db6a40dF41e5EFec71Db248199D1452d60", 8 | "clearinghouse": "0x96d143992dfEAd13502a68BfE714c7712bA659bA", 9 | "endpoint": "0x597c7D7b33a8b0A73e929BA97814d8b35910D98E", 10 | "spotEngine": "0x9b1232D7AdC1879c69892A0454c6Fee9DfdC1805", 11 | "perpEngine": "0x90520E2159078C2BcF1dD653568951E0ea0767CB", 12 | "vrtxAirdrop": "0x0000000000000000000000000000000000000000", 13 | "vrtxStaking": "0x0000000000000000000000000000000000000000", 14 | "foundationRewardsAirdrop": "0x0000000000000000000000000000000000000000" 15 | } 16 | -------------------------------------------------------------------------------- /vertex_protocol/contracts/deployments/deployment.arbitrumOne.json: -------------------------------------------------------------------------------- 1 | { 2 | "publicNodeUrl": "https://arb1.arbitrum.io/rpc", 3 | "explorerUrl": "https://explorer.arbitrum.io", 4 | "subgraphCoreUrl": "https://api.thegraph.com/subgraphs/name/vertex-protocol/vertex-prod-core", 5 | "subgraphMarketsUrl": "https://api.thegraph.com/subgraphs/name/vertex-protocol/vertex-prod-markets", 6 | "subgraphCandleSticksUrl": "https://api.thegraph.com/subgraphs/name/vertex-protocol/vertex-prod-candlesticks", 7 | "startBlock": 67108377, 8 | "deployer": "0xB746472C10f9F128FF9F5029f424cC91bb1D8C3a", 9 | "quote": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", 10 | "querier": "0x1693273B443699bee277eCbc60e2C8027E91995d", 11 | "feeCalculator": "0x2259440579447D0625a5E28dfF3E743d207e8890", 12 | "clearinghouse": "0xAE1ec28d6225dCE2ff787dcb8CE11cF6D3AE064f", 13 | "clearinghouseLiq": "0xE775A8Ef82C19054A8690399947bEce83fE14dd7", 14 | "endpoint": "0xbbEE07B3e8121227AfCFe1E2B82772246226128e", 15 | "spotEngine": "0x32d91Af2B17054D575A7bF1ACfa7615f41CCEfaB", 16 | "perpEngine": "0xb74C78cca0FADAFBeE52B2f48A67eE8c834b5fd1", 17 | "vrtxAirdrop": "0xAfE39cD8e17Fa4172144ff95274BB665dA411F80", 18 | "vrtxStaking": "0x5Be754aD77766089c4284d914F0cC37E8E3F669A", 19 | "foundationRewardsAirdrop": "0x75A99528b5FC4D328473032c9f390db7C8BabdF1" 20 | } 21 | -------------------------------------------------------------------------------- /vertex_protocol/contracts/deployments/deployment.arbitrumSepolia.json: -------------------------------------------------------------------------------- 1 | { 2 | "publicNodeUrl": "https://sepolia-rollup.arbitrum.io/rpc", 3 | "explorerUrl": "https://sepolia-rollup-explorer.arbitrum.io", 4 | "startBlock": 1027154, 5 | "deployer": "0x3c06e307BA6Ab81E8Ff6661c1559ce8027744AE5", 6 | "quote": "0xD32ea1C76ef1c296F131DD4C5B2A0aac3b22485a", 7 | "querier": "0x2F579046eC1e88Ff580ca5ED9373e91ece8894b0", 8 | "feeCalculator": "0x9D37c59380BA015cF0c115fB89ac6238B96c868E", 9 | "clearinghouse": "0xDFA3926296eaAc8E33c9798836Eae7e8CA1B02FB", 10 | "clearinghouseLiq": "0x0751C5360A418ff3cFe90B7A61199067f3b39ab5", 11 | "endpoint": "0xaDeFDE1A14B6ba4DA3e82414209408a49930E8DC", 12 | "spotEngine": "0x4597CFdd371239a99477Cdabf9cF0B23fDf559B4", 13 | "perpEngine": "0xF2710b0BfBdE4F7aA701f632695e13f2ad118a9E", 14 | "vrtxAirdrop": "0x8D885DC0c3B92B514e1d37839a53053b8402Fb5E", 15 | "vrtxStaking": "0xb186BD223f1Dcc571Ac71C086b2c322414572250", 16 | "foundationRewardsAirdrop": "0x73703D383a8023d7aA8C3D847b77DaFCaC311893" 17 | } 18 | -------------------------------------------------------------------------------- /vertex_protocol/contracts/deployments/deployment.avaxMainnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "publicNodeUrl": "https://api.avax.network/ext/bc/C/rpc", 3 | "explorerUrl": "https://snowtrace.io", 4 | "startBlock": 59043939, 5 | "deployer": "0xB398aE762e9d788e30F430C013f4de46D96794D8", 6 | "quote": "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E", 7 | "querier": "0xc523008CE1D7a5f4cc9f0a9a9c973aA19bE054BC", 8 | "clearinghouse": "0x7069798A5714c5833E36e70df8AeFAac7CEC9302", 9 | "endpoint": "0x36dc76c0C8FC6B4fFe73178C351BA5a3F2178eb3", 10 | "spotEngine": "0xCf0934104391eD43685Ae6aBf24F7CdE93F3Dfa8", 11 | "perpEngine": "0x207c0ef981b4F1FBDfccA88F025C917cFdF1e7C5", 12 | "vrtxAirdrop": "0x0000000000000000000000000000000000000000", 13 | "vrtxStaking": "0x0000000000000000000000000000000000000000", 14 | "foundationRewardsAirdrop": "0x0000000000000000000000000000000000000000" 15 | } 16 | -------------------------------------------------------------------------------- /vertex_protocol/contracts/deployments/deployment.avaxTestnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "publicNodeUrl": "https://api.avax-test.network/ext/bc/C/rpc", 3 | "explorerUrl": "https://subnets-test.avax.network/c-chain", 4 | "startBlock": 38008561, 5 | "deployer": "0x3c06e307BA6Ab81E8Ff6661c1559ce8027744AE5", 6 | "quote": "0x94B3173E0a23C28b2BA9a52464AC24c2B032791c", 7 | "querier": "0x611A1Bf97B94A526a23Bb11cE12341884f8F6Fe0", 8 | "clearinghouse": "0x08F44516d84Eb6D0f057b1Dd198B928aa7191AbD", 9 | "endpoint": "0x1a77cB939B7C4fA5Cc90b09787260DABf5f05c41", 10 | "spotEngine": "0xA1DbC395Cbaa8c7b74456697C42ADa8F2b08CAbb", 11 | "perpEngine": "0xEC1149dF1559eaDB0Af4cf6d9BeAc7a08Aa9f694", 12 | "vrtxAirdrop": "0x0000000000000000000000000000000000000000", 13 | "vrtxStaking": "0x0000000000000000000000000000000000000000", 14 | "foundationRewardsAirdrop": "0x0000000000000000000000000000000000000000" 15 | } 16 | -------------------------------------------------------------------------------- /vertex_protocol/contracts/deployments/deployment.baseMainnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "publicNodeUrl": "https://mainnet.base.org", 3 | "explorerUrl": "https://basescan.org", 4 | "startBlock": 19343662, 5 | "deployer": "0xeCe5f68E5faE5cff2F231c185aC4db83d50aBe0c", 6 | "quote": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913", 7 | "querier": "0x57237f44e893468efDD568cA7dE1EA8A57d14c1b", 8 | "clearinghouse": "0xE46Cb729F92D287F6459bDA6899434E22eCC48AE", 9 | "endpoint": "0x92C2201D48481e2d42772Da02485084A4407Bbe2", 10 | "spotEngine": "0xe818be1DA4E53763bC77df904aD1B5A1C5A61626", 11 | "perpEngine": "0x5BD184F408932F9E6bA00e44A071bCCb8977fb47", 12 | "vrtxAirdrop": "0x0000000000000000000000000000000000000000", 13 | "vrtxStaking": "0x0000000000000000000000000000000000000000", 14 | "foundationRewardsAirdrop": "0x0000000000000000000000000000000000000000" 15 | } 16 | -------------------------------------------------------------------------------- /vertex_protocol/contracts/deployments/deployment.baseTestnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "publicNodeUrl": "https://sepolia.base.org", 3 | "explorerUrl": "https://sepolia-explorer.base.org", 4 | "startBlock": 14467784, 5 | "deployer": "0x3c06e307BA6Ab81E8Ff6661c1559ce8027744AE5", 6 | "quote": "0xbC47901f4d2C5fc871ae0037Ea05c3F614690781", 7 | "querier": "0xFD496f151c60d72DFAfF371e524829565C194D7A", 8 | "clearinghouse": "0x0751C5360A418ff3cFe90B7A61199067f3b39ab5", 9 | "endpoint": "0x08F44516d84Eb6D0f057b1Dd198B928aa7191AbD", 10 | "spotEngine": "0xB61B069d741C98DA3302986BF107aA9083b64133", 11 | "perpEngine": "0xA1DbC395Cbaa8c7b74456697C42ADa8F2b08CAbb", 12 | "vrtxAirdrop": "0x0000000000000000000000000000000000000000", 13 | "vrtxStaking": "0x0000000000000000000000000000000000000000", 14 | "foundationRewardsAirdrop": "0x0000000000000000000000000000000000000000" 15 | } 16 | -------------------------------------------------------------------------------- /vertex_protocol/contracts/deployments/deployment.beraMainnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "publicNodeUrl": "https://rpc.berachain.com", 3 | "explorerUrl": "https://berascan.com", 4 | "startBlock": 1045097, 5 | "deployer": "0xCd55Efc131BEBB66Dd3D68cbda0657eadBDc214D", 6 | "quote": "0xFCBD14DC51f0A4d49d5E53C2E0950e0bC26d0Dce", 7 | "querier": "0xc4002068DEa1Ae3206Ef9CC7fE3fd672934cf9b4", 8 | "clearinghouse": "0x9F90b17E7134aF112C15437Ec4521E4541156036", 9 | "endpoint": "0xACda13Df56ab92A244239f4E2ad291657Ac9C5f2", 10 | "spotEngine": "0x03aAD14233084c1378b81Ca45783b28aacf186BA", 11 | "perpEngine": "0xeba84dbaAeC229dCae114C1a22D9868aB45B87e0", 12 | "vrtxAirdrop": "0x0000000000000000000000000000000000000000", 13 | "vrtxStaking": "0x0000000000000000000000000000000000000000", 14 | "foundationRewardsAirdrop": "0x0000000000000000000000000000000000000000" 15 | } 16 | -------------------------------------------------------------------------------- /vertex_protocol/contracts/deployments/deployment.blastMainnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "publicNodeUrl": "https://rpc.blast.io", 3 | "explorerUrl": "https://blastscan.io", 4 | "startBlock": 184008, 5 | "deployer": "0x02DE3328969394bc3C791c465EeDBb1C654aDec6", 6 | "quote": "0x4300000000000000000000000000000000000003", 7 | "querier": "0x24367B4f22dD406C8BaC3fc54bd5bD0E0d9C56F1", 8 | "clearinghouse": "0xC748532C202828969b2Ee68E0F8487E69cC1d800", 9 | "endpoint": "0x00F076FE36f2341A1054B16ae05FcE0C65180DeD", 10 | "spotEngine": "0x57c1AB256403532d02D1150C5790423967B22Bf2", 11 | "perpEngine": "0x0bc0c84976e21aaF7bE71d318eD93A5f5c9978A4", 12 | "vrtxAirdrop": "0x0000000000000000000000000000000000000000", 13 | "vrtxStaking": "0x0000000000000000000000000000000000000000", 14 | "foundationRewardsAirdrop": "0x0000000000000000000000000000000000000000" 15 | } 16 | -------------------------------------------------------------------------------- /vertex_protocol/contracts/deployments/deployment.blastTestnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "publicNodeUrl": "https://sepolia.blast.io", 3 | "explorerUrl": "https://testnet.blastscan.io", 4 | "startBlock": 1301404, 5 | "deployer": "0x3c06e307BA6Ab81E8Ff6661c1559ce8027744AE5", 6 | "quote": "0xbC47901f4d2C5fc871ae0037Ea05c3F614690781", 7 | "querier": "0xae557AEf1C7290252BA390589C717b9355017fD4", 8 | "clearinghouse": "0xf72BE10454B2fB514A2639da885045C89e3EB693", 9 | "endpoint": "0xDFA3926296eaAc8E33c9798836Eae7e8CA1B02FB", 10 | "spotEngine": "0x9D37c59380BA015cF0c115fB89ac6238B96c868E", 11 | "perpEngine": "0x4597CFdd371239a99477Cdabf9cF0B23fDf559B4", 12 | "vrtxAirdrop": "0x0000000000000000000000000000000000000000", 13 | "vrtxStaking": "0x0000000000000000000000000000000000000000", 14 | "foundationRewardsAirdrop": "0x0000000000000000000000000000000000000000" 15 | } 16 | -------------------------------------------------------------------------------- /vertex_protocol/contracts/deployments/deployment.mantleMainnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "publicNodeUrl": "https://rpc.mantle.xyz", 3 | "explorerUrl": "https://explorer.mantle.xyz", 4 | "startBlock": 63224056, 5 | "deployer": "0xFF9346dFAF07520ef64B006F5F4B2C17c54F59f4", 6 | "quote": "0x09Bc4E0D864854c6aFB6eB9A9cdF58aC190D0dF9", 7 | "querier": "0x71b50Ce0E7f7B920c1BAee3BDE00F2c3F7470395", 8 | "clearinghouse": "0x5bcfC8AD38Ee1da5F45d9795aCaDf57D37FEC172", 9 | "endpoint": "0x526D7C7ea3677efF28CB5bA457f9d341F297Fd52", 10 | "spotEngine": "0xb64d2d606DC23D7a055B770e192631f5c8e1d9f8", 11 | "perpEngine": "0x38080ee5fb939d045A9e533dF355e85Ff4f7e13D", 12 | "vrtxAirdrop": "0x0000000000000000000000000000000000000000", 13 | "vrtxStaking": "0x0000000000000000000000000000000000000000", 14 | "foundationRewardsAirdrop": "0x0000000000000000000000000000000000000000" 15 | } 16 | -------------------------------------------------------------------------------- /vertex_protocol/contracts/deployments/deployment.mantleTestnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "publicNodeUrl": "https://rpc.sepolia.mantle.xyz", 3 | "explorerUrl": "https://explorer.sepolia.mantle.xyz", 4 | "startBlock": 6137613, 5 | "deployer": "0x3c06e307BA6Ab81E8Ff6661c1559ce8027744AE5", 6 | "quote": "0xA7Fcb606611358afa388b6bd23b3B2F2c6abEd82", 7 | "querier": "0x97F9430c279637267D43bcD996F789e1d52Efd60", 8 | "clearinghouse": "0x08F44516d84Eb6D0f057b1Dd198B928aa7191AbD", 9 | "endpoint": "0x1a77cB939B7C4fA5Cc90b09787260DABf5f05c41", 10 | "spotEngine": "0xA1DbC395Cbaa8c7b74456697C42ADa8F2b08CAbb", 11 | "perpEngine": "0xEC1149dF1559eaDB0Af4cf6d9BeAc7a08Aa9f694", 12 | "vrtxAirdrop": "0x0000000000000000000000000000000000000000", 13 | "vrtxStaking": "0x0000000000000000000000000000000000000000", 14 | "foundationRewardsAirdrop": "0x22614Ae2A21c22d46279dB4ADd1aFb9902763465" 15 | } 16 | -------------------------------------------------------------------------------- /vertex_protocol/contracts/deployments/deployment.seiMainnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "publicNodeUrl": "https://evm-rpc.sei-apis.com", 3 | "explorerUrl": "https://seitrace.com/?chain=pacific-1", 4 | "startBlock": 94136270, 5 | "deployer": "0x4007fA559E09Afe29f0b1d58f35b9671e2b82FF0", 6 | "quote": "0x3894085Ef7Ff0f0aeDf52E2A2704928d1Ec074F1", 7 | "querier": "0xecc3dE1cD86CB07c3763D21A45041791574964C2", 8 | "clearinghouse": "0xaE1510367aA8d500bdF507E251147Ea50B22307F", 9 | "endpoint": "0x2777268EeE0d224F99013Bc4af24ec756007f1a6", 10 | "spotEngine": "0x3E113cde3D6309e9bd45Bf7E273ecBB8b50ca127", 11 | "perpEngine": "0x0F54f46979C62aB73D03Da60eBE044c8D63F724f", 12 | "vrtxAirdrop": "0x0000000000000000000000000000000000000000", 13 | "vrtxStaking": "0x0000000000000000000000000000000000000000", 14 | "foundationRewardsAirdrop": "0xC73fBEBb30b1415Fdb40a0aD650c76d6f7e522B5" 15 | } 16 | -------------------------------------------------------------------------------- /vertex_protocol/contracts/deployments/deployment.seiTestnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "publicNodeUrl": "https://evm-rpc-testnet.sei-apis.com", 3 | "explorerUrl": "https://seitrace.com/?chain=atlantic-2", 4 | "startBlock": 101101325, 5 | "deployer": "0x3c06e307BA6Ab81E8Ff6661c1559ce8027744AE5", 6 | "quote": "0xbC47901f4d2C5fc871ae0037Ea05c3F614690781", 7 | "querier": "0x7aDd4cCa426B9dc80564644D8fE19687fFcE4dCE", 8 | "clearinghouse": "0x1A56da8f3619ac25207A754DF19dAd84420F6ed3", 9 | "endpoint": "0xa822AfcdDff7c3236D2dC2643646C7B36BDefb3c", 10 | "spotEngine": "0x9DC1A319dA6aF2D16B6Bac84303cC63ac9e3E2b2", 11 | "perpEngine": "0xF9DE3847F0bD11a15886741a64Db7dc50c79ED89", 12 | "vrtxAirdrop": "0x0000000000000000000000000000000000000000", 13 | "vrtxStaking": "0x0000000000000000000000000000000000000000", 14 | "foundationRewardsAirdrop": "0x5Ab918fCcdE0236C537C2c97BFA40b52180ea643" 15 | } 16 | -------------------------------------------------------------------------------- /vertex_protocol/contracts/deployments/deployment.sonicMainnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "publicNodeUrl": "https://rpc.soniclabs.com", 3 | "explorerUrl": "https://sonicscan.org", 4 | "startBlock": 596514, 5 | "deployer": "0xc88E9A634bC1D3Ee499317aF16e5Ccbe3C9ca490", 6 | "quote": "0x29219dd400f2Bf60E5a23d13Be72B486D4038894", 7 | "querier": "0xcC7895C391041231BfB5837A6923A4A26586d14f", 8 | "clearinghouse": "0x447c9aEe069F6A13007eb9D2d2a4Bb4Ad92AB721", 9 | "endpoint": "0x2f5F835d778eBE8c28fC743E50EB9a68Ca93c2Fa", 10 | "spotEngine": "0xEa555556ab1973973e4f9d3378277Ab156de783d", 11 | "perpEngine": "0x9100770dE5268B969e540650D003D909d5012826", 12 | "vrtxAirdrop": "0x0000000000000000000000000000000000000000", 13 | "vrtxStaking": "0x0000000000000000000000000000000000000000", 14 | "foundationRewardsAirdrop": "0x0000000000000000000000000000000000000000" 15 | } 16 | -------------------------------------------------------------------------------- /vertex_protocol/contracts/deployments/deployment.sonicTestnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "publicNodeUrl": "https://rpc.blaze.soniclabs.com", 3 | "explorerUrl": "https://testnet.sonicscan.org", 4 | "startBlock": 644780, 5 | "deployer": "0x3c06e307BA6Ab81E8Ff6661c1559ce8027744AE5", 6 | "quote": "0xbC47901f4d2C5fc871ae0037Ea05c3F614690781", 7 | "querier": "0xFD496f151c60d72DFAfF371e524829565C194D7A", 8 | "clearinghouse": "0x0751C5360A418ff3cFe90B7A61199067f3b39ab5", 9 | "endpoint": "0x08F44516d84Eb6D0f057b1Dd198B928aa7191AbD", 10 | "spotEngine": "0xB61B069d741C98DA3302986BF107aA9083b64133", 11 | "perpEngine": "0xA1DbC395Cbaa8c7b74456697C42ADa8F2b08CAbb", 12 | "vrtxAirdrop": "0x0000000000000000000000000000000000000000", 13 | "vrtxStaking": "0x0000000000000000000000000000000000000000", 14 | "foundationRewardsAirdrop": "0x0000000000000000000000000000000000000000" 15 | } 16 | -------------------------------------------------------------------------------- /vertex_protocol/contracts/deployments/deployment.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "nodeUrl": "http://0.0.0.0:8545", 3 | "publicNodeUrl": "http://0.0.0.0:8545", 4 | "explorerUrl": "", 5 | "startBlock": 0, 6 | "deployer": "0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", 7 | "quote": "0x5FbDB2315678afecb367f032d93F642f64180aa3", 8 | "querier": "0x4ed7c70F96B99c776995fB64377f0d4aB3B0e1C1", 9 | "feeCalculator": "0xB7f8BC63BbcaD18155201308C8f3540b07f84F5e", 10 | "clearinghouse": "0xa513E6E4b8f2a923D98304ec87F64353C4D5C853", 11 | "clearinghouseLiq": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9", 12 | "endpoint": "0x8A791620dd6260079BF849Dc5567aDC3F2FdC318", 13 | "spotEngine": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82", 14 | "perpEngine": "0x0B306BF915C4d645ff596e518fAf3F9669b97016", 15 | "vrtxAirdrop": "0x0000000000000000000000000000000000000000", 16 | "vrtxStaking": "0x0000000000000000000000000000000000000000", 17 | "foundationRewardsAirdrop": "0x0000000000000000000000000000000000000000" 18 | } 19 | -------------------------------------------------------------------------------- /vertex_protocol/contracts/deployments/deployment.xrplTestnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "publicNodeUrl": "https://rpc.testnet.xrplevm.org", 3 | "explorerUrl": "https://explorer.testnet.xrplevm.org", 4 | "startBlock": 1027303, 5 | "deployer": "0x3c06e307BA6Ab81E8Ff6661c1559ce8027744AE5", 6 | "quote": "0xE26D509C661c4F16FaFfBB1eAce1Fa1CdA8cc146", 7 | "querier": "0xB05648299133D50759091150c9cc83Ba63B7219B", 8 | "clearinghouse": "0x493CfDc0092709646Ebe06f8Ce93bf3020393dbE", 9 | "endpoint": "0x2D47a68A951cAd5A11199C6162aF73405aB65a26", 10 | "spotEngine": "0x994B75F3891855c04b4455c0148f2867E37698Bd", 11 | "perpEngine": "0x0B56e7Cb0F7927680850c83454b20437Ee75742d", 12 | "vrtxAirdrop": "0x0000000000000000000000000000000000000000", 13 | "vrtxStaking": "0x0000000000000000000000000000000000000000", 14 | "foundationRewardsAirdrop": "0x0000000000000000000000000000000000000000" 15 | } 16 | -------------------------------------------------------------------------------- /vertex_protocol/contracts/eip712/__init__.py: -------------------------------------------------------------------------------- 1 | from vertex_protocol.contracts.eip712.domain import * 2 | from vertex_protocol.contracts.eip712.sign import * 3 | from vertex_protocol.contracts.eip712.types import * 4 | 5 | 6 | __all__ = [ 7 | "get_vertex_eip712_domain", 8 | "get_eip712_domain_type", 9 | "build_eip712_typed_data", 10 | "get_eip712_typed_data_digest", 11 | "sign_eip712_typed_data", 12 | "get_vertex_eip712_type", 13 | "EIP712Domain", 14 | "EIP712Types", 15 | "EIP712TypedData", 16 | ] 17 | -------------------------------------------------------------------------------- /vertex_protocol/contracts/eip712/domain.py: -------------------------------------------------------------------------------- 1 | from vertex_protocol.contracts.eip712.types import EIP712Domain 2 | 3 | 4 | def get_vertex_eip712_domain(verifying_contract: str, chain_id: int) -> EIP712Domain: 5 | """ 6 | Util to create an EIP712Domain instance specific to Vertex. 7 | 8 | Args: 9 | verifying_contract (str): The address of the contract that will verify the EIP-712 signature. 10 | 11 | chain_id (int): The chain ID of the originating network. 12 | 13 | Returns: 14 | EIP712Domain: An instance of EIP712Domain with name set to "Vertex", version "0.0.1", and the provided verifying contract and chain ID. 15 | """ 16 | return EIP712Domain( 17 | name="Vertex", 18 | version="0.0.1", 19 | verifyingContract=verifying_contract, 20 | chainId=chain_id, 21 | ) 22 | 23 | 24 | def get_eip712_domain_type() -> list[dict[str, str]]: 25 | """ 26 | Util to return the structure of an EIP712Domain as per EIP-712. 27 | 28 | Returns: 29 | dict: A list of dictionaries each containing the name and type of a field in EIP712Domain. 30 | """ 31 | return [ 32 | {"name": "name", "type": "string"}, 33 | {"name": "version", "type": "string"}, 34 | {"name": "chainId", "type": "uint256"}, 35 | {"name": "verifyingContract", "type": "address"}, 36 | ] 37 | -------------------------------------------------------------------------------- /vertex_protocol/contracts/eip712/sign.py: -------------------------------------------------------------------------------- 1 | from eth_account.signers.local import LocalAccount 2 | from vertex_protocol.contracts.eip712.domain import ( 3 | get_eip712_domain_type, 4 | get_vertex_eip712_domain, 5 | ) 6 | from vertex_protocol.contracts.eip712.types import ( 7 | EIP712TypedData, 8 | EIP712Types, 9 | get_vertex_eip712_type, 10 | ) 11 | from eth_account.messages import encode_structured_data, _hash_eip191_message 12 | 13 | from vertex_protocol.contracts.types import VertexTxType 14 | 15 | 16 | def build_eip712_typed_data( 17 | tx: VertexTxType, msg: dict, verifying_contract: str, chain_id: int 18 | ) -> EIP712TypedData: 19 | """ 20 | Util to build EIP712 typed data for Vertex execution. 21 | 22 | Args: 23 | tx (VertexTxType): The Vertex tx type being signed. 24 | 25 | msg (dict): The message being signed. 26 | 27 | verifying_contract (str): The contract that will verify the signature. 28 | 29 | chain_id (int): The chain ID of the originating network. 30 | 31 | Returns: 32 | EIP712TypedData: A structured data object that adheres to the EIP-712 standard. 33 | """ 34 | eip17_domain = get_vertex_eip712_domain(verifying_contract, chain_id) 35 | eip712_tx_type = get_vertex_eip712_type(tx) 36 | eip712_primary_type = list(eip712_tx_type.keys())[0] 37 | eip712_types = EIP712Types( 38 | **{ 39 | "EIP712Domain": get_eip712_domain_type(), 40 | **eip712_tx_type, 41 | } 42 | ) 43 | return EIP712TypedData( 44 | domain=eip17_domain, 45 | primaryType=eip712_primary_type, 46 | types=eip712_types, 47 | message=msg, 48 | ) 49 | 50 | 51 | def get_eip712_typed_data_digest(typed_data: EIP712TypedData) -> str: 52 | """ 53 | Util to get the EIP-712 typed data hash. 54 | 55 | Args: 56 | typed_data (EIP712TypedData): The EIP-712 typed data to hash. 57 | 58 | Returns: 59 | str: The hexadecimal representation of the hash. 60 | """ 61 | encoded_data = encode_structured_data(typed_data.dict()) 62 | return f"0x{_hash_eip191_message(encoded_data).hex()}" 63 | 64 | 65 | def sign_eip712_typed_data(typed_data: EIP712TypedData, signer: LocalAccount) -> str: 66 | """ 67 | Util to sign EIP-712 typed data using a local Ethereum account. 68 | 69 | Args: 70 | typed_data (EIP712TypedData): The EIP-712 typed data to sign. 71 | 72 | signer (LocalAccount): The local Ethereum account to sign the data. 73 | 74 | Returns: 75 | str: The hexadecimal representation of the signature. 76 | """ 77 | encoded_data = encode_structured_data(typed_data.dict()) 78 | typed_data_hash = signer.sign_message(encoded_data) 79 | return typed_data_hash.signature.hex() 80 | -------------------------------------------------------------------------------- /vertex_protocol/contracts/eip712/types.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | from vertex_protocol.contracts.types import VertexTxType 3 | 4 | 5 | class EIP712Domain(BaseModel): 6 | """ 7 | Model that represents the EIP-712 Domain data structure. 8 | 9 | Attributes: 10 | name (str): The user-readable name of the signing domain, i.e., the name of the DApp or the protocol. 11 | version (str): The current major version of the signing domain. Signatures from different versions are not compatible. 12 | chainId (int): The chain ID of the originating network. 13 | verifyingContract (str): The address of the contract that will verify the signature. 14 | """ 15 | 16 | name: str 17 | version: str 18 | chainId: int 19 | verifyingContract: str 20 | 21 | 22 | class EIP712Types(BaseModel): 23 | """ 24 | Util to encapsulate the EIP-712 type data structure. 25 | 26 | Attributes: 27 | EIP712Domain (list[dict]): A list of dictionaries representing EIP-712 Domain data. 28 | """ 29 | 30 | EIP712Domain: list[dict] 31 | 32 | class Config: 33 | arbitrary_types_allowed = True 34 | extra = "allow" 35 | 36 | 37 | class EIP712TypedData(BaseModel): 38 | """ 39 | Util to represent the EIP-712 Typed Data structure. 40 | 41 | Attributes: 42 | types (EIP712Types): EIP-712 type data. 43 | primaryType (str): The primary type for EIP-712 message signing. 44 | domain (EIP712Domain): The domain data of the EIP-712 typed message. 45 | message (dict): The actual data to sign. 46 | """ 47 | 48 | types: EIP712Types 49 | primaryType: str 50 | domain: EIP712Domain 51 | message: dict 52 | 53 | 54 | def get_vertex_eip712_type(tx: VertexTxType) -> dict: 55 | """ 56 | Util that provides the EIP712 type information for Vertex execute types. 57 | 58 | Args: 59 | tx (VertexTxType): The Vertex transaction type for which to retrieve EIP712 type information. 60 | 61 | Returns: 62 | dict: A dictionary containing the EIP712 type information for the given execute type. 63 | """ 64 | return { 65 | VertexTxType.PLACE_ORDER: { 66 | "Order": [ 67 | {"name": "sender", "type": "bytes32"}, 68 | {"name": "priceX18", "type": "int128"}, 69 | {"name": "amount", "type": "int128"}, 70 | {"name": "expiration", "type": "uint64"}, 71 | {"name": "nonce", "type": "uint64"}, 72 | ] 73 | }, 74 | VertexTxType.PLACE_ISOLATED_ORDER: { 75 | "IsolatedOrder": [ 76 | {"name": "sender", "type": "bytes32"}, 77 | {"name": "priceX18", "type": "int128"}, 78 | {"name": "amount", "type": "int128"}, 79 | {"name": "expiration", "type": "uint64"}, 80 | {"name": "nonce", "type": "uint64"}, 81 | {"name": "margin", "type": "int128"}, 82 | ] 83 | }, 84 | VertexTxType.CANCEL_ORDERS: { 85 | "Cancellation": [ 86 | {"name": "sender", "type": "bytes32"}, 87 | {"name": "productIds", "type": "uint32[]"}, 88 | {"name": "digests", "type": "bytes32[]"}, 89 | {"name": "nonce", "type": "uint64"}, 90 | ] 91 | }, 92 | VertexTxType.CANCEL_PRODUCT_ORDERS: { 93 | "CancellationProducts": [ 94 | {"name": "sender", "type": "bytes32"}, 95 | {"name": "productIds", "type": "uint32[]"}, 96 | {"name": "nonce", "type": "uint64"}, 97 | ], 98 | }, 99 | VertexTxType.WITHDRAW_COLLATERAL: { 100 | "WithdrawCollateral": [ 101 | {"name": "sender", "type": "bytes32"}, 102 | {"name": "productId", "type": "uint32"}, 103 | {"name": "amount", "type": "uint128"}, 104 | {"name": "nonce", "type": "uint64"}, 105 | ] 106 | }, 107 | VertexTxType.LIQUIDATE_SUBACCOUNT: { 108 | "LiquidateSubaccount": [ 109 | {"name": "sender", "type": "bytes32"}, 110 | {"name": "liquidatee", "type": "bytes32"}, 111 | {"name": "productId", "type": "uint32"}, 112 | {"name": "isEncodedSpread", "type": "bool"}, 113 | {"name": "amount", "type": "int128"}, 114 | {"name": "nonce", "type": "uint64"}, 115 | ], 116 | }, 117 | VertexTxType.MINT_LP: { 118 | "MintLp": [ 119 | {"name": "sender", "type": "bytes32"}, 120 | {"name": "productId", "type": "uint32"}, 121 | {"name": "amountBase", "type": "uint128"}, 122 | {"name": "quoteAmountLow", "type": "uint128"}, 123 | {"name": "quoteAmountHigh", "type": "uint128"}, 124 | {"name": "nonce", "type": "uint64"}, 125 | ] 126 | }, 127 | VertexTxType.BURN_LP: { 128 | "BurnLp": [ 129 | {"name": "sender", "type": "bytes32"}, 130 | {"name": "productId", "type": "uint32"}, 131 | {"name": "amount", "type": "uint128"}, 132 | {"name": "nonce", "type": "uint64"}, 133 | ] 134 | }, 135 | VertexTxType.LINK_SIGNER: { 136 | "LinkSigner": [ 137 | {"name": "sender", "type": "bytes32"}, 138 | {"name": "signer", "type": "bytes32"}, 139 | {"name": "nonce", "type": "uint64"}, 140 | ] 141 | }, 142 | VertexTxType.AUTHENTICATE_STREAM: { 143 | "StreamAuthentication": [ 144 | {"name": "sender", "type": "bytes32"}, 145 | {"name": "expiration", "type": "uint64"}, 146 | ] 147 | }, 148 | VertexTxType.LIST_TRIGGER_ORDERS: { 149 | "ListTriggerOrders": [ 150 | {"name": "sender", "type": "bytes32"}, 151 | {"name": "recvTime", "type": "uint64"}, 152 | ] 153 | }, 154 | }[tx] 155 | -------------------------------------------------------------------------------- /vertex_protocol/contracts/loader.py: -------------------------------------------------------------------------------- 1 | import json 2 | from pathlib import Path 3 | from vertex_protocol.contracts.types import ( 4 | VertexAbiName, 5 | VertexDeployment, 6 | VertexNetwork, 7 | ) 8 | from vertex_protocol.utils.model import ensure_data_type, parse_enum_value 9 | 10 | 11 | def load_abi(abi_name: VertexAbiName) -> list[dict]: 12 | """ 13 | Load the Application Binary Interface (ABI) for a given contract. 14 | 15 | Args: 16 | abi_name (VertexAbiName): The name of the contract for which the ABI is loaded. 17 | 18 | Returns: 19 | list[dict]: A list of dictionaries representing the ABI of the contract. 20 | """ 21 | file_path = Path(__file__).parent / "abis" / f"{parse_enum_value(abi_name)}.json" 22 | return ensure_data_type(_load_json(file_path), list) 23 | 24 | 25 | def load_deployment(network: VertexNetwork) -> VertexDeployment: 26 | """ 27 | Load the deployment data for a given network. 28 | 29 | Args: 30 | network (VertexNetwork): The network for which the deployment data is loaded. 31 | 32 | Returns: 33 | VertexDeployment: An instance of VertexDeployment containing the loaded deployment data. 34 | """ 35 | file_path = ( 36 | Path(__file__).parent 37 | / "deployments" 38 | / f"deployment.{parse_enum_value(network)}.json" 39 | ) 40 | return VertexDeployment(**_load_json(file_path)) 41 | 42 | 43 | def _load_json(file_path: Path) -> dict: 44 | """ 45 | Load a JSON file. 46 | 47 | Args: 48 | file_path (Path): The path to the JSON file. 49 | 50 | Returns: 51 | dict: The content of the JSON file as a dictionary. 52 | """ 53 | with open(file_path, "r") as f: 54 | data = json.load(f) 55 | return data 56 | -------------------------------------------------------------------------------- /vertex_protocol/contracts/types.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from pydantic import AnyUrl, Field 3 | from vertex_protocol.utils.enum import StrEnum 4 | 5 | from vertex_protocol.utils.model import VertexBaseModel 6 | 7 | 8 | class VertexNetwork(StrEnum): 9 | """ 10 | Enumeration representing various network environments for the Vertex protocol. 11 | """ 12 | 13 | # mainnet 14 | ARBITRUM_ONE = "arbitrumOne" 15 | BLAST_MAINNET = "blastMainnet" 16 | MANTLE_MAINNET = "mantleMainnet" 17 | SEI_MAINNET = "seiMainnet" 18 | BASE_MAINNET = "baseMainnet" 19 | SONIC_MAINNET = "sonicMainnet" 20 | ABSTRACT_MAINNET = "abstractMainnet" 21 | BERA_MAINNET = "beraMainnet" 22 | AVAX_MAINNET = "avaxMainnet" 23 | 24 | # testnet 25 | ARBITRUM_SEPOLIA = "arbitrumSepolia" 26 | BLAST_TESTNET = "blastTestnet" 27 | MANTLE_TESTNET = "mantleTestnet" 28 | SEI_TESTNET = "seiTestnet" 29 | BASE_TESTNET = "baseTestnet" 30 | SONIC_TESTNET = "sonicTestnet" 31 | ABSTRACT_TESTNET = "abstractTestnet" 32 | AVAX_TESTNET = "avaxTestnet" 33 | XRPL_TESTNET = "xrplTestnet" 34 | 35 | # dev 36 | HARDHAT = "localhost" 37 | TESTING = "test" 38 | 39 | 40 | class VertexAbiName(StrEnum): 41 | """ 42 | Enumeration representing various contract names for which the ABI can be loaded in the Vertex protocol. 43 | """ 44 | 45 | ENDPOINT = "Endpoint" 46 | FQUERIER = "FQuerier" 47 | ICLEARINGHOUSE = "IClearinghouse" 48 | IENDPOINT = "IEndpoint" 49 | IOFFCHAIN_BOOK = "IOffchainBook" 50 | IPERP_ENGINE = "IPerpEngine" 51 | ISPOT_ENGINE = "ISpotEngine" 52 | MOCK_ERC20 = "MockERC20" 53 | ISTAKING = "IStaking" 54 | IVRTX_AIRDROP = "IVrtxAirdrop" 55 | IFOUNDATION_REWARDS_AIRDROP = "IFoundationRewardsAirdrop" 56 | 57 | 58 | class VertexDeployment(VertexBaseModel): 59 | """ 60 | Class representing deployment data for Vertex protocol contracts. 61 | 62 | Attributes: 63 | node_url (AnyUrl): The URL of the node. 64 | 65 | quote_addr (str): The address of the quote contract. 66 | 67 | querier_addr (str): The address of the querier contract. 68 | 69 | clearinghouse_addr (str): The address of the clearinghouse contract. 70 | 71 | endpoint_addr (str): The address of the endpoint contract. 72 | 73 | spot_engine_addr (str): The address of the spot engine contract. 74 | 75 | perp_engine_addr (str): The address of the perpetual engine contract. 76 | 77 | vrtx_airdrop_addr (str): The address of the VRTX airdrop contract. 78 | 79 | vrtx_staking_addr (str): The address of the VRTX staking contract. 80 | 81 | foundation_rewards_airdrop_addr (str): The address of Foundation Rewards airdrop contract for the corresponding chain (e.g: Arb airdrop for Arbitrum). 82 | """ 83 | 84 | node_url: AnyUrl = Field(alias="publicNodeUrl") 85 | quote_addr: str = Field(alias="quote") 86 | querier_addr: str = Field(alias="querier") 87 | clearinghouse_addr: str = Field(alias="clearinghouse") 88 | endpoint_addr: str = Field(alias="endpoint") 89 | spot_engine_addr: str = Field(alias="spotEngine") 90 | perp_engine_addr: str = Field(alias="perpEngine") 91 | vrtx_airdrop_addr: str = Field(alias="vrtxAirdrop") 92 | vrtx_staking_addr: str = Field(alias="vrtxStaking") 93 | foundation_rewards_airdrop_addr: str = Field(alias="foundationRewardsAirdrop") 94 | 95 | 96 | class DepositCollateralParams(VertexBaseModel): 97 | """ 98 | Class representing parameters for depositing collateral in the Vertex protocol. 99 | 100 | Attributes: 101 | subaccount_name (str): The name of the subaccount. 102 | 103 | product_id (int): The ID of the spot product to deposit collateral for. 104 | 105 | amount (int): The amount of collateral to be deposited. 106 | 107 | referral_code (Optional[str]): Use this to indicate you were referred by existing member. 108 | """ 109 | 110 | subaccount_name: str 111 | product_id: int 112 | amount: int 113 | referral_code: Optional[str] 114 | 115 | 116 | class ClaimVrtxParams(VertexBaseModel): 117 | epoch: int 118 | amount: Optional[int] 119 | claim_all: Optional[bool] 120 | 121 | 122 | class ClaimVrtxContractParams(VertexBaseModel): 123 | epoch: int 124 | amount_to_claim: int 125 | total_claimable_amount: int 126 | merkle_proof: list[str] 127 | 128 | 129 | class ClaimFoundationRewardsProofStruct(VertexBaseModel): 130 | totalAmount: int 131 | week: int 132 | proof: list[str] 133 | 134 | 135 | class ClaimFoundationRewardsContractParams(VertexBaseModel): 136 | claim_proofs: list[ClaimFoundationRewardsProofStruct] 137 | 138 | 139 | class VertexExecuteType(StrEnum): 140 | """ 141 | Enumeration of possible actions to execute in Vertex. 142 | """ 143 | 144 | PLACE_ORDER = "place_order" 145 | PLACE_ISOLATED_ORDER = "place_isolated_order" 146 | CANCEL_ORDERS = "cancel_orders" 147 | CANCEL_PRODUCT_ORDERS = "cancel_product_orders" 148 | CANCEL_AND_PLACE = "cancel_and_place" 149 | WITHDRAW_COLLATERAL = "withdraw_collateral" 150 | LIQUIDATE_SUBACCOUNT = "liquidate_subaccount" 151 | MINT_LP = "mint_lp" 152 | BURN_LP = "burn_lp" 153 | LINK_SIGNER = "link_signer" 154 | 155 | 156 | VertexTxType = StrEnum( 157 | "VertexTxType", 158 | { 159 | **{ 160 | name: member.value for name, member in VertexExecuteType.__members__.items() 161 | }, 162 | **{"AUTHENTICATE_STREAM": "authenticate"}, 163 | **{"LIST_TRIGGER_ORDERS": "list_trigger_orders"}, 164 | }, 165 | ) # type: ignore 166 | -------------------------------------------------------------------------------- /vertex_protocol/engine_client/__init__.py: -------------------------------------------------------------------------------- 1 | from vertex_protocol.engine_client.types import EngineClientOpts 2 | from vertex_protocol.engine_client.execute import EngineExecuteClient 3 | from vertex_protocol.engine_client.query import EngineQueryClient 4 | 5 | 6 | class EngineClient(EngineQueryClient, EngineExecuteClient): # type: ignore 7 | """ 8 | Client for interacting with the engine service. 9 | 10 | It allows users to both query data from and execute commands on the engine service. 11 | 12 | Attributes: 13 | opts (EngineClientOpts): Client configuration options for connecting and interacting with the engine service. 14 | 15 | Methods: 16 | __init__: Initializes the `EngineClient` with the provided options. 17 | """ 18 | 19 | def __init__(self, opts: EngineClientOpts): 20 | """ 21 | Initializes the EngineClient with the provided options. 22 | 23 | Args: 24 | opts (EngineClientOpts): Client configuration options for connecting and interacting with the engine service. 25 | """ 26 | EngineQueryClient.__init__(self, opts) 27 | EngineExecuteClient.__init__(self, opts) 28 | 29 | 30 | __all__ = [ 31 | "EngineClient", 32 | "EngineClientOpts", 33 | "EngineExecuteClient", 34 | "EngineQueryClient", 35 | ] 36 | -------------------------------------------------------------------------------- /vertex_protocol/engine_client/types/__init__.py: -------------------------------------------------------------------------------- 1 | from vertex_protocol.engine_client.types.execute import * 2 | from vertex_protocol.engine_client.types.models import * 3 | from vertex_protocol.engine_client.types.query import * 4 | from vertex_protocol.engine_client.types.stream import * 5 | from vertex_protocol.utils.backend import VertexClientOpts 6 | 7 | 8 | class EngineClientOpts(VertexClientOpts): 9 | """ 10 | Model defining the configuration options for the Engine Client. 11 | """ 12 | 13 | 14 | __all__ = [ 15 | "BaseParams", 16 | "SignatureParams", 17 | "BaseParamsSigned", 18 | "OrderParams", 19 | "PlaceOrderParams", 20 | "CancelOrdersParams", 21 | "CancelProductOrdersParams", 22 | "CancelAndPlaceParams", 23 | "WithdrawCollateralParams", 24 | "LiquidateSubaccountParams", 25 | "MintLpParams", 26 | "BurnLpParams", 27 | "LinkSignerParams", 28 | "ExecuteParams", 29 | "TxRequest", 30 | "PlaceOrderRequest", 31 | "CancelOrdersRequest", 32 | "CancelProductOrdersRequest", 33 | "CancelAndPlaceRequest", 34 | "WithdrawCollateralRequest", 35 | "LiquidateSubaccountRequest", 36 | "MintLpRequest", 37 | "BurnLpRequest", 38 | "LinkSignerRequest", 39 | "ExecuteRequest", 40 | "ExecuteResponse", 41 | "EngineQueryType", 42 | "QueryStatusParams", 43 | "QueryContractsParams", 44 | "QueryNoncesParams", 45 | "QueryOrderParams", 46 | "QuerySubaccountInfoTx", 47 | "QuerySubaccountInfoParams", 48 | "QuerySubaccountOpenOrdersParams", 49 | "QueryMarketLiquidityParams", 50 | "QueryAllProductsParams", 51 | "QueryMarketPriceParams", 52 | "QueryMaxOrderSizeParams", 53 | "QueryMaxWithdrawableParams", 54 | "QueryMaxLpMintableParams", 55 | "QueryFeeRatesParams", 56 | "QueryHealthGroupsParams", 57 | "QueryLinkedSignerParams", 58 | "QueryRequest", 59 | "StatusData", 60 | "ContractsData", 61 | "NoncesData", 62 | "OrderData", 63 | "SubaccountInfoData", 64 | "SubaccountOpenOrdersData", 65 | "MarketLiquidityData", 66 | "AllProductsData", 67 | "MarketPriceData", 68 | "MaxOrderSizeData", 69 | "MaxWithdrawableData", 70 | "MaxLpMintableData", 71 | "FeeRatesData", 72 | "HealthGroupsData", 73 | "LinkedSignerData", 74 | "QueryResponseData", 75 | "QueryResponse", 76 | "ResponseStatus", 77 | "EngineStatus", 78 | "MintLp", 79 | "BurnLp", 80 | "ApplyDelta", 81 | "MintLpTx", 82 | "BurnLpTx", 83 | "ApplyDeltaTx", 84 | "SubaccountHealth", 85 | "SpotLpBalance", 86 | "SpotBalance", 87 | "SpotProductBalance", 88 | "PerpLpBalance", 89 | "PerpBalance", 90 | "PerpProductBalance", 91 | "ProductRisk", 92 | "ProductBookInfo", 93 | "BaseProduct", 94 | "BaseProductLpState", 95 | "SpotProductConfig", 96 | "SpotProductState", 97 | "SpotProductLpAmount", 98 | "SpotProductLpState", 99 | "SpotProduct", 100 | "PerpProductState", 101 | "PerpProductLpState", 102 | "PerpProduct", 103 | "MaxOrderSizeDirection", 104 | "MarketLiquidity", 105 | "StreamAuthenticationParams", 106 | "Asset", 107 | "MarketPair", 108 | "SpotApr", 109 | "Orderbook", 110 | "AssetsData", 111 | "MarketPairsData", 112 | "SpotsAprData", 113 | ] 114 | -------------------------------------------------------------------------------- /vertex_protocol/engine_client/types/stream.py: -------------------------------------------------------------------------------- 1 | from vertex_protocol.engine_client.types.execute import SignatureParams 2 | 3 | 4 | class StreamAuthenticationParams(SignatureParams): 5 | sender: str 6 | expiration: int 7 | -------------------------------------------------------------------------------- /vertex_protocol/indexer_client/__init__.py: -------------------------------------------------------------------------------- 1 | from vertex_protocol.indexer_client.query import IndexerQueryClient 2 | from vertex_protocol.indexer_client.types import IndexerClientOpts 3 | 4 | 5 | class IndexerClient(IndexerQueryClient): 6 | """ 7 | Client for interacting with the indexer service. 8 | 9 | It provides methods for querying data from the indexer service. 10 | 11 | Attributes: 12 | opts (IndexerClientOpts): Client configuration options for connecting and interacting with the indexer service. 13 | 14 | Methods: 15 | __init__: Initializes the `IndexerClient` with the provided options. 16 | """ 17 | 18 | def __init__(self, opts: IndexerClientOpts): 19 | """ 20 | Initializes the IndexerClient with the provided options. 21 | 22 | Args: 23 | opts (IndexerClientOpts): Client configuration options for connecting and interacting with the indexer service. 24 | """ 25 | super().__init__(opts) 26 | 27 | 28 | __all__ = ["IndexerClient", "IndexerClientOpts", "IndexerQueryClient"] 29 | -------------------------------------------------------------------------------- /vertex_protocol/indexer_client/types/__init__.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel, AnyUrl, validator 2 | from vertex_protocol.indexer_client.types.models import * 3 | from vertex_protocol.indexer_client.types.query import * 4 | 5 | 6 | class IndexerClientOpts(BaseModel): 7 | """ 8 | Model representing the options for the Indexer Client 9 | """ 10 | 11 | url: AnyUrl 12 | 13 | @validator("url") 14 | def clean_url(cls, v: AnyUrl) -> str: 15 | return v.rstrip("/") 16 | 17 | 18 | __all__ = [ 19 | "IndexerQueryType", 20 | "IndexerBaseParams", 21 | "IndexerSubaccountHistoricalOrdersParams", 22 | "IndexerHistoricalOrdersByDigestParams", 23 | "IndexerMatchesParams", 24 | "IndexerEventsRawLimit", 25 | "IndexerEventsTxsLimit", 26 | "IndexerEventsLimit", 27 | "IndexerEventsParams", 28 | "IndexerSubaccountSummaryParams", 29 | "IndexerProductSnapshotsParams", 30 | "IndexerCandlesticksParams", 31 | "IndexerFundingRateParams", 32 | "IndexerPerpPricesParams", 33 | "IndexerOraclePricesParams", 34 | "IndexerTokenRewardsParams", 35 | "IndexerMakerStatisticsParams", 36 | "IndexerLiquidationFeedParams", 37 | "IndexerLinkedSignerRateLimitParams", 38 | "IndexerReferralCodeParams", 39 | "IndexerSubaccountsParams", 40 | "IndexerParams", 41 | "IndexerHistoricalOrdersRequest", 42 | "IndexerMatchesRequest", 43 | "IndexerEventsRequest", 44 | "IndexerSubaccountSummaryRequest", 45 | "IndexerProductSnapshotsRequest", 46 | "IndexerCandlesticksRequest", 47 | "IndexerFundingRateRequest", 48 | "IndexerFundingRatesRequest", 49 | "IndexerPerpPricesRequest", 50 | "IndexerOraclePricesRequest", 51 | "IndexerTokenRewardsRequest", 52 | "IndexerMakerStatisticsRequest", 53 | "IndexerLiquidationFeedRequest", 54 | "IndexerLinkedSignerRateLimitRequest", 55 | "IndexerReferralCodeRequest", 56 | "IndexerSubaccountsRequest", 57 | "IndexerRequest", 58 | "IndexerHistoricalOrdersData", 59 | "IndexerMatchesData", 60 | "IndexerEventsData", 61 | "IndexerSubaccountSummaryData", 62 | "IndexerProductSnapshotsData", 63 | "IndexerCandlesticksData", 64 | "IndexerFundingRateData", 65 | "IndexerPerpPricesData", 66 | "IndexerOraclePricesData", 67 | "IndexerTokenRewardsData", 68 | "IndexerMakerStatisticsData", 69 | "IndexerLinkedSignerRateLimitData", 70 | "IndexerReferralCodeData", 71 | "IndexerSubaccountsData", 72 | "IndexerLiquidationFeedData", 73 | "IndexerResponseData", 74 | "IndexerResponse", 75 | "IndexerEventType", 76 | "IndexerCandlesticksGranularity", 77 | "IndexerBaseModel", 78 | "IndexerBaseOrder", 79 | "IndexerOrderFill", 80 | "IndexerHistoricalOrder", 81 | "IndexerSignedOrder", 82 | "IndexerMatch", 83 | "IndexerMatchOrdersTxData", 84 | "IndexerMatchOrdersTx", 85 | "IndexerWithdrawCollateralTxData", 86 | "IndexerWithdrawCollateralTx", 87 | "IndexerLiquidateSubaccountTxData", 88 | "IndexerLiquidateSubaccountTx", 89 | "IndexerMintLpTxData", 90 | "IndexerMintLpTx", 91 | "IndexerBurnLpTxData", 92 | "IndexerBurnLpTx", 93 | "IndexerTxData", 94 | "IndexerTx", 95 | "IndexerSpotProductBalanceData", 96 | "IndexerSpotProductData", 97 | "IndexerPerpProductData", 98 | "IndexerProductData", 99 | "IndexerEventTrackedData", 100 | "IndexerEvent", 101 | "IndexerProduct", 102 | "IndexerCandlestick", 103 | "IndexerOraclePrice", 104 | "IndexerAddressReward", 105 | "IndexerGlobalRewards", 106 | "IndexerTokenReward", 107 | "IndexerMarketMakerData", 108 | "IndexerMarketMaker", 109 | "IndexerLiquidatableAccount", 110 | "IndexerSubaccount", 111 | "IndexerUsdcPriceData", 112 | "IndexerInterestAndFundingParams", 113 | "IndexerInterestAndFundingRequest", 114 | "IndexerInterestAndFundingData", 115 | "IndexerTickerInfo", 116 | "IndexerPerpContractInfo", 117 | "IndexerTradeInfo", 118 | "VrtxTokenQueryType", 119 | "IndexerTickersData", 120 | "IndexerPerpContractsData", 121 | "IndexerHistoricalTradesData", 122 | ] 123 | -------------------------------------------------------------------------------- /vertex_protocol/trigger_client/__init__.py: -------------------------------------------------------------------------------- 1 | from vertex_protocol.trigger_client.types import TriggerClientOpts 2 | from vertex_protocol.trigger_client.execute import TriggerExecuteClient 3 | from vertex_protocol.trigger_client.query import TriggerQueryClient 4 | 5 | 6 | class TriggerClient(TriggerQueryClient, TriggerExecuteClient): # type: ignore 7 | def __init__(self, opts: TriggerClientOpts): 8 | TriggerQueryClient.__init__(self, opts) 9 | TriggerExecuteClient.__init__(self, opts) 10 | 11 | 12 | __all__ = [ 13 | "TriggerClient", 14 | "TriggerClientOpts", 15 | "TriggerExecuteClient", 16 | "TriggerQueryClient", 17 | ] 18 | -------------------------------------------------------------------------------- /vertex_protocol/trigger_client/execute.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from functools import singledispatchmethod 3 | from typing import Union 4 | from vertex_protocol.contracts.types import VertexExecuteType 5 | from vertex_protocol.trigger_client.types.execute import ( 6 | TriggerExecuteParams, 7 | TriggerExecuteRequest, 8 | PlaceTriggerOrderParams, 9 | CancelTriggerOrdersParams, 10 | CancelProductTriggerOrdersParams, 11 | to_trigger_execute_request, 12 | ) 13 | from vertex_protocol.engine_client.types.execute import ExecuteResponse 14 | from vertex_protocol.trigger_client.types import TriggerClientOpts 15 | from vertex_protocol.utils.exceptions import ( 16 | BadStatusCodeException, 17 | ExecuteFailedException, 18 | ) 19 | from vertex_protocol.utils.execute import VertexBaseExecute 20 | from vertex_protocol.utils.model import VertexBaseModel, is_instance_of_union 21 | 22 | 23 | class TriggerExecuteClient(VertexBaseExecute): 24 | def __init__(self, opts: TriggerClientOpts): 25 | super().__init__(opts) 26 | self._opts: TriggerClientOpts = TriggerClientOpts.parse_obj(opts) 27 | self.url: str = self._opts.url 28 | self.session = requests.Session() 29 | 30 | def tx_nonce(self, _: str) -> int: 31 | raise NotImplementedError 32 | 33 | @singledispatchmethod 34 | def execute( 35 | self, params: Union[TriggerExecuteParams, TriggerExecuteRequest] 36 | ) -> ExecuteResponse: 37 | """ 38 | Executes the operation defined by the provided parameters. 39 | 40 | Args: 41 | params (ExecuteParams): The parameters for the operation to execute. This can represent a variety of operations, such as placing orders, cancelling orders, and more. 42 | 43 | Returns: 44 | ExecuteResponse: The response from the executed operation. 45 | """ 46 | req: TriggerExecuteRequest = ( 47 | params if is_instance_of_union(params, TriggerExecuteRequest) else to_trigger_execute_request(params) # type: ignore 48 | ) 49 | return self._execute(req) 50 | 51 | @execute.register 52 | def _(self, req: dict) -> ExecuteResponse: 53 | """ 54 | Overloaded method to execute the operation defined by the provided request. 55 | 56 | Args: 57 | req (dict): The request data for the operation to execute. Can be a dictionary or an instance of ExecuteRequest. 58 | 59 | Returns: 60 | ExecuteResponse: The response from the executed operation. 61 | """ 62 | parsed_req: TriggerExecuteRequest = VertexBaseModel.parse_obj(req) # type: ignore 63 | return self._execute(parsed_req) 64 | 65 | def _execute(self, req: TriggerExecuteRequest) -> ExecuteResponse: 66 | """ 67 | Internal method to execute the operation. Sends request to the server. 68 | 69 | Args: 70 | req (TriggerExecuteRequest): The request data for the operation to execute. 71 | 72 | Returns: 73 | ExecuteResponse: The response from the executed operation. 74 | 75 | Raises: 76 | BadStatusCodeException: If the server response status code is not 200. 77 | ExecuteFailedException: If there's an error in the execution or the response status is not "success". 78 | """ 79 | res = self.session.post(f"{self.url}/execute", json=req.dict()) 80 | if res.status_code != 200: 81 | raise BadStatusCodeException(res.text) 82 | try: 83 | execute_res = ExecuteResponse(**res.json(), req=req.dict()) 84 | except Exception: 85 | raise ExecuteFailedException(res.text) 86 | if execute_res.status != "success": 87 | raise ExecuteFailedException(res.text) 88 | return execute_res 89 | 90 | def place_trigger_order(self, params: PlaceTriggerOrderParams) -> ExecuteResponse: 91 | params = PlaceTriggerOrderParams.parse_obj(params) 92 | params.order = self.prepare_execute_params(params.order, True, True) 93 | params.signature = params.signature or self._sign( 94 | VertexExecuteType.PLACE_ORDER, params.order.dict(), params.product_id 95 | ) 96 | return self.execute(params) 97 | 98 | def cancel_trigger_orders( 99 | self, params: CancelTriggerOrdersParams 100 | ) -> ExecuteResponse: 101 | params = self.prepare_execute_params( 102 | CancelTriggerOrdersParams.parse_obj(params), True 103 | ) 104 | params.signature = params.signature or self._sign( 105 | VertexExecuteType.CANCEL_ORDERS, params.dict() 106 | ) 107 | return self.execute(params) 108 | 109 | def cancel_product_trigger_orders( 110 | self, params: CancelProductTriggerOrdersParams 111 | ) -> ExecuteResponse: 112 | params = self.prepare_execute_params( 113 | CancelProductTriggerOrdersParams.parse_obj(params), True 114 | ) 115 | params.signature = params.signature or self._sign( 116 | VertexExecuteType.CANCEL_PRODUCT_ORDERS, params.dict() 117 | ) 118 | return self.execute(params) 119 | -------------------------------------------------------------------------------- /vertex_protocol/trigger_client/query.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from vertex_protocol.contracts.types import VertexTxType 3 | from vertex_protocol.trigger_client.types import TriggerClientOpts 4 | from vertex_protocol.trigger_client.types.query import ( 5 | ListTriggerOrdersParams, 6 | ListTriggerOrdersRequest, 7 | TriggerQueryResponse, 8 | ) 9 | from vertex_protocol.utils.exceptions import ( 10 | BadStatusCodeException, 11 | QueryFailedException, 12 | ) 13 | from vertex_protocol.utils.execute import VertexBaseExecute 14 | 15 | 16 | class TriggerQueryClient(VertexBaseExecute): 17 | """ 18 | Client class for querying the trigger service. 19 | """ 20 | 21 | def __init__(self, opts: TriggerClientOpts): 22 | self._opts: TriggerClientOpts = TriggerClientOpts.parse_obj(opts) 23 | self.url: str = self._opts.url 24 | self.session = requests.Session() # type: ignore 25 | 26 | def tx_nonce(self, _: str) -> int: 27 | raise NotImplementedError 28 | 29 | def query(self, req: dict) -> TriggerQueryResponse: 30 | """ 31 | Send a query to the trigger service. 32 | 33 | Args: 34 | req (QueryRequest): The query request parameters. 35 | 36 | Returns: 37 | QueryResponse: The response from the engine. 38 | 39 | Raises: 40 | BadStatusCodeException: If the response status code is not 200. 41 | QueryFailedException: If the query status is not "success". 42 | """ 43 | res = self.session.post(f"{self.url}/query", json=req) 44 | if res.status_code != 200: 45 | raise BadStatusCodeException(res.text) 46 | try: 47 | query_res = TriggerQueryResponse(**res.json()) 48 | except Exception: 49 | raise QueryFailedException(res.text) 50 | if query_res.status != "success": 51 | raise QueryFailedException(res.text) 52 | return query_res 53 | 54 | def list_trigger_orders( 55 | self, params: ListTriggerOrdersParams 56 | ) -> TriggerQueryResponse: 57 | params = ListTriggerOrdersParams.parse_obj(params) 58 | params.signature = params.signature or self._sign( 59 | VertexTxType.LIST_TRIGGER_ORDERS, params.tx.dict() 60 | ) 61 | return self.query(ListTriggerOrdersRequest.parse_obj(params).dict()) 62 | -------------------------------------------------------------------------------- /vertex_protocol/trigger_client/types/__init__.py: -------------------------------------------------------------------------------- 1 | from vertex_protocol.utils.backend import VertexClientOpts 2 | 3 | 4 | class TriggerClientOpts(VertexClientOpts): 5 | """ 6 | Model defining the configuration options for the Trigger Client. 7 | """ 8 | -------------------------------------------------------------------------------- /vertex_protocol/trigger_client/types/execute.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | from pydantic import validator 3 | from vertex_protocol.contracts.types import VertexExecuteType 4 | from vertex_protocol.utils.bytes32 import bytes32_to_hex 5 | from vertex_protocol.utils.model import VertexBaseModel 6 | from vertex_protocol.engine_client.types.execute import ( 7 | PlaceOrderParams, 8 | CancelOrdersParams, 9 | CancelProductOrdersParams, 10 | CancelOrdersRequest, 11 | CancelProductOrdersRequest, 12 | ) 13 | from vertex_protocol.trigger_client.types.models import TriggerCriteria 14 | 15 | 16 | class PlaceTriggerOrderParams(PlaceOrderParams): 17 | trigger: TriggerCriteria 18 | 19 | 20 | CancelTriggerOrdersParams = CancelOrdersParams 21 | CancelProductTriggerOrdersParams = CancelProductOrdersParams 22 | 23 | TriggerExecuteParams = Union[ 24 | PlaceTriggerOrderParams, CancelTriggerOrdersParams, CancelProductTriggerOrdersParams 25 | ] 26 | 27 | 28 | class PlaceTriggerOrderRequest(VertexBaseModel): 29 | """ 30 | Parameters for a request to place an order. 31 | 32 | Attributes: 33 | place_order (PlaceOrderParams): The parameters for the order to be placed. 34 | 35 | Methods: 36 | serialize: Validates and serializes the order parameters. 37 | """ 38 | 39 | place_order: PlaceTriggerOrderParams 40 | 41 | @validator("place_order") 42 | def serialize(cls, v: PlaceTriggerOrderParams) -> PlaceTriggerOrderParams: 43 | if v.order.nonce is None: 44 | raise ValueError("Missing order `nonce`") 45 | if v.signature is None: 46 | raise ValueError("Missing `signature") 47 | if isinstance(v.order.sender, bytes): 48 | v.order.serialize_dict(["sender"], bytes32_to_hex) 49 | v.order.serialize_dict(["nonce", "priceX18", "amount", "expiration"], str) 50 | return v 51 | 52 | 53 | CancelTriggerOrdersRequest = CancelOrdersRequest 54 | CancelProductTriggerOrdersRequest = CancelProductOrdersRequest 55 | 56 | TriggerExecuteRequest = Union[ 57 | PlaceTriggerOrderRequest, 58 | CancelTriggerOrdersRequest, 59 | CancelProductTriggerOrdersRequest, 60 | ] 61 | 62 | 63 | def to_trigger_execute_request(params: TriggerExecuteParams) -> TriggerExecuteRequest: 64 | """ 65 | Maps `TriggerExecuteParams` to its corresponding `TriggerExecuteRequest` object based on the parameter type. 66 | 67 | Args: 68 | params (TriggerExecuteParams): The parameters to be executed. 69 | 70 | Returns: 71 | TriggerExecuteRequest: The corresponding `TriggerExecuteRequest` object. 72 | """ 73 | execute_request_mapping = { 74 | PlaceTriggerOrderParams: ( 75 | PlaceTriggerOrderRequest, 76 | VertexExecuteType.PLACE_ORDER.value, 77 | ), 78 | CancelTriggerOrdersParams: ( 79 | CancelTriggerOrdersRequest, 80 | VertexExecuteType.CANCEL_ORDERS.value, 81 | ), 82 | CancelProductTriggerOrdersParams: ( 83 | CancelProductTriggerOrdersRequest, 84 | VertexExecuteType.CANCEL_PRODUCT_ORDERS.value, 85 | ), 86 | } 87 | 88 | RequestClass, field_name = execute_request_mapping[type(params)] 89 | return RequestClass(**{field_name: params}) # type: ignore 90 | -------------------------------------------------------------------------------- /vertex_protocol/trigger_client/types/models.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Union 2 | from vertex_protocol.utils.model import VertexBaseModel 3 | 4 | 5 | class PriceAboveTrigger(VertexBaseModel): 6 | price_above: str 7 | 8 | 9 | class PriceBelowTrigger(VertexBaseModel): 10 | price_below: str 11 | 12 | 13 | class LastPriceAboveTrigger(VertexBaseModel): 14 | last_price_above: str 15 | 16 | 17 | class LastPriceBelowTrigger(VertexBaseModel): 18 | last_price_below: str 19 | 20 | 21 | TriggerCriteria = Union[ 22 | PriceAboveTrigger, PriceBelowTrigger, LastPriceAboveTrigger, LastPriceBelowTrigger 23 | ] 24 | 25 | 26 | class OrderData(VertexBaseModel): 27 | sender: str 28 | priceX18: str 29 | amount: str 30 | expiration: str 31 | nonce: str 32 | 33 | 34 | class TriggerOrderData(VertexBaseModel): 35 | """ 36 | Data model for details of a trigger order. 37 | """ 38 | 39 | product_id: int 40 | order: OrderData 41 | signature: str 42 | spot_leverage: Optional[bool] 43 | digest: Optional[str] 44 | trigger: TriggerCriteria 45 | -------------------------------------------------------------------------------- /vertex_protocol/trigger_client/types/query.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import validator 4 | 5 | from vertex_protocol.engine_client.types.models import ResponseStatus 6 | from vertex_protocol.trigger_client.types.models import TriggerOrderData 7 | from vertex_protocol.utils.bytes32 import bytes32_to_hex 8 | from vertex_protocol.utils.execute import BaseParams, SignatureParams 9 | from vertex_protocol.utils.model import VertexBaseModel 10 | 11 | 12 | class ListTriggerOrdersTx(BaseParams): 13 | recvTime: int 14 | 15 | 16 | class ListTriggerOrdersParams(VertexBaseModel): 17 | """ 18 | Parameters for listing trigger orders 19 | """ 20 | 21 | type = "list_trigger_orders" 22 | tx: ListTriggerOrdersTx 23 | product_id: Optional[int] 24 | pending: bool 25 | max_update_time: Optional[str] 26 | max_digest: Optional[str] 27 | digests: Optional[list[str]] 28 | limit: Optional[int] 29 | signature: Optional[str] 30 | 31 | 32 | class TriggerOrder(VertexBaseModel): 33 | order: TriggerOrderData 34 | status: str 35 | updated_at: int 36 | 37 | 38 | class TriggerOrdersData(VertexBaseModel): 39 | """ 40 | Data model for trigger orders 41 | """ 42 | 43 | orders: list[TriggerOrder] 44 | 45 | 46 | class ListTriggerOrdersRequest(ListTriggerOrdersParams): 47 | tx: ListTriggerOrdersTx 48 | 49 | @validator("tx") 50 | def serialize(cls, v: ListTriggerOrdersTx) -> ListTriggerOrdersTx: 51 | if isinstance(v.sender, bytes): 52 | v.serialize_dict(["sender"], bytes32_to_hex) 53 | v.serialize_dict(["recvTime"], str) 54 | return v 55 | 56 | 57 | class TriggerQueryResponse(VertexBaseModel): 58 | """ 59 | Represents a response to a query request. 60 | 61 | Attributes: 62 | status (ResponseStatus): The status of the query response. 63 | 64 | data (Optional[QueryResponseData]): The data returned from the query, or an error message if the query failed. 65 | 66 | error (Optional[str]): The error message, if any error occurred during the query. 67 | 68 | error_code (Optional[int]): The error code, if any error occurred during the query. 69 | 70 | request_type (Optional[str]): Type of the request. 71 | """ 72 | 73 | status: ResponseStatus 74 | data: Optional[TriggerOrdersData] 75 | error: Optional[str] 76 | error_code: Optional[int] 77 | request_type: Optional[str] 78 | -------------------------------------------------------------------------------- /vertex_protocol/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from vertex_protocol.utils.backend import * 2 | from vertex_protocol.utils.bytes32 import * 3 | from vertex_protocol.utils.subaccount import * 4 | from vertex_protocol.utils.expiration import * 5 | from vertex_protocol.utils.math import * 6 | from vertex_protocol.utils.nonce import * 7 | from vertex_protocol.utils.exceptions import * 8 | 9 | __all__ = [ 10 | "VertexBackendURL", 11 | "VertexClientOpts", 12 | "SubaccountParams", 13 | "Subaccount", 14 | "subaccount_to_bytes32", 15 | "subaccount_to_hex", 16 | "subaccount_name_to_bytes12", 17 | "hex_to_bytes32", 18 | "hex_to_bytes12", 19 | "hex_to_bytes", 20 | "str_to_hex", 21 | "bytes32_to_hex", 22 | "zero_subaccount", 23 | "zero_address", 24 | "OrderType", 25 | "get_expiration_timestamp", 26 | "gen_order_nonce", 27 | "decode_expiration", 28 | "to_pow_10", 29 | "to_x18", 30 | "from_pow_10", 31 | "from_x18", 32 | "ExecuteFailedException", 33 | "QueryFailedException", 34 | "BadStatusCodeException", 35 | "MissingSignerException", 36 | "InvalidProductId", 37 | ] 38 | -------------------------------------------------------------------------------- /vertex_protocol/utils/bytes32.py: -------------------------------------------------------------------------------- 1 | import binascii 2 | from typing import Optional, Union 3 | from vertex_protocol.utils.subaccount import Subaccount, SubaccountParams 4 | 5 | 6 | def hex_to_bytes32(input: Union[str, bytes]) -> bytes: 7 | """Converts a hexadecimal string or bytes to a bytes object of length 32. 8 | 9 | Args: 10 | input (str | bytes): The hexadecimal string or bytes to be converted. 11 | 12 | Returns: 13 | bytes: The converted bytes object of length 32. 14 | """ 15 | return hex_to_bytes(input, 32) 16 | 17 | 18 | def hex_to_bytes12(input: Union[str, bytes]) -> bytes: 19 | """Converts a hexadecimal string or bytes to a bytes object of length 12. 20 | 21 | Args: 22 | input (str | bytes): The hexadecimal string or bytes to be converted. 23 | 24 | Returns: 25 | bytes: The converted bytes object of length 12. 26 | """ 27 | return hex_to_bytes(input, 12) 28 | 29 | 30 | def hex_to_bytes(input: Union[str, bytes], size: int) -> bytes: 31 | """Converts a hexadecimal string or bytes to a bytes object of specified size. 32 | 33 | Args: 34 | input (str | bytes): The hexadecimal string or bytes to be converted. 35 | 36 | size (int): The specified size for the output bytes object. 37 | 38 | Returns: 39 | bytes: The converted bytes object of the specified size. 40 | """ 41 | if isinstance(input, bytes): 42 | return input 43 | if input.encode() == zero_subaccount(): 44 | return zero_subaccount() 45 | if input.startswith("0x"): 46 | input = input[2:] 47 | data_bytes = bytes.fromhex(input) 48 | padded_data = data_bytes + b"\x00" * (size - len(data_bytes)) 49 | return padded_data 50 | 51 | 52 | def str_to_hex(input: str) -> str: 53 | """Converts a string to its hexadecimal representation. 54 | 55 | Args: 56 | input (str): The string to be converted. 57 | 58 | Returns: 59 | str: The hexadecimal representation of the input string. 60 | """ 61 | return binascii.hexlify(input.encode()).decode() 62 | 63 | 64 | def subaccount_to_bytes32( 65 | subaccount: Subaccount, name: Optional[Union[str, bytes]] = None 66 | ) -> bytes: 67 | """Converts a subaccount representation to a bytes object of length 32. 68 | 69 | Args: 70 | subaccount (Subaccount): The subaccount, which can be a string, bytes, or SubaccountParams instance. 71 | 72 | name (str|bytes, optional): The subaccount name, when provided `subaccount` is expected to be the owner address. 73 | 74 | Returns: 75 | (bytes|SubaccountParams): The bytes object of length 32 representing the subaccount. 76 | 77 | Raises: 78 | ValueError: If the `subaccount` is a `SubaccountParams` instance and is missing either `subaccount_owner` or `subaccount_name` 79 | 80 | Note: 81 | If `name` is provided, `subaccount` must be the owner address, otherwise `subaccount` 82 | can be the bytes32 or hex representation of the subaccount or a SubaccountParams object. 83 | """ 84 | if isinstance(subaccount, str): 85 | if name is None: 86 | return hex_to_bytes32(subaccount) 87 | else: 88 | name = name.hex() if isinstance(name, bytes) else name 89 | return hex_to_bytes32(subaccount + str_to_hex(name)) 90 | elif isinstance(subaccount, SubaccountParams): 91 | subaccount_owner = subaccount.dict().get("subaccount_owner") 92 | subaccount_name = subaccount.dict().get("subaccount_name") 93 | if subaccount_owner is None or subaccount_name is None: 94 | raise ValueError("Missing `subaccount_owner` or `subaccount_name`") 95 | else: 96 | return hex_to_bytes32(subaccount_owner + str_to_hex(subaccount_name)) 97 | else: 98 | return subaccount 99 | 100 | 101 | def subaccount_to_hex( 102 | subaccount: Subaccount, name: Optional[Union[str, bytes]] = None 103 | ) -> str: 104 | """Converts a subaccount representation to its hexadecimal representation. 105 | 106 | Args: 107 | subaccount (Subaccount): The subaccount, which can be a string, bytes, or SubaccountParams instance. 108 | 109 | name (str|bytes, optional): Additional string, if any, to be appended to the subaccount string before conversion. Defaults to None. 110 | 111 | Returns: 112 | (str|SubaccountParams): The hexadecimal representation of the subaccount. 113 | """ 114 | return bytes32_to_hex(subaccount_to_bytes32(subaccount, name)) 115 | 116 | 117 | def subaccount_name_to_bytes12(subaccount_name: str) -> bytes: 118 | """Converts a subaccount name to a bytes object of length 12. 119 | 120 | Args: 121 | subaccount_name (str): The subaccount name to be converted. 122 | 123 | Returns: 124 | bytes: A bytes object of length 12 representing the subaccount name. 125 | """ 126 | return hex_to_bytes12(str_to_hex(subaccount_name)) 127 | 128 | 129 | def bytes32_to_hex(bytes32: bytes) -> str: 130 | """Converts a bytes object of length 32 to its hexadecimal representation. 131 | 132 | Args: 133 | bytes32 (bytes): The bytes object of length 32 to be converted. 134 | 135 | Returns: 136 | str: The hexadecimal representation of the input bytes object. If the input is not a bytes object, the function returns the input itself. 137 | """ 138 | if isinstance(bytes32, bytes): 139 | return f"0x{bytes32.hex()}" 140 | else: 141 | return bytes32 142 | 143 | 144 | def zero_subaccount() -> bytes: 145 | """Generates a bytes object of length 32 filled with zero bytes. 146 | 147 | Returns: 148 | bytes: A bytes object of length 32 filled with zero bytes. 149 | """ 150 | return b"\x00" * 32 151 | 152 | 153 | def zero_address() -> bytes: 154 | """Generates a bytes object of length 20 filled with zero bytes. 155 | 156 | Returns: 157 | bytes: A bytes object of length 20 filled with zero bytes. 158 | """ 159 | return b"\x00" * 20 160 | -------------------------------------------------------------------------------- /vertex_protocol/utils/enum.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class StrEnum(str, Enum): 5 | def __str__(self): 6 | return self.value 7 | -------------------------------------------------------------------------------- /vertex_protocol/utils/exceptions.py: -------------------------------------------------------------------------------- 1 | class ExecuteFailedException(Exception): 2 | """Raised when the execute status is not 'success'""" 3 | 4 | def __init__(self, message="Execute failed"): 5 | self.message = message 6 | super().__init__(self.message) 7 | 8 | 9 | class QueryFailedException(Exception): 10 | """Raised when the query status is not 'success'""" 11 | 12 | def __init__(self, message="Query failed"): 13 | self.message = message 14 | super().__init__(self.message) 15 | 16 | 17 | class BadStatusCodeException(Exception): 18 | """Raised when the response status code is not 200""" 19 | 20 | def __init__(self, message="Bad status code"): 21 | self.message = message 22 | super().__init__(self.message) 23 | 24 | 25 | class MissingSignerException(Exception): 26 | """Raised when the Signer is required to perform an operation but it's not provided.""" 27 | 28 | def __init__(self, message="Signer not provided"): 29 | self.message = message 30 | super().__init__(self.message) 31 | 32 | 33 | class InvalidProductId(Exception): 34 | """Raised when product id is invalid.""" 35 | 36 | def __init__(self, message="Invalid product id provided"): 37 | self.message = message 38 | super().__init__(self.message) 39 | 40 | 41 | class InvalidVrtxClaimParams(Exception): 42 | """Raised when providing invalid VRTX claim parameters.""" 43 | 44 | def __init__( 45 | self, 46 | message="Invalid VRTX params. Either `amount` or `claim_all` must be provided", 47 | ): 48 | self.message = message 49 | super().__init__(self.message) 50 | 51 | 52 | class MissingTriggerClient(Exception): 53 | def __init__( 54 | self, 55 | message="Trigger client not initialized.", 56 | ): 57 | self.message = message 58 | super().__init__(self.message) 59 | -------------------------------------------------------------------------------- /vertex_protocol/utils/expiration.py: -------------------------------------------------------------------------------- 1 | from enum import IntEnum 2 | 3 | 4 | class OrderType(IntEnum): 5 | DEFAULT = 0 6 | IOC = 1 7 | FOK = 2 8 | POST_ONLY = 3 9 | 10 | 11 | def get_expiration_timestamp( 12 | order_type: OrderType, expiration: int, reduce_only: bool = False 13 | ) -> int: 14 | """ 15 | Encodes the order type into the expiration timestamp for special order types such as immediate-or-cancel. 16 | 17 | Args: 18 | order_type (OrderType): The type of order. 19 | 20 | expiration (int): The expiration timestamp in UNIX seconds. 21 | 22 | reduce_only (bool): When True, the order can only reduce the size of an existing position. Works only with IOC & FOK. 23 | 24 | Returns: 25 | int: The properly formatted timestamp needed for the specified order type. 26 | """ 27 | expiration = int(expiration) | (order_type << 62) 28 | if reduce_only: 29 | expiration |= 1 << 61 30 | return expiration 31 | 32 | 33 | def decode_expiration(expiration: int) -> tuple[OrderType, int]: 34 | """ 35 | Decodes the expiration timestamp to retrieve the order type and original expiration timestamp. 36 | 37 | Args: 38 | expiration (int): The encoded expiration timestamp. 39 | 40 | Returns: 41 | Tuple[OrderType, int]: The decoded order type and the original expiration timestamp. 42 | """ 43 | order_type: OrderType = OrderType(expiration >> 62) 44 | exp_timestamp = expiration & ((1 << 62) - 1) 45 | return order_type, exp_timestamp 46 | -------------------------------------------------------------------------------- /vertex_protocol/utils/interest.py: -------------------------------------------------------------------------------- 1 | from vertex_protocol.engine_client.types.models import SpotProduct 2 | from vertex_protocol.utils.time import TimeInSeconds 3 | from vertex_protocol.utils.math import from_x18, mul_x18 4 | 5 | 6 | def calc_deposits_and_borrows(product: SpotProduct) -> tuple[float, float]: 7 | total_deposited = from_x18( 8 | mul_x18( 9 | int(product.state.total_deposits_normalized), 10 | int(product.state.cumulative_deposits_multiplier_x18), 11 | ) 12 | ) 13 | total_borrowed = from_x18( 14 | mul_x18( 15 | int(product.state.total_borrows_normalized), 16 | int(product.state.cumulative_borrows_multiplier_x18), 17 | ) 18 | ) 19 | return (total_deposited, total_borrowed) 20 | 21 | 22 | def calc_utilization_ratio(product: SpotProduct) -> float: 23 | total_deposited, total_borrowed = calc_deposits_and_borrows(product) 24 | 25 | if total_deposited == 0 or total_borrowed == 0: 26 | return 0 27 | 28 | return abs(total_borrowed) / total_deposited 29 | 30 | 31 | def calc_borrow_rate_per_second(product: SpotProduct) -> float: 32 | utilization = calc_utilization_ratio(product) 33 | if utilization == 0: 34 | return 0 35 | interest_floor = from_x18(int(product.config.interest_floor_x18)) 36 | interest_inflection_util = from_x18( 37 | int(product.config.interest_inflection_util_x18) 38 | ) 39 | interest_small_cap = from_x18(int(product.config.interest_small_cap_x18)) 40 | interest_large_cap = from_x18(int(product.config.interest_large_cap_x18)) 41 | 42 | annual_rate = 0.0 43 | if utilization > interest_inflection_util: 44 | utilization_term = interest_large_cap * ( 45 | (utilization - interest_inflection_util) / (1 - interest_inflection_util) 46 | ) 47 | annual_rate = interest_floor + interest_small_cap + utilization_term 48 | else: 49 | utilization_term = (utilization / interest_inflection_util) * interest_small_cap 50 | annual_rate = interest_floor + utilization_term 51 | return annual_rate / TimeInSeconds.YEAR 52 | 53 | 54 | def calc_borrow_rate_in_period(product: SpotProduct, period_in_seconds) -> float: 55 | borrow_rate_per_second = calc_borrow_rate_per_second(product) 56 | return (borrow_rate_per_second + 1) ** period_in_seconds - 1 57 | 58 | 59 | def calc_deposit_rate_in_period( 60 | product: SpotProduct, period_in_seconds: int, interest_fee_fraction: float 61 | ) -> float: 62 | utilization = calc_utilization_ratio(product) 63 | if utilization == 0: 64 | return 0 65 | borrow_rate_in_period = calc_borrow_rate_in_period(product, period_in_seconds) 66 | return utilization * borrow_rate_in_period * (1 - interest_fee_fraction) 67 | -------------------------------------------------------------------------------- /vertex_protocol/utils/math.py: -------------------------------------------------------------------------------- 1 | from decimal import Decimal 2 | from typing import Union 3 | 4 | 5 | def to_pow_10(x: int, pow: int) -> int: 6 | """ 7 | Converts integer to power of 10 format. 8 | 9 | Args: 10 | x (int): Integer value. 11 | 12 | pow (int): Power of 10. 13 | 14 | Returns: 15 | int: Converted value. 16 | """ 17 | return x * 10**pow 18 | 19 | 20 | def to_x18(x: float) -> int: 21 | """ 22 | Converts a float to a fixed point of 1e18. 23 | 24 | Args: 25 | x (float): Float value to convert. 26 | 27 | Returns: 28 | int: Fixed point value represented as an integer. 29 | """ 30 | return int(Decimal(str(x)) * Decimal(10**18)) 31 | 32 | 33 | def from_pow_10(x: int, pow: int) -> float: 34 | """ 35 | Reverts integer from power of 10 format. 36 | 37 | Args: 38 | x (int): Converted value. 39 | 40 | pow (int): Power of 10. 41 | 42 | Returns: 43 | float: Original value. 44 | """ 45 | return float(x) / 10**pow 46 | 47 | 48 | def from_x18(x: int) -> float: 49 | """ 50 | Reverts integer from power of 10^18 format. 51 | 52 | Args: 53 | x (int): Converted value. 54 | 55 | Returns: 56 | float: Original value. 57 | """ 58 | return from_pow_10(x, 18) 59 | 60 | 61 | def mul_x18(x: Union[float, str], y: Union[float, str]) -> int: 62 | return int(Decimal(str(x)) * Decimal(str(y)) / Decimal(10**18)) 63 | 64 | 65 | def round_x18(x: Union[int, str], y: Union[str, int]) -> int: 66 | x, y = int(x), int(y) 67 | return x - x % y 68 | -------------------------------------------------------------------------------- /vertex_protocol/utils/model.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | from pydantic import BaseModel 3 | from typing import Any, Callable, Type, TypeVar, Union 4 | 5 | 6 | class VertexBaseModel(BaseModel): 7 | """ 8 | This base model extends Pydantic's BaseModel and excludes fields with None 9 | values by default when serializing via .dict() or .json() 10 | """ 11 | 12 | def dict(self, **kwargs): 13 | """ 14 | Convert model to dictionary, excluding None fields by default. 15 | 16 | Args: 17 | kwargs: Arbitrary keyword arguments. 18 | 19 | Returns: 20 | dict: The model as a dictionary. 21 | """ 22 | kwargs.setdefault("exclude_none", True) 23 | return super().dict(**kwargs) 24 | 25 | def json(self, **kwargs): 26 | """ 27 | Convert model to JSON, excluding None fields by default. 28 | 29 | Args: 30 | kwargs: Arbitrary keyword arguments. 31 | 32 | Returns: 33 | str: The model as a JSON string. 34 | """ 35 | kwargs.setdefault("exclude_none", True) 36 | return super().json(**kwargs) 37 | 38 | def serialize_dict(self, fields: list[str], func: Callable): 39 | """ 40 | Apply a function to specified fields in the model's dictionary. 41 | 42 | Args: 43 | fields (list[str]): Fields to be modified. 44 | 45 | func (Callable): Function to apply to each field. 46 | """ 47 | for field in fields: 48 | self.__dict__[field] = func(self.__dict__[field]) 49 | 50 | 51 | def parse_enum_value(value: Union[str, Enum]) -> str: 52 | """ 53 | Utility function to parse an enum value. 54 | 55 | Args: 56 | value (str | Enum): Original value which may be an Enum. 57 | 58 | Returns: 59 | str: The Enum value. 60 | """ 61 | if isinstance(value, Enum): 62 | return value.value 63 | else: 64 | return value 65 | 66 | 67 | T = TypeVar("T") 68 | 69 | 70 | def ensure_data_type(data, expected_type: Type[T]) -> T: 71 | assert isinstance( 72 | data, expected_type 73 | ), f"Expected {expected_type.__name__}, but got {type(data).__name__}" 74 | return data 75 | 76 | 77 | def is_instance_of_union(obj: Any, union) -> bool: 78 | """Check if `obj` is an instance of any type in the `union`.""" 79 | return any(isinstance(obj, cls) for cls in union.__args__) 80 | -------------------------------------------------------------------------------- /vertex_protocol/utils/nonce.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | from datetime import timezone, datetime, timedelta 3 | import random 4 | 5 | 6 | def gen_order_nonce( 7 | recv_time_ms: Optional[int] = None, 8 | random_int: Optional[int] = None, 9 | is_trigger_order: bool = False, 10 | ) -> int: 11 | """ 12 | Generates an order nonce based on a received timestamp and a random integer. 13 | 14 | Args: 15 | recv_time_ms (int, optional): Received timestamp in milliseconds. Defaults to the current time plus 90 seconds. 16 | 17 | random_int (int, optional): An integer for the nonce. Defaults to a random integer between 0 and 999. 18 | 19 | Returns: 20 | int: The generated order nonce. 21 | """ 22 | if recv_time_ms is None: 23 | recv_time_ms = int( 24 | (datetime.now(tz=timezone.utc) + timedelta(seconds=90)).timestamp() * 1000 25 | ) 26 | if random_int is None: 27 | random_int = random.randint(0, 999) 28 | 29 | nonce = (recv_time_ms << 20) + random_int 30 | 31 | if is_trigger_order: 32 | nonce = nonce | (1 << 63) 33 | return nonce 34 | -------------------------------------------------------------------------------- /vertex_protocol/utils/subaccount.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Union 2 | from vertex_protocol.utils.model import VertexBaseModel 3 | 4 | 5 | class SubaccountParams(VertexBaseModel): 6 | """ 7 | A class used to represent parameters for a Subaccount in the Vertex system. 8 | 9 | Attributes: 10 | subaccount_owner (Optional[str]): The wallet address of the subaccount. 11 | subaccount_name (str): The subaccount name identifier. 12 | """ 13 | 14 | subaccount_owner: Optional[str] 15 | subaccount_name: str 16 | 17 | 18 | Subaccount = Union[str, bytes, SubaccountParams] 19 | -------------------------------------------------------------------------------- /vertex_protocol/utils/time.py: -------------------------------------------------------------------------------- 1 | from enum import IntEnum 2 | import time 3 | 4 | 5 | class TimeInSeconds(IntEnum): 6 | MINUTE = 60 7 | HOUR = 3600 8 | DAY = 86400 9 | YEAR = 31536000 10 | 11 | 12 | def millis_to_seconds(millis: int) -> int: 13 | return millis // 1000 14 | 15 | 16 | def now_in_seconds() -> int: 17 | return int(time.time()) 18 | 19 | 20 | def now_in_millis(padding: int = 0) -> int: 21 | return (now_in_seconds() + padding) * 1000 22 | --------------------------------------------------------------------------------