From d632996006a21b3ac1abc186c703f6e9bb63f593 Mon Sep 17 00:00:00 2001
From: samtin0x <40127309+samtin0x@users.noreply.github.com>
Date: Fri, 14 Jun 2024 16:21:57 +0200
Subject: [PATCH 1/7] Deprecate existing python client
---
{v4-client-py => v4-client-py-deprecated}/.gitignore | 0
{v4-client-py => v4-client-py-deprecated}/.gitleaks.toml | 0
{v4-client-py => v4-client-py-deprecated}/.gitleaksignore | 0
{v4-client-py => v4-client-py-deprecated}/.vscode/launch.json | 0
{v4-client-py => v4-client-py-deprecated}/.vscode/settings.json | 0
{v4-client-py => v4-client-py-deprecated}/LICENSE | 0
{v4-client-py => v4-client-py-deprecated}/README.md | 0
{v4-client-py => v4-client-py-deprecated}/examples/README.md | 0
{v4-client-py => v4-client-py-deprecated}/examples/__init__.py | 0
.../examples/account_endpoints.py | 0
.../examples/composite_example.py | 0
.../examples/faucet_endpoint.py | 0
.../examples/human_readable_orders.json | 0
.../examples/human_readable_short_term_orders.json | 0
.../examples/long_term_order_cancel_example.py | 0
.../examples/markets_endpoints.py | 0
.../examples/raw_orders.json | 0
.../examples/short_term_order_cancel_example.py | 0
.../examples/short_term_order_composite_example.py | 0
.../examples/transfer_example_deposit.py | 0
.../examples/transfer_example_subaccount_transfer.py | 0
.../examples/transfer_example_withdraw.py | 0
.../examples/utility_endpoints.py | 0
{v4-client-py => v4-client-py-deprecated}/examples/utils.py | 0
.../examples/validator_get_examples.py | 0
.../examples/validator_post_examples.py | 0
.../examples/wallet_address.py | 0
.../examples/websocket_example.py | 0
{v4-client-py => v4-client-py-deprecated}/pyproject.toml | 0
{v4-client-py => v4-client-py-deprecated}/pytest.ini | 0
{v4-client-py => v4-client-py-deprecated}/pytest_integration.ini | 0
{v4-client-py => v4-client-py-deprecated}/requirements-lint.txt | 0
.../requirements-publish.txt | 0
{v4-client-py => v4-client-py-deprecated}/requirements-test.txt | 0
{v4-client-py => v4-client-py-deprecated}/requirements.txt | 0
{v4-client-py => v4-client-py-deprecated}/setup.py | 0
{v4-client-py => v4-client-py-deprecated}/tests/__init__.py | 0
{v4-client-py => v4-client-py-deprecated}/tests/constants.py | 0
.../tests/test_indexer_markets_endpoints.py | 0
.../tests/test_indexer_utility_endpoints.py | 0
.../tests/test_request_helpers.py | 0
.../tests/test_validator_get_endpoints.py | 0
.../tests_integration/__init__.py | 0
.../tests_integration/human_readable_orders.json | 0
.../tests_integration/raw_orders.json | 0
.../tests_integration/test_faucet.py | 0
.../tests_integration/test_indexer_account_endpoints.py | 0
.../tests_integration/test_trades.py | 0
.../tests_integration/test_transfers.py | 0
.../tests_integration/util.py | 0
.../v4_client_py/__init__.py | 0
.../v4_client_py/chain/__init__.py | 0
.../v4_client_py/chain/aerial/__init__.py | 0
.../v4_client_py/chain/aerial/client/__init__.py | 0
.../v4_client_py/chain/aerial/client/bank.py | 0
.../v4_client_py/chain/aerial/client/distribution.py | 0
.../v4_client_py/chain/aerial/client/staking.py | 0
.../v4_client_py/chain/aerial/client/utils.py | 0
.../v4_client_py/chain/aerial/coins.py | 0
.../v4_client_py/chain/aerial/config.py | 0
.../v4_client_py/chain/aerial/exceptions.py | 0
.../v4_client_py/chain/aerial/faucet.py | 0
.../v4_client_py/chain/aerial/gas.py | 0
.../v4_client_py/chain/aerial/tx.py | 0
.../v4_client_py/chain/aerial/tx_helpers.py | 0
.../v4_client_py/chain/aerial/urls.py | 0
.../v4_client_py/chain/aerial/wallet.py | 0
.../v4_client_py/chain/auth/__init__.py | 0
.../v4_client_py/chain/auth/interface.py | 0
.../v4_client_py/chain/auth/rest_client.py | 0
.../v4_client_py/chain/bank/__init__.py | 0
.../v4_client_py/chain/bank/interface.py | 0
.../v4_client_py/chain/bank/rest_client.py | 0
.../v4_client_py/chain/common/__init__.py | 0
.../v4_client_py/chain/common/rest_client.py | 0
.../v4_client_py/chain/common/types.py | 0
.../v4_client_py/chain/common/utils.py | 0
.../v4_client_py/chain/crypto/__init__.py | 0
.../v4_client_py/chain/crypto/address.py | 0
.../v4_client_py/chain/crypto/hashfuncs.py | 0
.../v4_client_py/chain/crypto/interface.py | 0
.../v4_client_py/chain/crypto/keypairs.py | 0
.../v4_client_py/chain/crypto/keypairs_bls.py | 0
.../v4_client_py/chain/distribution/__init__.py | 0
.../v4_client_py/chain/distribution/interface.py | 0
.../v4_client_py/chain/distribution/rest_client.py | 0
.../v4_client_py/chain/evidence/__init__.py | 0
.../v4_client_py/chain/evidence/interface.py | 0
.../v4_client_py/chain/evidence/rest_client.py | 0
.../v4_client_py/chain/gov/__init__.py | 0
.../v4_client_py/chain/gov/interface.py | 0
.../v4_client_py/chain/gov/rest_client.py | 0
.../v4_client_py/chain/mint/__init__.py | 0
.../v4_client_py/chain/mint/interface.py | 0
.../v4_client_py/chain/mint/rest_client.py | 0
.../v4_client_py/chain/params/__init__.py | 0
.../v4_client_py/chain/params/interface.py | 0
.../v4_client_py/chain/params/rest_client.py | 0
.../v4_client_py/chain/slashing/__init__.py | 0
.../v4_client_py/chain/slashing/interface.py | 0
.../v4_client_py/chain/slashing/rest_client.py | 0
.../v4_client_py/chain/staking/__init__.py | 0
.../v4_client_py/chain/staking/interface.py | 0
.../v4_client_py/chain/staking/rest_client.py | 0
.../v4_client_py/chain/tendermint/__init__.py | 0
.../v4_client_py/chain/tendermint/interface.py | 0
.../v4_client_py/chain/tendermint/rest_client.py | 0
.../v4_client_py/chain/tx/__init__.py | 0
.../v4_client_py/chain/tx/interface.py | 0
.../v4_client_py/chain/tx/rest_client.py | 0
.../v4_client_py/chain/upgrade/__init__.py | 0
.../v4_client_py/chain/upgrade/interface.py | 0
.../v4_client_py/chain/upgrade/rest_client.py | 0
.../v4_client_py/clients/__init__.py | 0
.../v4_client_py/clients/composer.py | 0
.../v4_client_py/clients/constants.py | 0
.../v4_client_py/clients/dydx_composite_client.py | 0
.../v4_client_py/clients/dydx_faucet_client.py | 0
.../v4_client_py/clients/dydx_indexer_client.py | 0
.../v4_client_py/clients/dydx_socket_client.py | 0
.../v4_client_py/clients/dydx_subaccount.py | 0
.../v4_client_py/clients/dydx_validator_client.py | 0
.../v4_client_py/clients/errors.py | 0
.../v4_client_py/clients/helpers/__init__.py | 0
.../v4_client_py/clients/helpers/chain_helpers.py | 0
.../v4_client_py/clients/helpers/request_helpers.py | 0
.../v4_client_py/clients/helpers/requests.py | 0
.../v4_client_py/clients/modules/__init__.py | 0
.../v4_client_py/clients/modules/account.py | 0
.../v4_client_py/clients/modules/get.py | 0
.../v4_client_py/clients/modules/markets.py | 0
.../v4_client_py/clients/modules/post.py | 0
.../v4_client_py/clients/modules/utility.py | 0
133 files changed, 0 insertions(+), 0 deletions(-)
rename {v4-client-py => v4-client-py-deprecated}/.gitignore (100%)
rename {v4-client-py => v4-client-py-deprecated}/.gitleaks.toml (100%)
rename {v4-client-py => v4-client-py-deprecated}/.gitleaksignore (100%)
rename {v4-client-py => v4-client-py-deprecated}/.vscode/launch.json (100%)
rename {v4-client-py => v4-client-py-deprecated}/.vscode/settings.json (100%)
rename {v4-client-py => v4-client-py-deprecated}/LICENSE (100%)
rename {v4-client-py => v4-client-py-deprecated}/README.md (100%)
rename {v4-client-py => v4-client-py-deprecated}/examples/README.md (100%)
rename {v4-client-py => v4-client-py-deprecated}/examples/__init__.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/examples/account_endpoints.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/examples/composite_example.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/examples/faucet_endpoint.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/examples/human_readable_orders.json (100%)
rename {v4-client-py => v4-client-py-deprecated}/examples/human_readable_short_term_orders.json (100%)
rename {v4-client-py => v4-client-py-deprecated}/examples/long_term_order_cancel_example.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/examples/markets_endpoints.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/examples/raw_orders.json (100%)
rename {v4-client-py => v4-client-py-deprecated}/examples/short_term_order_cancel_example.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/examples/short_term_order_composite_example.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/examples/transfer_example_deposit.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/examples/transfer_example_subaccount_transfer.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/examples/transfer_example_withdraw.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/examples/utility_endpoints.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/examples/utils.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/examples/validator_get_examples.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/examples/validator_post_examples.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/examples/wallet_address.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/examples/websocket_example.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/pyproject.toml (100%)
rename {v4-client-py => v4-client-py-deprecated}/pytest.ini (100%)
rename {v4-client-py => v4-client-py-deprecated}/pytest_integration.ini (100%)
rename {v4-client-py => v4-client-py-deprecated}/requirements-lint.txt (100%)
rename {v4-client-py => v4-client-py-deprecated}/requirements-publish.txt (100%)
rename {v4-client-py => v4-client-py-deprecated}/requirements-test.txt (100%)
rename {v4-client-py => v4-client-py-deprecated}/requirements.txt (100%)
rename {v4-client-py => v4-client-py-deprecated}/setup.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/tests/__init__.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/tests/constants.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/tests/test_indexer_markets_endpoints.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/tests/test_indexer_utility_endpoints.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/tests/test_request_helpers.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/tests/test_validator_get_endpoints.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/tests_integration/__init__.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/tests_integration/human_readable_orders.json (100%)
rename {v4-client-py => v4-client-py-deprecated}/tests_integration/raw_orders.json (100%)
rename {v4-client-py => v4-client-py-deprecated}/tests_integration/test_faucet.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/tests_integration/test_indexer_account_endpoints.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/tests_integration/test_trades.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/tests_integration/test_transfers.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/tests_integration/util.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/__init__.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/__init__.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/aerial/__init__.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/aerial/client/__init__.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/aerial/client/bank.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/aerial/client/distribution.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/aerial/client/staking.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/aerial/client/utils.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/aerial/coins.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/aerial/config.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/aerial/exceptions.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/aerial/faucet.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/aerial/gas.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/aerial/tx.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/aerial/tx_helpers.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/aerial/urls.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/aerial/wallet.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/auth/__init__.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/auth/interface.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/auth/rest_client.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/bank/__init__.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/bank/interface.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/bank/rest_client.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/common/__init__.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/common/rest_client.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/common/types.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/common/utils.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/crypto/__init__.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/crypto/address.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/crypto/hashfuncs.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/crypto/interface.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/crypto/keypairs.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/crypto/keypairs_bls.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/distribution/__init__.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/distribution/interface.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/distribution/rest_client.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/evidence/__init__.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/evidence/interface.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/evidence/rest_client.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/gov/__init__.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/gov/interface.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/gov/rest_client.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/mint/__init__.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/mint/interface.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/mint/rest_client.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/params/__init__.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/params/interface.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/params/rest_client.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/slashing/__init__.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/slashing/interface.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/slashing/rest_client.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/staking/__init__.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/staking/interface.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/staking/rest_client.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/tendermint/__init__.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/tendermint/interface.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/tendermint/rest_client.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/tx/__init__.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/tx/interface.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/tx/rest_client.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/upgrade/__init__.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/upgrade/interface.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/chain/upgrade/rest_client.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/clients/__init__.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/clients/composer.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/clients/constants.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/clients/dydx_composite_client.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/clients/dydx_faucet_client.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/clients/dydx_indexer_client.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/clients/dydx_socket_client.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/clients/dydx_subaccount.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/clients/dydx_validator_client.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/clients/errors.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/clients/helpers/__init__.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/clients/helpers/chain_helpers.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/clients/helpers/request_helpers.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/clients/helpers/requests.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/clients/modules/__init__.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/clients/modules/account.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/clients/modules/get.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/clients/modules/markets.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/clients/modules/post.py (100%)
rename {v4-client-py => v4-client-py-deprecated}/v4_client_py/clients/modules/utility.py (100%)
diff --git a/v4-client-py/.gitignore b/v4-client-py-deprecated/.gitignore
similarity index 100%
rename from v4-client-py/.gitignore
rename to v4-client-py-deprecated/.gitignore
diff --git a/v4-client-py/.gitleaks.toml b/v4-client-py-deprecated/.gitleaks.toml
similarity index 100%
rename from v4-client-py/.gitleaks.toml
rename to v4-client-py-deprecated/.gitleaks.toml
diff --git a/v4-client-py/.gitleaksignore b/v4-client-py-deprecated/.gitleaksignore
similarity index 100%
rename from v4-client-py/.gitleaksignore
rename to v4-client-py-deprecated/.gitleaksignore
diff --git a/v4-client-py/.vscode/launch.json b/v4-client-py-deprecated/.vscode/launch.json
similarity index 100%
rename from v4-client-py/.vscode/launch.json
rename to v4-client-py-deprecated/.vscode/launch.json
diff --git a/v4-client-py/.vscode/settings.json b/v4-client-py-deprecated/.vscode/settings.json
similarity index 100%
rename from v4-client-py/.vscode/settings.json
rename to v4-client-py-deprecated/.vscode/settings.json
diff --git a/v4-client-py/LICENSE b/v4-client-py-deprecated/LICENSE
similarity index 100%
rename from v4-client-py/LICENSE
rename to v4-client-py-deprecated/LICENSE
diff --git a/v4-client-py/README.md b/v4-client-py-deprecated/README.md
similarity index 100%
rename from v4-client-py/README.md
rename to v4-client-py-deprecated/README.md
diff --git a/v4-client-py/examples/README.md b/v4-client-py-deprecated/examples/README.md
similarity index 100%
rename from v4-client-py/examples/README.md
rename to v4-client-py-deprecated/examples/README.md
diff --git a/v4-client-py/examples/__init__.py b/v4-client-py-deprecated/examples/__init__.py
similarity index 100%
rename from v4-client-py/examples/__init__.py
rename to v4-client-py-deprecated/examples/__init__.py
diff --git a/v4-client-py/examples/account_endpoints.py b/v4-client-py-deprecated/examples/account_endpoints.py
similarity index 100%
rename from v4-client-py/examples/account_endpoints.py
rename to v4-client-py-deprecated/examples/account_endpoints.py
diff --git a/v4-client-py/examples/composite_example.py b/v4-client-py-deprecated/examples/composite_example.py
similarity index 100%
rename from v4-client-py/examples/composite_example.py
rename to v4-client-py-deprecated/examples/composite_example.py
diff --git a/v4-client-py/examples/faucet_endpoint.py b/v4-client-py-deprecated/examples/faucet_endpoint.py
similarity index 100%
rename from v4-client-py/examples/faucet_endpoint.py
rename to v4-client-py-deprecated/examples/faucet_endpoint.py
diff --git a/v4-client-py/examples/human_readable_orders.json b/v4-client-py-deprecated/examples/human_readable_orders.json
similarity index 100%
rename from v4-client-py/examples/human_readable_orders.json
rename to v4-client-py-deprecated/examples/human_readable_orders.json
diff --git a/v4-client-py/examples/human_readable_short_term_orders.json b/v4-client-py-deprecated/examples/human_readable_short_term_orders.json
similarity index 100%
rename from v4-client-py/examples/human_readable_short_term_orders.json
rename to v4-client-py-deprecated/examples/human_readable_short_term_orders.json
diff --git a/v4-client-py/examples/long_term_order_cancel_example.py b/v4-client-py-deprecated/examples/long_term_order_cancel_example.py
similarity index 100%
rename from v4-client-py/examples/long_term_order_cancel_example.py
rename to v4-client-py-deprecated/examples/long_term_order_cancel_example.py
diff --git a/v4-client-py/examples/markets_endpoints.py b/v4-client-py-deprecated/examples/markets_endpoints.py
similarity index 100%
rename from v4-client-py/examples/markets_endpoints.py
rename to v4-client-py-deprecated/examples/markets_endpoints.py
diff --git a/v4-client-py/examples/raw_orders.json b/v4-client-py-deprecated/examples/raw_orders.json
similarity index 100%
rename from v4-client-py/examples/raw_orders.json
rename to v4-client-py-deprecated/examples/raw_orders.json
diff --git a/v4-client-py/examples/short_term_order_cancel_example.py b/v4-client-py-deprecated/examples/short_term_order_cancel_example.py
similarity index 100%
rename from v4-client-py/examples/short_term_order_cancel_example.py
rename to v4-client-py-deprecated/examples/short_term_order_cancel_example.py
diff --git a/v4-client-py/examples/short_term_order_composite_example.py b/v4-client-py-deprecated/examples/short_term_order_composite_example.py
similarity index 100%
rename from v4-client-py/examples/short_term_order_composite_example.py
rename to v4-client-py-deprecated/examples/short_term_order_composite_example.py
diff --git a/v4-client-py/examples/transfer_example_deposit.py b/v4-client-py-deprecated/examples/transfer_example_deposit.py
similarity index 100%
rename from v4-client-py/examples/transfer_example_deposit.py
rename to v4-client-py-deprecated/examples/transfer_example_deposit.py
diff --git a/v4-client-py/examples/transfer_example_subaccount_transfer.py b/v4-client-py-deprecated/examples/transfer_example_subaccount_transfer.py
similarity index 100%
rename from v4-client-py/examples/transfer_example_subaccount_transfer.py
rename to v4-client-py-deprecated/examples/transfer_example_subaccount_transfer.py
diff --git a/v4-client-py/examples/transfer_example_withdraw.py b/v4-client-py-deprecated/examples/transfer_example_withdraw.py
similarity index 100%
rename from v4-client-py/examples/transfer_example_withdraw.py
rename to v4-client-py-deprecated/examples/transfer_example_withdraw.py
diff --git a/v4-client-py/examples/utility_endpoints.py b/v4-client-py-deprecated/examples/utility_endpoints.py
similarity index 100%
rename from v4-client-py/examples/utility_endpoints.py
rename to v4-client-py-deprecated/examples/utility_endpoints.py
diff --git a/v4-client-py/examples/utils.py b/v4-client-py-deprecated/examples/utils.py
similarity index 100%
rename from v4-client-py/examples/utils.py
rename to v4-client-py-deprecated/examples/utils.py
diff --git a/v4-client-py/examples/validator_get_examples.py b/v4-client-py-deprecated/examples/validator_get_examples.py
similarity index 100%
rename from v4-client-py/examples/validator_get_examples.py
rename to v4-client-py-deprecated/examples/validator_get_examples.py
diff --git a/v4-client-py/examples/validator_post_examples.py b/v4-client-py-deprecated/examples/validator_post_examples.py
similarity index 100%
rename from v4-client-py/examples/validator_post_examples.py
rename to v4-client-py-deprecated/examples/validator_post_examples.py
diff --git a/v4-client-py/examples/wallet_address.py b/v4-client-py-deprecated/examples/wallet_address.py
similarity index 100%
rename from v4-client-py/examples/wallet_address.py
rename to v4-client-py-deprecated/examples/wallet_address.py
diff --git a/v4-client-py/examples/websocket_example.py b/v4-client-py-deprecated/examples/websocket_example.py
similarity index 100%
rename from v4-client-py/examples/websocket_example.py
rename to v4-client-py-deprecated/examples/websocket_example.py
diff --git a/v4-client-py/pyproject.toml b/v4-client-py-deprecated/pyproject.toml
similarity index 100%
rename from v4-client-py/pyproject.toml
rename to v4-client-py-deprecated/pyproject.toml
diff --git a/v4-client-py/pytest.ini b/v4-client-py-deprecated/pytest.ini
similarity index 100%
rename from v4-client-py/pytest.ini
rename to v4-client-py-deprecated/pytest.ini
diff --git a/v4-client-py/pytest_integration.ini b/v4-client-py-deprecated/pytest_integration.ini
similarity index 100%
rename from v4-client-py/pytest_integration.ini
rename to v4-client-py-deprecated/pytest_integration.ini
diff --git a/v4-client-py/requirements-lint.txt b/v4-client-py-deprecated/requirements-lint.txt
similarity index 100%
rename from v4-client-py/requirements-lint.txt
rename to v4-client-py-deprecated/requirements-lint.txt
diff --git a/v4-client-py/requirements-publish.txt b/v4-client-py-deprecated/requirements-publish.txt
similarity index 100%
rename from v4-client-py/requirements-publish.txt
rename to v4-client-py-deprecated/requirements-publish.txt
diff --git a/v4-client-py/requirements-test.txt b/v4-client-py-deprecated/requirements-test.txt
similarity index 100%
rename from v4-client-py/requirements-test.txt
rename to v4-client-py-deprecated/requirements-test.txt
diff --git a/v4-client-py/requirements.txt b/v4-client-py-deprecated/requirements.txt
similarity index 100%
rename from v4-client-py/requirements.txt
rename to v4-client-py-deprecated/requirements.txt
diff --git a/v4-client-py/setup.py b/v4-client-py-deprecated/setup.py
similarity index 100%
rename from v4-client-py/setup.py
rename to v4-client-py-deprecated/setup.py
diff --git a/v4-client-py/tests/__init__.py b/v4-client-py-deprecated/tests/__init__.py
similarity index 100%
rename from v4-client-py/tests/__init__.py
rename to v4-client-py-deprecated/tests/__init__.py
diff --git a/v4-client-py/tests/constants.py b/v4-client-py-deprecated/tests/constants.py
similarity index 100%
rename from v4-client-py/tests/constants.py
rename to v4-client-py-deprecated/tests/constants.py
diff --git a/v4-client-py/tests/test_indexer_markets_endpoints.py b/v4-client-py-deprecated/tests/test_indexer_markets_endpoints.py
similarity index 100%
rename from v4-client-py/tests/test_indexer_markets_endpoints.py
rename to v4-client-py-deprecated/tests/test_indexer_markets_endpoints.py
diff --git a/v4-client-py/tests/test_indexer_utility_endpoints.py b/v4-client-py-deprecated/tests/test_indexer_utility_endpoints.py
similarity index 100%
rename from v4-client-py/tests/test_indexer_utility_endpoints.py
rename to v4-client-py-deprecated/tests/test_indexer_utility_endpoints.py
diff --git a/v4-client-py/tests/test_request_helpers.py b/v4-client-py-deprecated/tests/test_request_helpers.py
similarity index 100%
rename from v4-client-py/tests/test_request_helpers.py
rename to v4-client-py-deprecated/tests/test_request_helpers.py
diff --git a/v4-client-py/tests/test_validator_get_endpoints.py b/v4-client-py-deprecated/tests/test_validator_get_endpoints.py
similarity index 100%
rename from v4-client-py/tests/test_validator_get_endpoints.py
rename to v4-client-py-deprecated/tests/test_validator_get_endpoints.py
diff --git a/v4-client-py/tests_integration/__init__.py b/v4-client-py-deprecated/tests_integration/__init__.py
similarity index 100%
rename from v4-client-py/tests_integration/__init__.py
rename to v4-client-py-deprecated/tests_integration/__init__.py
diff --git a/v4-client-py/tests_integration/human_readable_orders.json b/v4-client-py-deprecated/tests_integration/human_readable_orders.json
similarity index 100%
rename from v4-client-py/tests_integration/human_readable_orders.json
rename to v4-client-py-deprecated/tests_integration/human_readable_orders.json
diff --git a/v4-client-py/tests_integration/raw_orders.json b/v4-client-py-deprecated/tests_integration/raw_orders.json
similarity index 100%
rename from v4-client-py/tests_integration/raw_orders.json
rename to v4-client-py-deprecated/tests_integration/raw_orders.json
diff --git a/v4-client-py/tests_integration/test_faucet.py b/v4-client-py-deprecated/tests_integration/test_faucet.py
similarity index 100%
rename from v4-client-py/tests_integration/test_faucet.py
rename to v4-client-py-deprecated/tests_integration/test_faucet.py
diff --git a/v4-client-py/tests_integration/test_indexer_account_endpoints.py b/v4-client-py-deprecated/tests_integration/test_indexer_account_endpoints.py
similarity index 100%
rename from v4-client-py/tests_integration/test_indexer_account_endpoints.py
rename to v4-client-py-deprecated/tests_integration/test_indexer_account_endpoints.py
diff --git a/v4-client-py/tests_integration/test_trades.py b/v4-client-py-deprecated/tests_integration/test_trades.py
similarity index 100%
rename from v4-client-py/tests_integration/test_trades.py
rename to v4-client-py-deprecated/tests_integration/test_trades.py
diff --git a/v4-client-py/tests_integration/test_transfers.py b/v4-client-py-deprecated/tests_integration/test_transfers.py
similarity index 100%
rename from v4-client-py/tests_integration/test_transfers.py
rename to v4-client-py-deprecated/tests_integration/test_transfers.py
diff --git a/v4-client-py/tests_integration/util.py b/v4-client-py-deprecated/tests_integration/util.py
similarity index 100%
rename from v4-client-py/tests_integration/util.py
rename to v4-client-py-deprecated/tests_integration/util.py
diff --git a/v4-client-py/v4_client_py/__init__.py b/v4-client-py-deprecated/v4_client_py/__init__.py
similarity index 100%
rename from v4-client-py/v4_client_py/__init__.py
rename to v4-client-py-deprecated/v4_client_py/__init__.py
diff --git a/v4-client-py/v4_client_py/chain/__init__.py b/v4-client-py-deprecated/v4_client_py/chain/__init__.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/__init__.py
rename to v4-client-py-deprecated/v4_client_py/chain/__init__.py
diff --git a/v4-client-py/v4_client_py/chain/aerial/__init__.py b/v4-client-py-deprecated/v4_client_py/chain/aerial/__init__.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/aerial/__init__.py
rename to v4-client-py-deprecated/v4_client_py/chain/aerial/__init__.py
diff --git a/v4-client-py/v4_client_py/chain/aerial/client/__init__.py b/v4-client-py-deprecated/v4_client_py/chain/aerial/client/__init__.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/aerial/client/__init__.py
rename to v4-client-py-deprecated/v4_client_py/chain/aerial/client/__init__.py
diff --git a/v4-client-py/v4_client_py/chain/aerial/client/bank.py b/v4-client-py-deprecated/v4_client_py/chain/aerial/client/bank.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/aerial/client/bank.py
rename to v4-client-py-deprecated/v4_client_py/chain/aerial/client/bank.py
diff --git a/v4-client-py/v4_client_py/chain/aerial/client/distribution.py b/v4-client-py-deprecated/v4_client_py/chain/aerial/client/distribution.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/aerial/client/distribution.py
rename to v4-client-py-deprecated/v4_client_py/chain/aerial/client/distribution.py
diff --git a/v4-client-py/v4_client_py/chain/aerial/client/staking.py b/v4-client-py-deprecated/v4_client_py/chain/aerial/client/staking.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/aerial/client/staking.py
rename to v4-client-py-deprecated/v4_client_py/chain/aerial/client/staking.py
diff --git a/v4-client-py/v4_client_py/chain/aerial/client/utils.py b/v4-client-py-deprecated/v4_client_py/chain/aerial/client/utils.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/aerial/client/utils.py
rename to v4-client-py-deprecated/v4_client_py/chain/aerial/client/utils.py
diff --git a/v4-client-py/v4_client_py/chain/aerial/coins.py b/v4-client-py-deprecated/v4_client_py/chain/aerial/coins.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/aerial/coins.py
rename to v4-client-py-deprecated/v4_client_py/chain/aerial/coins.py
diff --git a/v4-client-py/v4_client_py/chain/aerial/config.py b/v4-client-py-deprecated/v4_client_py/chain/aerial/config.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/aerial/config.py
rename to v4-client-py-deprecated/v4_client_py/chain/aerial/config.py
diff --git a/v4-client-py/v4_client_py/chain/aerial/exceptions.py b/v4-client-py-deprecated/v4_client_py/chain/aerial/exceptions.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/aerial/exceptions.py
rename to v4-client-py-deprecated/v4_client_py/chain/aerial/exceptions.py
diff --git a/v4-client-py/v4_client_py/chain/aerial/faucet.py b/v4-client-py-deprecated/v4_client_py/chain/aerial/faucet.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/aerial/faucet.py
rename to v4-client-py-deprecated/v4_client_py/chain/aerial/faucet.py
diff --git a/v4-client-py/v4_client_py/chain/aerial/gas.py b/v4-client-py-deprecated/v4_client_py/chain/aerial/gas.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/aerial/gas.py
rename to v4-client-py-deprecated/v4_client_py/chain/aerial/gas.py
diff --git a/v4-client-py/v4_client_py/chain/aerial/tx.py b/v4-client-py-deprecated/v4_client_py/chain/aerial/tx.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/aerial/tx.py
rename to v4-client-py-deprecated/v4_client_py/chain/aerial/tx.py
diff --git a/v4-client-py/v4_client_py/chain/aerial/tx_helpers.py b/v4-client-py-deprecated/v4_client_py/chain/aerial/tx_helpers.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/aerial/tx_helpers.py
rename to v4-client-py-deprecated/v4_client_py/chain/aerial/tx_helpers.py
diff --git a/v4-client-py/v4_client_py/chain/aerial/urls.py b/v4-client-py-deprecated/v4_client_py/chain/aerial/urls.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/aerial/urls.py
rename to v4-client-py-deprecated/v4_client_py/chain/aerial/urls.py
diff --git a/v4-client-py/v4_client_py/chain/aerial/wallet.py b/v4-client-py-deprecated/v4_client_py/chain/aerial/wallet.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/aerial/wallet.py
rename to v4-client-py-deprecated/v4_client_py/chain/aerial/wallet.py
diff --git a/v4-client-py/v4_client_py/chain/auth/__init__.py b/v4-client-py-deprecated/v4_client_py/chain/auth/__init__.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/auth/__init__.py
rename to v4-client-py-deprecated/v4_client_py/chain/auth/__init__.py
diff --git a/v4-client-py/v4_client_py/chain/auth/interface.py b/v4-client-py-deprecated/v4_client_py/chain/auth/interface.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/auth/interface.py
rename to v4-client-py-deprecated/v4_client_py/chain/auth/interface.py
diff --git a/v4-client-py/v4_client_py/chain/auth/rest_client.py b/v4-client-py-deprecated/v4_client_py/chain/auth/rest_client.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/auth/rest_client.py
rename to v4-client-py-deprecated/v4_client_py/chain/auth/rest_client.py
diff --git a/v4-client-py/v4_client_py/chain/bank/__init__.py b/v4-client-py-deprecated/v4_client_py/chain/bank/__init__.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/bank/__init__.py
rename to v4-client-py-deprecated/v4_client_py/chain/bank/__init__.py
diff --git a/v4-client-py/v4_client_py/chain/bank/interface.py b/v4-client-py-deprecated/v4_client_py/chain/bank/interface.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/bank/interface.py
rename to v4-client-py-deprecated/v4_client_py/chain/bank/interface.py
diff --git a/v4-client-py/v4_client_py/chain/bank/rest_client.py b/v4-client-py-deprecated/v4_client_py/chain/bank/rest_client.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/bank/rest_client.py
rename to v4-client-py-deprecated/v4_client_py/chain/bank/rest_client.py
diff --git a/v4-client-py/v4_client_py/chain/common/__init__.py b/v4-client-py-deprecated/v4_client_py/chain/common/__init__.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/common/__init__.py
rename to v4-client-py-deprecated/v4_client_py/chain/common/__init__.py
diff --git a/v4-client-py/v4_client_py/chain/common/rest_client.py b/v4-client-py-deprecated/v4_client_py/chain/common/rest_client.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/common/rest_client.py
rename to v4-client-py-deprecated/v4_client_py/chain/common/rest_client.py
diff --git a/v4-client-py/v4_client_py/chain/common/types.py b/v4-client-py-deprecated/v4_client_py/chain/common/types.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/common/types.py
rename to v4-client-py-deprecated/v4_client_py/chain/common/types.py
diff --git a/v4-client-py/v4_client_py/chain/common/utils.py b/v4-client-py-deprecated/v4_client_py/chain/common/utils.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/common/utils.py
rename to v4-client-py-deprecated/v4_client_py/chain/common/utils.py
diff --git a/v4-client-py/v4_client_py/chain/crypto/__init__.py b/v4-client-py-deprecated/v4_client_py/chain/crypto/__init__.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/crypto/__init__.py
rename to v4-client-py-deprecated/v4_client_py/chain/crypto/__init__.py
diff --git a/v4-client-py/v4_client_py/chain/crypto/address.py b/v4-client-py-deprecated/v4_client_py/chain/crypto/address.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/crypto/address.py
rename to v4-client-py-deprecated/v4_client_py/chain/crypto/address.py
diff --git a/v4-client-py/v4_client_py/chain/crypto/hashfuncs.py b/v4-client-py-deprecated/v4_client_py/chain/crypto/hashfuncs.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/crypto/hashfuncs.py
rename to v4-client-py-deprecated/v4_client_py/chain/crypto/hashfuncs.py
diff --git a/v4-client-py/v4_client_py/chain/crypto/interface.py b/v4-client-py-deprecated/v4_client_py/chain/crypto/interface.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/crypto/interface.py
rename to v4-client-py-deprecated/v4_client_py/chain/crypto/interface.py
diff --git a/v4-client-py/v4_client_py/chain/crypto/keypairs.py b/v4-client-py-deprecated/v4_client_py/chain/crypto/keypairs.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/crypto/keypairs.py
rename to v4-client-py-deprecated/v4_client_py/chain/crypto/keypairs.py
diff --git a/v4-client-py/v4_client_py/chain/crypto/keypairs_bls.py b/v4-client-py-deprecated/v4_client_py/chain/crypto/keypairs_bls.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/crypto/keypairs_bls.py
rename to v4-client-py-deprecated/v4_client_py/chain/crypto/keypairs_bls.py
diff --git a/v4-client-py/v4_client_py/chain/distribution/__init__.py b/v4-client-py-deprecated/v4_client_py/chain/distribution/__init__.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/distribution/__init__.py
rename to v4-client-py-deprecated/v4_client_py/chain/distribution/__init__.py
diff --git a/v4-client-py/v4_client_py/chain/distribution/interface.py b/v4-client-py-deprecated/v4_client_py/chain/distribution/interface.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/distribution/interface.py
rename to v4-client-py-deprecated/v4_client_py/chain/distribution/interface.py
diff --git a/v4-client-py/v4_client_py/chain/distribution/rest_client.py b/v4-client-py-deprecated/v4_client_py/chain/distribution/rest_client.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/distribution/rest_client.py
rename to v4-client-py-deprecated/v4_client_py/chain/distribution/rest_client.py
diff --git a/v4-client-py/v4_client_py/chain/evidence/__init__.py b/v4-client-py-deprecated/v4_client_py/chain/evidence/__init__.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/evidence/__init__.py
rename to v4-client-py-deprecated/v4_client_py/chain/evidence/__init__.py
diff --git a/v4-client-py/v4_client_py/chain/evidence/interface.py b/v4-client-py-deprecated/v4_client_py/chain/evidence/interface.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/evidence/interface.py
rename to v4-client-py-deprecated/v4_client_py/chain/evidence/interface.py
diff --git a/v4-client-py/v4_client_py/chain/evidence/rest_client.py b/v4-client-py-deprecated/v4_client_py/chain/evidence/rest_client.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/evidence/rest_client.py
rename to v4-client-py-deprecated/v4_client_py/chain/evidence/rest_client.py
diff --git a/v4-client-py/v4_client_py/chain/gov/__init__.py b/v4-client-py-deprecated/v4_client_py/chain/gov/__init__.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/gov/__init__.py
rename to v4-client-py-deprecated/v4_client_py/chain/gov/__init__.py
diff --git a/v4-client-py/v4_client_py/chain/gov/interface.py b/v4-client-py-deprecated/v4_client_py/chain/gov/interface.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/gov/interface.py
rename to v4-client-py-deprecated/v4_client_py/chain/gov/interface.py
diff --git a/v4-client-py/v4_client_py/chain/gov/rest_client.py b/v4-client-py-deprecated/v4_client_py/chain/gov/rest_client.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/gov/rest_client.py
rename to v4-client-py-deprecated/v4_client_py/chain/gov/rest_client.py
diff --git a/v4-client-py/v4_client_py/chain/mint/__init__.py b/v4-client-py-deprecated/v4_client_py/chain/mint/__init__.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/mint/__init__.py
rename to v4-client-py-deprecated/v4_client_py/chain/mint/__init__.py
diff --git a/v4-client-py/v4_client_py/chain/mint/interface.py b/v4-client-py-deprecated/v4_client_py/chain/mint/interface.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/mint/interface.py
rename to v4-client-py-deprecated/v4_client_py/chain/mint/interface.py
diff --git a/v4-client-py/v4_client_py/chain/mint/rest_client.py b/v4-client-py-deprecated/v4_client_py/chain/mint/rest_client.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/mint/rest_client.py
rename to v4-client-py-deprecated/v4_client_py/chain/mint/rest_client.py
diff --git a/v4-client-py/v4_client_py/chain/params/__init__.py b/v4-client-py-deprecated/v4_client_py/chain/params/__init__.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/params/__init__.py
rename to v4-client-py-deprecated/v4_client_py/chain/params/__init__.py
diff --git a/v4-client-py/v4_client_py/chain/params/interface.py b/v4-client-py-deprecated/v4_client_py/chain/params/interface.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/params/interface.py
rename to v4-client-py-deprecated/v4_client_py/chain/params/interface.py
diff --git a/v4-client-py/v4_client_py/chain/params/rest_client.py b/v4-client-py-deprecated/v4_client_py/chain/params/rest_client.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/params/rest_client.py
rename to v4-client-py-deprecated/v4_client_py/chain/params/rest_client.py
diff --git a/v4-client-py/v4_client_py/chain/slashing/__init__.py b/v4-client-py-deprecated/v4_client_py/chain/slashing/__init__.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/slashing/__init__.py
rename to v4-client-py-deprecated/v4_client_py/chain/slashing/__init__.py
diff --git a/v4-client-py/v4_client_py/chain/slashing/interface.py b/v4-client-py-deprecated/v4_client_py/chain/slashing/interface.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/slashing/interface.py
rename to v4-client-py-deprecated/v4_client_py/chain/slashing/interface.py
diff --git a/v4-client-py/v4_client_py/chain/slashing/rest_client.py b/v4-client-py-deprecated/v4_client_py/chain/slashing/rest_client.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/slashing/rest_client.py
rename to v4-client-py-deprecated/v4_client_py/chain/slashing/rest_client.py
diff --git a/v4-client-py/v4_client_py/chain/staking/__init__.py b/v4-client-py-deprecated/v4_client_py/chain/staking/__init__.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/staking/__init__.py
rename to v4-client-py-deprecated/v4_client_py/chain/staking/__init__.py
diff --git a/v4-client-py/v4_client_py/chain/staking/interface.py b/v4-client-py-deprecated/v4_client_py/chain/staking/interface.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/staking/interface.py
rename to v4-client-py-deprecated/v4_client_py/chain/staking/interface.py
diff --git a/v4-client-py/v4_client_py/chain/staking/rest_client.py b/v4-client-py-deprecated/v4_client_py/chain/staking/rest_client.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/staking/rest_client.py
rename to v4-client-py-deprecated/v4_client_py/chain/staking/rest_client.py
diff --git a/v4-client-py/v4_client_py/chain/tendermint/__init__.py b/v4-client-py-deprecated/v4_client_py/chain/tendermint/__init__.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/tendermint/__init__.py
rename to v4-client-py-deprecated/v4_client_py/chain/tendermint/__init__.py
diff --git a/v4-client-py/v4_client_py/chain/tendermint/interface.py b/v4-client-py-deprecated/v4_client_py/chain/tendermint/interface.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/tendermint/interface.py
rename to v4-client-py-deprecated/v4_client_py/chain/tendermint/interface.py
diff --git a/v4-client-py/v4_client_py/chain/tendermint/rest_client.py b/v4-client-py-deprecated/v4_client_py/chain/tendermint/rest_client.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/tendermint/rest_client.py
rename to v4-client-py-deprecated/v4_client_py/chain/tendermint/rest_client.py
diff --git a/v4-client-py/v4_client_py/chain/tx/__init__.py b/v4-client-py-deprecated/v4_client_py/chain/tx/__init__.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/tx/__init__.py
rename to v4-client-py-deprecated/v4_client_py/chain/tx/__init__.py
diff --git a/v4-client-py/v4_client_py/chain/tx/interface.py b/v4-client-py-deprecated/v4_client_py/chain/tx/interface.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/tx/interface.py
rename to v4-client-py-deprecated/v4_client_py/chain/tx/interface.py
diff --git a/v4-client-py/v4_client_py/chain/tx/rest_client.py b/v4-client-py-deprecated/v4_client_py/chain/tx/rest_client.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/tx/rest_client.py
rename to v4-client-py-deprecated/v4_client_py/chain/tx/rest_client.py
diff --git a/v4-client-py/v4_client_py/chain/upgrade/__init__.py b/v4-client-py-deprecated/v4_client_py/chain/upgrade/__init__.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/upgrade/__init__.py
rename to v4-client-py-deprecated/v4_client_py/chain/upgrade/__init__.py
diff --git a/v4-client-py/v4_client_py/chain/upgrade/interface.py b/v4-client-py-deprecated/v4_client_py/chain/upgrade/interface.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/upgrade/interface.py
rename to v4-client-py-deprecated/v4_client_py/chain/upgrade/interface.py
diff --git a/v4-client-py/v4_client_py/chain/upgrade/rest_client.py b/v4-client-py-deprecated/v4_client_py/chain/upgrade/rest_client.py
similarity index 100%
rename from v4-client-py/v4_client_py/chain/upgrade/rest_client.py
rename to v4-client-py-deprecated/v4_client_py/chain/upgrade/rest_client.py
diff --git a/v4-client-py/v4_client_py/clients/__init__.py b/v4-client-py-deprecated/v4_client_py/clients/__init__.py
similarity index 100%
rename from v4-client-py/v4_client_py/clients/__init__.py
rename to v4-client-py-deprecated/v4_client_py/clients/__init__.py
diff --git a/v4-client-py/v4_client_py/clients/composer.py b/v4-client-py-deprecated/v4_client_py/clients/composer.py
similarity index 100%
rename from v4-client-py/v4_client_py/clients/composer.py
rename to v4-client-py-deprecated/v4_client_py/clients/composer.py
diff --git a/v4-client-py/v4_client_py/clients/constants.py b/v4-client-py-deprecated/v4_client_py/clients/constants.py
similarity index 100%
rename from v4-client-py/v4_client_py/clients/constants.py
rename to v4-client-py-deprecated/v4_client_py/clients/constants.py
diff --git a/v4-client-py/v4_client_py/clients/dydx_composite_client.py b/v4-client-py-deprecated/v4_client_py/clients/dydx_composite_client.py
similarity index 100%
rename from v4-client-py/v4_client_py/clients/dydx_composite_client.py
rename to v4-client-py-deprecated/v4_client_py/clients/dydx_composite_client.py
diff --git a/v4-client-py/v4_client_py/clients/dydx_faucet_client.py b/v4-client-py-deprecated/v4_client_py/clients/dydx_faucet_client.py
similarity index 100%
rename from v4-client-py/v4_client_py/clients/dydx_faucet_client.py
rename to v4-client-py-deprecated/v4_client_py/clients/dydx_faucet_client.py
diff --git a/v4-client-py/v4_client_py/clients/dydx_indexer_client.py b/v4-client-py-deprecated/v4_client_py/clients/dydx_indexer_client.py
similarity index 100%
rename from v4-client-py/v4_client_py/clients/dydx_indexer_client.py
rename to v4-client-py-deprecated/v4_client_py/clients/dydx_indexer_client.py
diff --git a/v4-client-py/v4_client_py/clients/dydx_socket_client.py b/v4-client-py-deprecated/v4_client_py/clients/dydx_socket_client.py
similarity index 100%
rename from v4-client-py/v4_client_py/clients/dydx_socket_client.py
rename to v4-client-py-deprecated/v4_client_py/clients/dydx_socket_client.py
diff --git a/v4-client-py/v4_client_py/clients/dydx_subaccount.py b/v4-client-py-deprecated/v4_client_py/clients/dydx_subaccount.py
similarity index 100%
rename from v4-client-py/v4_client_py/clients/dydx_subaccount.py
rename to v4-client-py-deprecated/v4_client_py/clients/dydx_subaccount.py
diff --git a/v4-client-py/v4_client_py/clients/dydx_validator_client.py b/v4-client-py-deprecated/v4_client_py/clients/dydx_validator_client.py
similarity index 100%
rename from v4-client-py/v4_client_py/clients/dydx_validator_client.py
rename to v4-client-py-deprecated/v4_client_py/clients/dydx_validator_client.py
diff --git a/v4-client-py/v4_client_py/clients/errors.py b/v4-client-py-deprecated/v4_client_py/clients/errors.py
similarity index 100%
rename from v4-client-py/v4_client_py/clients/errors.py
rename to v4-client-py-deprecated/v4_client_py/clients/errors.py
diff --git a/v4-client-py/v4_client_py/clients/helpers/__init__.py b/v4-client-py-deprecated/v4_client_py/clients/helpers/__init__.py
similarity index 100%
rename from v4-client-py/v4_client_py/clients/helpers/__init__.py
rename to v4-client-py-deprecated/v4_client_py/clients/helpers/__init__.py
diff --git a/v4-client-py/v4_client_py/clients/helpers/chain_helpers.py b/v4-client-py-deprecated/v4_client_py/clients/helpers/chain_helpers.py
similarity index 100%
rename from v4-client-py/v4_client_py/clients/helpers/chain_helpers.py
rename to v4-client-py-deprecated/v4_client_py/clients/helpers/chain_helpers.py
diff --git a/v4-client-py/v4_client_py/clients/helpers/request_helpers.py b/v4-client-py-deprecated/v4_client_py/clients/helpers/request_helpers.py
similarity index 100%
rename from v4-client-py/v4_client_py/clients/helpers/request_helpers.py
rename to v4-client-py-deprecated/v4_client_py/clients/helpers/request_helpers.py
diff --git a/v4-client-py/v4_client_py/clients/helpers/requests.py b/v4-client-py-deprecated/v4_client_py/clients/helpers/requests.py
similarity index 100%
rename from v4-client-py/v4_client_py/clients/helpers/requests.py
rename to v4-client-py-deprecated/v4_client_py/clients/helpers/requests.py
diff --git a/v4-client-py/v4_client_py/clients/modules/__init__.py b/v4-client-py-deprecated/v4_client_py/clients/modules/__init__.py
similarity index 100%
rename from v4-client-py/v4_client_py/clients/modules/__init__.py
rename to v4-client-py-deprecated/v4_client_py/clients/modules/__init__.py
diff --git a/v4-client-py/v4_client_py/clients/modules/account.py b/v4-client-py-deprecated/v4_client_py/clients/modules/account.py
similarity index 100%
rename from v4-client-py/v4_client_py/clients/modules/account.py
rename to v4-client-py-deprecated/v4_client_py/clients/modules/account.py
diff --git a/v4-client-py/v4_client_py/clients/modules/get.py b/v4-client-py-deprecated/v4_client_py/clients/modules/get.py
similarity index 100%
rename from v4-client-py/v4_client_py/clients/modules/get.py
rename to v4-client-py-deprecated/v4_client_py/clients/modules/get.py
diff --git a/v4-client-py/v4_client_py/clients/modules/markets.py b/v4-client-py-deprecated/v4_client_py/clients/modules/markets.py
similarity index 100%
rename from v4-client-py/v4_client_py/clients/modules/markets.py
rename to v4-client-py-deprecated/v4_client_py/clients/modules/markets.py
diff --git a/v4-client-py/v4_client_py/clients/modules/post.py b/v4-client-py-deprecated/v4_client_py/clients/modules/post.py
similarity index 100%
rename from v4-client-py/v4_client_py/clients/modules/post.py
rename to v4-client-py-deprecated/v4_client_py/clients/modules/post.py
diff --git a/v4-client-py/v4_client_py/clients/modules/utility.py b/v4-client-py-deprecated/v4_client_py/clients/modules/utility.py
similarity index 100%
rename from v4-client-py/v4_client_py/clients/modules/utility.py
rename to v4-client-py-deprecated/v4_client_py/clients/modules/utility.py
From 604e41645924817c25a7d4eaa5081e0d2c0a5d01 Mon Sep 17 00:00:00 2001
From: samtin0x <40127309+samtin0x@users.noreply.github.com>
Date: Fri, 14 Jun 2024 16:43:11 +0200
Subject: [PATCH 2/7] Adding v4-client-py-v2
---
v4-client-py-v2/.gitignore | 171 +++
v4-client-py-v2/.pre-commit-config.yaml | 11 +
v4-client-py-v2/DIFF.md | 85 ++
v4-client-py-v2/README.md | 236 ++++
v4-client-py-v2/dydx_v4_client/__init__.py | 18 +
v4-client-py-v2/dydx_v4_client/config.py | 1 +
.../dydx_v4_client/faucet_client.py | 45 +
.../dydx_v4_client/indexer/__init__.py | 0
.../dydx_v4_client/indexer/rest/__init__.py | 1 +
.../dydx_v4_client/indexer/rest/constants.py | 79 ++
.../indexer/rest/indexer_client.py | 45 +
.../indexer/rest/modules/__init__.py | 0
.../indexer/rest/modules/account.py | 337 ++++++
.../indexer/rest/modules/markets.py | 135 +++
.../indexer/rest/modules/status.py | 38 +
.../indexer/rest/noble_client.py | 261 +++++
.../indexer/rest/shared/__init__.py | 0
.../indexer/rest/shared/rest.py | 37 +
.../indexer/rest/utils/__init__.py | 0
.../indexer/rest/utils/request_helpers.py | 23 +
.../dydx_v4_client/indexer/socket/__init__.py | 1 +
.../indexer/socket/websocket.py | 133 +++
v4-client-py-v2/dydx_v4_client/network.py | 52 +
.../dydx_v4_client/node/__init__.py | 0
.../dydx_v4_client/node/builder.py | 88 ++
v4-client-py-v2/dydx_v4_client/node/client.py | 678 +++++++++++
v4-client-py-v2/dydx_v4_client/node/fee.py | 37 +
v4-client-py-v2/dydx_v4_client/node/market.py | 78 ++
.../dydx_v4_client/node/message.py | 131 +++
v4-client-py-v2/dydx_v4_client/wallet.py | 47 +
v4-client-py-v2/examples/account_endpoints.py | 185 +++
v4-client-py-v2/examples/basic_adder.py | 229 ++++
v4-client-py-v2/examples/faucet_endpoint.py | 15 +
.../human_readable_short_term_orders.json | 43 +
.../long_term_order_cancel_example.py | 52 +
v4-client-py-v2/examples/raw_orders.json | 130 +++
.../short_term_order_cancel_example.py | 54 +
.../short_term_order_composite_example.py | 65 ++
.../examples/transfer_example_deposit.py | 24 +
.../examples/transfer_example_transfer.py | 23 +
.../examples/transfer_example_withdraw.py | 21 +
.../transfer_example_withdraw_other.py | 36 +
.../examples/validator_get_example.py | 197 ++++
.../examples/validator_post_example.py | 60 +
v4-client-py-v2/examples/websocket_example.py | 24 +
v4-client-py-v2/poetry.lock | 1014 +++++++++++++++++
v4-client-py-v2/pyproject.toml | 32 +
v4-client-py-v2/pytest.ini | 2 +
v4-client-py-v2/tests/__init__.py | 0
v4-client-py-v2/tests/conftest.py | 127 +++
v4-client-py-v2/tests/indexer/__init__.py | 0
.../tests/indexer/rest/__init__.py | 0
.../tests/indexer/rest/modules/__init__.py | 0
.../rest/modules/test_account_endpoints.py | 148 +++
.../rest/modules/test_markets_endpoints.py | 79 ++
.../rest/modules/test_status_endpoints.py | 29 +
.../tests/indexer/rest/test_noble_client.py | 50 +
.../tests/indexer/socket/__init__.py | 0
.../tests/indexer/socket/test_websocket.py | 141 +++
v4-client-py-v2/tests/test_faucet_client.py | 20 +
.../tests/test_mutating_node_client.py | 122 ++
.../tests/test_query_node_client.py | 176 +++
v4-client-py-v2/tests/test_v4_proto.py | 91 ++
63 files changed, 5957 insertions(+)
create mode 100644 v4-client-py-v2/.gitignore
create mode 100644 v4-client-py-v2/.pre-commit-config.yaml
create mode 100644 v4-client-py-v2/DIFF.md
create mode 100644 v4-client-py-v2/README.md
create mode 100644 v4-client-py-v2/dydx_v4_client/__init__.py
create mode 100644 v4-client-py-v2/dydx_v4_client/config.py
create mode 100644 v4-client-py-v2/dydx_v4_client/faucet_client.py
create mode 100644 v4-client-py-v2/dydx_v4_client/indexer/__init__.py
create mode 100644 v4-client-py-v2/dydx_v4_client/indexer/rest/__init__.py
create mode 100644 v4-client-py-v2/dydx_v4_client/indexer/rest/constants.py
create mode 100644 v4-client-py-v2/dydx_v4_client/indexer/rest/indexer_client.py
create mode 100644 v4-client-py-v2/dydx_v4_client/indexer/rest/modules/__init__.py
create mode 100644 v4-client-py-v2/dydx_v4_client/indexer/rest/modules/account.py
create mode 100644 v4-client-py-v2/dydx_v4_client/indexer/rest/modules/markets.py
create mode 100644 v4-client-py-v2/dydx_v4_client/indexer/rest/modules/status.py
create mode 100644 v4-client-py-v2/dydx_v4_client/indexer/rest/noble_client.py
create mode 100644 v4-client-py-v2/dydx_v4_client/indexer/rest/shared/__init__.py
create mode 100644 v4-client-py-v2/dydx_v4_client/indexer/rest/shared/rest.py
create mode 100644 v4-client-py-v2/dydx_v4_client/indexer/rest/utils/__init__.py
create mode 100644 v4-client-py-v2/dydx_v4_client/indexer/rest/utils/request_helpers.py
create mode 100644 v4-client-py-v2/dydx_v4_client/indexer/socket/__init__.py
create mode 100644 v4-client-py-v2/dydx_v4_client/indexer/socket/websocket.py
create mode 100644 v4-client-py-v2/dydx_v4_client/network.py
create mode 100644 v4-client-py-v2/dydx_v4_client/node/__init__.py
create mode 100644 v4-client-py-v2/dydx_v4_client/node/builder.py
create mode 100644 v4-client-py-v2/dydx_v4_client/node/client.py
create mode 100644 v4-client-py-v2/dydx_v4_client/node/fee.py
create mode 100644 v4-client-py-v2/dydx_v4_client/node/market.py
create mode 100644 v4-client-py-v2/dydx_v4_client/node/message.py
create mode 100644 v4-client-py-v2/dydx_v4_client/wallet.py
create mode 100644 v4-client-py-v2/examples/account_endpoints.py
create mode 100644 v4-client-py-v2/examples/basic_adder.py
create mode 100644 v4-client-py-v2/examples/faucet_endpoint.py
create mode 100644 v4-client-py-v2/examples/human_readable_short_term_orders.json
create mode 100644 v4-client-py-v2/examples/long_term_order_cancel_example.py
create mode 100644 v4-client-py-v2/examples/raw_orders.json
create mode 100644 v4-client-py-v2/examples/short_term_order_cancel_example.py
create mode 100644 v4-client-py-v2/examples/short_term_order_composite_example.py
create mode 100644 v4-client-py-v2/examples/transfer_example_deposit.py
create mode 100644 v4-client-py-v2/examples/transfer_example_transfer.py
create mode 100644 v4-client-py-v2/examples/transfer_example_withdraw.py
create mode 100644 v4-client-py-v2/examples/transfer_example_withdraw_other.py
create mode 100644 v4-client-py-v2/examples/validator_get_example.py
create mode 100644 v4-client-py-v2/examples/validator_post_example.py
create mode 100644 v4-client-py-v2/examples/websocket_example.py
create mode 100644 v4-client-py-v2/poetry.lock
create mode 100644 v4-client-py-v2/pyproject.toml
create mode 100644 v4-client-py-v2/pytest.ini
create mode 100644 v4-client-py-v2/tests/__init__.py
create mode 100644 v4-client-py-v2/tests/conftest.py
create mode 100644 v4-client-py-v2/tests/indexer/__init__.py
create mode 100644 v4-client-py-v2/tests/indexer/rest/__init__.py
create mode 100644 v4-client-py-v2/tests/indexer/rest/modules/__init__.py
create mode 100644 v4-client-py-v2/tests/indexer/rest/modules/test_account_endpoints.py
create mode 100644 v4-client-py-v2/tests/indexer/rest/modules/test_markets_endpoints.py
create mode 100644 v4-client-py-v2/tests/indexer/rest/modules/test_status_endpoints.py
create mode 100644 v4-client-py-v2/tests/indexer/rest/test_noble_client.py
create mode 100644 v4-client-py-v2/tests/indexer/socket/__init__.py
create mode 100644 v4-client-py-v2/tests/indexer/socket/test_websocket.py
create mode 100644 v4-client-py-v2/tests/test_faucet_client.py
create mode 100644 v4-client-py-v2/tests/test_mutating_node_client.py
create mode 100644 v4-client-py-v2/tests/test_query_node_client.py
create mode 100644 v4-client-py-v2/tests/test_v4_proto.py
diff --git a/v4-client-py-v2/.gitignore b/v4-client-py-v2/.gitignore
new file mode 100644
index 00000000..2ae665f0
--- /dev/null
+++ b/v4-client-py-v2/.gitignore
@@ -0,0 +1,171 @@
+### Python ###
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+# For a library or package, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# poetry
+# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+# This is especially recommended for binary packages to ensure reproducibility, and is more
+# commonly ignored for libraries.
+# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+
+# pdm
+# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+#pdm.lock
+# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+# in version control.
+# https://pdm.fming.dev/#use-with-ide
+.pdm.toml
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+# and can be added to the global gitignore or merged into this file. For a more nuclear
+# option (not recommended) you can uncomment the following to ignore the entire idea folder.
+#.idea/
+
+### Python Patch ###
+# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
+poetry.toml
+
+# ruff
+.ruff_cache/
+
+# LSP config files
+pyrightconfig.json
diff --git a/v4-client-py-v2/.pre-commit-config.yaml b/v4-client-py-v2/.pre-commit-config.yaml
new file mode 100644
index 00000000..6e9349ef
--- /dev/null
+++ b/v4-client-py-v2/.pre-commit-config.yaml
@@ -0,0 +1,11 @@
+repos:
+ - repo: https://github.com/psf/black
+ rev: 24.4.0
+ hooks:
+ - id: black
+ language_version: python3.12
+ - repo: https://github.com/pycqa/isort
+ rev: 5.13.2
+ hooks:
+ - id: isort
+ name: isort (python)
diff --git a/v4-client-py-v2/DIFF.md b/v4-client-py-v2/DIFF.md
new file mode 100644
index 00000000..c4287430
--- /dev/null
+++ b/v4-client-py-v2/DIFF.md
@@ -0,0 +1,85 @@
+## Comparison of Differences
+
+### `Indexer` Coverage Analysis
+
+This table compares the method names in the Python client with those found in the API documentation.
+
+Symbols:
+`=` - The method names are the same in both the client and API
+`-` - The method is missing from the client library
+
+| Method Name (sync client) | API Method (docs) | Method | Path |
+| ---------------------------------- | -------------------- | ------ | ------------------------------------ |
+| get_subaccounts | GetAddress | GET | /addresses |
+| get_subaccount = | GetSubaccount | GET | /addresses/{}/subaccountNumber/{} |
+| get_subaccount_asset_positions | GetAssetPositions | GET | /assetPositions |
+| get_perpetual_market_candles | GetCandles | GET | /candles/perpetualMarkets |
+| get_screen | Screen | GET | /screen |
+| get_subaccount_fills | GetFills | GET | /fills |
+| get_height = | GetHeight | GET | /height |
+| get_perpetual_market_funding | GetHistoricalFunding | GET | /historicalFunding |
+| get_subaccount_historical_pnls | GetHistoricalPnl | GET | /historical-pnl |
+| get_perpetual_market_orderbook | GetPerpetualMarket | GET | /orderbooks/perpetualMarket |
+| get_subaccount_orders | ListOrders | GET | /orders |
+| get_order = | GetOrder | GET | /orders/{} |
+| get_perpetual_markets | ListPerpetualMarkets | GET | /perpetualMarkets |
+| get_subaccount_perpetual_positions | ListPositions | GET | /perpetualPositions |
+| get_perpetual_markets_sparklines | Get | GET | /sparklines |
+| get_time = | GetTime | GET | /time |
+| get_perpetual_market_trades | GetTrades | GET | /trades/perpetualMarket |
+| get_subaccount_transfers | GetTransfers | GET | /transfers |
+| - (no Py, no TS) | GetTradingRewards | GET | /historicalBlockTradingRewards |
+| - (no Py, no TS) | GetAggregations | GET | /historicalTradingRewardAggregations |
+
+#### Categorization:
+
+Should the methods be grouped into `Account`, `Market`, and `Utility` sections?
+
+### Data Types Analysis
+
+#### `MarketType`
+
+- The `CROSS` enumeration option is not documented.
+
+#### `PerpetualMarketResponse`
+
+- The `openInterestLowerCap` and `openInterestUpperCap` fields are always empty and not documented. Are they new, optional, or obsolete?
+
+#### `PerpetualPositionResponseObject`
+
+- `subaccount_number` is present but undocumented.
+
+#### `FillResponseObject`
+
+- Contains `subaccountNumber`, but it is not documented.
+
+### Method-Specific Observations
+
+#### `ListPerpetualMarkets`
+
+- The `limit` parameter is optional.
+
+#### `GetTrades`
+
+- The `limit` parameter is optional.
+
+#### `GetCandles`
+
+- The `limit` parameter is optional.
+
+#### `GetAddress`
+
+- Although the implementation includes a `limit` parameter, it is undocumented.
+
+#### `GetSubaccount`
+
+- Returns `SubaccountResponse`, not `SubaccountResponseObject`. The former is a map with a single `subaccount` field.
+- The `limit` and `status` parameters are optional.
+
+#### `GetAssetPositions`
+
+- Additional parameters similar to `ListPositions` exist but are undocumented.
+
+#### `ListOrders`
+
+- The `OrderResponseObject` includes an optional `goodTilBlock` field that is not present in responses.
diff --git a/v4-client-py-v2/README.md b/v4-client-py-v2/README.md
new file mode 100644
index 00000000..17907035
--- /dev/null
+++ b/v4-client-py-v2/README.md
@@ -0,0 +1,236 @@
+
+
+# Python Client (async) for dYdX (v4 API)
+
+## Quick links
+
+
+
+### 📘 [Documentation](https://docs.dydx.exchange)
+### 📦 [Other implementations](https://github.com/dydxprotocol/v4-clients)
+
+
+
+## Install
+Install from github:
+
+```bash
+pip install git+https://github.com/NethermindEth/dydx-v4-client
+```
+
+## Quickstart
+The package allows asynchronous programming so any script using it has to be run using asyncio.run:
+
+```python
+import asyncio
+
+
+async def main():
+ pass
+
+
+asyncio.run(main())
+```
+
+### Node
+`NodeClient` allows to send transactions and fetch node state. E.g. you can deposit funds using the `deposit` method:
+
+https://github.com/NethermindEth/dydx-v4-client/blob/f8be7bf9165fb052e831fcafb8086d14e5af13aa/examples/transfer_example_deposit.py#L1-L24
+
+**Note:** It's possible to create a read only node client which doesn't allow to send transactions:
+```python
+from dydx_v4_client import QueryNodeClient
+
+
+node = await QueryNodeClient.connect("https://dydx-ops-rpc.kingnodes.com:443")
+```
+
+### REST Indexer
+`IndexerClient` allows to fetch data from indexer:
+
+```python
+import asyncio
+
+from dydx_v4_client.indexer.rest import IndexerClient
+from dydx_v4_client.network import TESTNET
+
+
+async def test_account():
+ indexer = IndexerClient(TESTNET.rest_indexer)
+
+ print(await indexer.account.get_subaccounts("dydx14zzueazeh0hj67cghhf9jypslcf9sh2n5k6art"))
+```
+
+### Websocket indexer
+Websocket indexer allows to subscribe to channels to obtain live updates:
+
+https://github.com/NethermindEth/dydx-v4-client/blob/18eb769dde2a8691fc13445a34f46f0ecb266ec8/examples/websocket_example.py#L1-L24
+
+### Networks
+A set of predefined networks may be imported:
+
+```python
+from dydx_v4_client.network import TESTNET, MAINNET, LOCAL
+```
+
+If you want to use a custom API you can create a network directly:
+```python
+from dydx_v4_client.network import Network
+
+CUSTOM_MAINNET = Network(
+ NodeConfig(
+ "dydx-mainnet-1",
+ "https://dydx-ops-rpc.kingnodes.com:443",
+ "adydx",
+ "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
+ ),
+ "https://indexer.dydx.trade",
+ "wss://indexer.dydx.trade/v4/ws",
+)
+```
+Or provide the URL directly to the client, e.g.:
+```python
+indexer = IndexerClient("https://indexer.dydx.trade")
+```
+### Faucet
+Faucet allows to obtain usdc on testnet. To use it create `FaucetClient`:
+https://github.com/NethermindEth/dydx-v4-client/blob/18eb769dde2a8691fc13445a34f46f0ecb266ec8/examples/faucet_endpoint.py#L1-L15
+
+### Placing order
+To place order first you have to build the order.
+
+To do this you can the direct interface:
+```python
+order(
+ id,
+ side,
+ quantums,
+ subticks,
+ time_in_force,
+ reduce_only,
+ good_til_block,
+ good_til_block_time
+)
+```
+
+Or market based calculator:
+```python
+from dydx_v4_client.node.market import Market
+from dydx_v4_client import MAX_CLIENT_ID, NodeClient, OrderFlags
+
+
+MARKET_ID = "ETH-USD"
+ADDRESS = "dydx14zzueazeh0hj67cghhf9jypslcf9sh2n5k6art"
+
+async def get_order():
+ indexer = IndexerClient(TESTNET.rest_indexer)
+
+ market = Market(
+ (await indexer.markets.get_perpetual_markets(MARKET_ID))["markets"][MARKET_ID]
+ )
+
+ order_id = market.order_id(
+ ADDRESS, 0, random.randint(0, MAX_CLIENT_ID), OrderFlags.SHORT_TERM
+ )
+ return market.order(
+ order_id,
+ side,
+ size,
+ price,
+ time_in_force,
+ reduce_only,
+ good_til_block,
+ good_til_block_time
+ )
+```
+
+The constructed order may then be provided to `NodeClient.place_order`:
+```python
+await node.place_order(
+ wallet,
+ order
+)
+```
+### Examples
+For more examples see [examples directory](/examples). Some examples may require installation of additional packages in order to work.
+
+## Changes
+
+### Migration
+
+If you are transitioning from a previous version of the Python client, please note the following differences:
+
+#### NodeClient
+
+`ValidatorClient` is renamed to `NodeClient`.
+
+All provided methods are asynchronous.
+
+Methods are available directly, no methods `get` or `post` needed, since the client uses inheritance, and consists of three layers:
+
+- `QueryNodeClient`, the basic layer that send queries to a node
+- `MutatingNodeClient` - the extension on top of the query client, that support transation simulation and sending
+- `NodeClient` the toppest layer that provides methods to control orders
+
+For parameters raw types used.
+
+For construcint order the `Market` builder is provided, that
+helps to calculate quantums and subticks values.
+
+#### IndexerClient
+
+The `IndexerClient` has the similar structure, but provides
+asynchronous methods as well.
+
+#### IndexerSocket
+
+The `SocketClient` is replaced with the `IndexerSocket` that provides separate channels concept and allow to add per-channel processing.
+
+### Key Improvements
+
+The latest version of the Python async client for dYdX offers notable enhancements over previous iterations. These improvements make it a more efficient tool for trading and integration.
+
+#### Asynchronous Execution
+
+The methods leverage Python's async features, allowing you to fully harness concurrency benefits. This approach optimizes resource usage, minimizes unnecessary threads, and reduces latency.
+
+#### Enhanced Type Hints
+
+Expanded type hint coverage enhances code readability and provides better tooling support. Additionally, it helps detect errors early during development.
+
+#### API Reflection
+
+The client closely mirrors the dYdX API, enabling seamless access to the exchange's features and parameters. This makes integrating the client with your applications intuitive and straightforward.
+
+#### Lightweight Implementation
+The client is built using pure Python libraries and maintains a thin, transparent layer that follows the Principle of Least Astonishment (POLA). This ensures explicit behavior and gives you greater control.
+
+#### MIT License
+Licensed under the permissive MIT license, the client can be easily integrated into your software projects without restrictive legal hurdles.
+
+## Development
+The project is divided into three main parts:
+* node - contains the `NodeClient`, transaction builder and other utilities
+* indexer - contains rest api indexer client and websocket indexer client
+* faucet - contains faucet client
+
+### Installing from source
+The project employs [`poetry`](https://python-poetry.org/). To install dependencies, run:
+
+```bash
+poetry install
+```
+
+### Testing
+To run tests use:
+
+```bash
+poetry run pytest
+```
+
+## Acknowledgements
+Developed by:
+- [Saul M.](https://github.com/samtin0x)
+- [Piotr P.](https://github.com/piwonskp)
+
+For more details, check out the [grant](https://www.dydxgrants.com/grants/python-trading-client).
diff --git a/v4-client-py-v2/dydx_v4_client/__init__.py b/v4-client-py-v2/dydx_v4_client/__init__.py
new file mode 100644
index 00000000..94c4918c
--- /dev/null
+++ b/v4-client-py-v2/dydx_v4_client/__init__.py
@@ -0,0 +1,18 @@
+from enum import IntEnum
+
+from v4_proto.dydxprotocol.clob.order_pb2 import Order
+
+from dydx_v4_client.faucet_client import FaucetClient
+from dydx_v4_client.indexer.rest.indexer_client import IndexerClient
+from dydx_v4_client.indexer.socket.websocket import IndexerSocket
+from dydx_v4_client.node.client import NodeClient, QueryNodeClient
+from dydx_v4_client.wallet import Wallet
+
+
+class OrderFlags(IntEnum):
+ SHORT_TERM = 0
+ LONG_TERM = 64
+ CONDITIONAL = 32
+
+
+MAX_CLIENT_ID = 2**32 - 1
diff --git a/v4-client-py-v2/dydx_v4_client/config.py b/v4-client-py-v2/dydx_v4_client/config.py
new file mode 100644
index 00000000..729bb27d
--- /dev/null
+++ b/v4-client-py-v2/dydx_v4_client/config.py
@@ -0,0 +1 @@
+GAS_MULTIPLIER = 1.4
diff --git a/v4-client-py-v2/dydx_v4_client/faucet_client.py b/v4-client-py-v2/dydx_v4_client/faucet_client.py
new file mode 100644
index 00000000..00e6e9c5
--- /dev/null
+++ b/v4-client-py-v2/dydx_v4_client/faucet_client.py
@@ -0,0 +1,45 @@
+from typing import Dict, Optional
+
+import httpx
+
+from dydx_v4_client.indexer.rest.constants import DEFAULT_API_TIMEOUT
+from dydx_v4_client.indexer.rest.shared.rest import RestClient
+
+
+class FaucetClient(RestClient):
+ def __init__(self, faucet_url: str, api_timeout: Optional[float] = None):
+ self.api_timeout = api_timeout or DEFAULT_API_TIMEOUT
+ super().__init__(faucet_url, self.api_timeout)
+
+ async def fill(
+ self,
+ address: str,
+ subaccount_number: int,
+ amount: float,
+ headers: Optional[Dict] = None,
+ ) -> httpx.Response:
+ """
+ If testnet, add USDC to a subaccount.
+ """
+ uri = "/faucet/tokens"
+ body = {
+ "address": address,
+ "subaccountNumber": subaccount_number,
+ "amount": amount,
+ }
+ return await self.post(uri, body=body, headers=headers or {})
+
+ async def fill_native(
+ self,
+ address: str,
+ headers: Optional[Dict] = None,
+ ) -> httpx.Response:
+ """
+ If testnet, add USDC to address.
+ """
+
+ uri = "/faucet/native-token"
+ body = {
+ "address": address,
+ }
+ return await self.post(uri, body=body, headers=headers or {})
diff --git a/v4-client-py-v2/dydx_v4_client/indexer/__init__.py b/v4-client-py-v2/dydx_v4_client/indexer/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/v4-client-py-v2/dydx_v4_client/indexer/rest/__init__.py b/v4-client-py-v2/dydx_v4_client/indexer/rest/__init__.py
new file mode 100644
index 00000000..e25297d8
--- /dev/null
+++ b/v4-client-py-v2/dydx_v4_client/indexer/rest/__init__.py
@@ -0,0 +1 @@
+from dydx_v4_client.indexer.rest.indexer_client import IndexerClient
diff --git a/v4-client-py-v2/dydx_v4_client/indexer/rest/constants.py b/v4-client-py-v2/dydx_v4_client/indexer/rest/constants.py
new file mode 100644
index 00000000..92be9dc9
--- /dev/null
+++ b/v4-client-py-v2/dydx_v4_client/indexer/rest/constants.py
@@ -0,0 +1,79 @@
+# Chain Constants
+GOV_MODULE_ADDRESS = "dydx10d07y265gmmuvt4z0w9aw880jnsr700jnmapky"
+DELAYMSG_MODULE_ADDRESS = "dydx1mkkvp26dngu6n8rmalaxyp3gwkjuzztq5zx6tr"
+
+
+# Market Statistic Day Types
+class MarketStatisticDay:
+ ONE = "1"
+ SEVEN = "7"
+ THIRTY = "30"
+
+
+# Order Types
+class OrderType:
+ LIMIT = "LIMIT"
+ MARKET = "MARKET"
+ STOP_LIMIT = "STOP_LIMIT"
+ TAKE_PROFIT_LIMIT = "TAKE_PROFIT"
+ STOP_MARKET = "STOP_MARKET"
+ TAKE_PROFIT_MARKET = "TAKE_PROFIT_MARKET"
+
+
+# Order Side
+class OrderSide:
+ BUY = "BUY"
+ SELL = "SELL"
+
+
+# Order TimeInForce
+class OrderTimeInForce:
+ GTT = "TIME_IN_FORCE_UNSPECIFIED"
+ IOC = "TIME_IN_FORCE_IOC"
+ FOK = "TIME_IN_FORCE_FILL_OR_KILL"
+
+
+# Order Execution
+class OrderExecution:
+ DEFAULT = "DEFAULT"
+ IOC = "IOC"
+ FOK = "FOK"
+ POST_ONLY = "POST_ONLY"
+
+
+# Order Status
+class OrderStatus:
+ BEST_EFFORT_OPENED = "BEST_EFFORT_OPENED"
+ OPEN = "OPEN"
+ FILLED = "FILLED"
+ BEST_EFFORT_CANCELED = "BEST_EFFORT_CANCELED"
+ CANCELED = "CANCELED"
+
+
+class TickerType:
+ PERPETUAL = "PERPETUAL" # Only PERPETUAL is supported right now
+
+
+class PositionStatus:
+ OPEN = "OPEN"
+ CLOSED = "CLOSED"
+ LIQUIDATED = "LIQUIDATED"
+
+
+# Time Period for Sparklines
+class TimePeriod:
+ ONE_DAY = "ONE_DAY"
+ SEVEN_DAYS = "SEVEN_DAYS"
+
+
+class TradingRewardAggregationPeriod:
+ DAILY = "DAILY"
+ WEEKLY = "WEEKLY"
+ MONTHLY = "MONTHLY"
+
+
+# API Defaults
+DEFAULT_API_TIMEOUT = 3_000
+MAX_MEMO_CHARACTERS = 256
+SHORT_BLOCK_WINDOW = 20
+SHORT_BLOCK_FORWARD = 3
diff --git a/v4-client-py-v2/dydx_v4_client/indexer/rest/indexer_client.py b/v4-client-py-v2/dydx_v4_client/indexer/rest/indexer_client.py
new file mode 100644
index 00000000..5774e28e
--- /dev/null
+++ b/v4-client-py-v2/dydx_v4_client/indexer/rest/indexer_client.py
@@ -0,0 +1,45 @@
+from typing import Optional
+
+from .constants import DEFAULT_API_TIMEOUT
+from .modules.account import AccountClient
+from .modules.markets import MarketsClient
+from .modules.status import StatusClient
+
+
+class IndexerClient:
+ """
+ Client for Indexer
+ """
+
+ def __init__(self, host: str, api_timeout: Optional[float] = None):
+ api_timeout = api_timeout or DEFAULT_API_TIMEOUT
+ self._markets = MarketsClient(host, api_timeout)
+ self._account = AccountClient(host, api_timeout)
+ self._status = StatusClient(host, api_timeout)
+
+ @property
+ def markets(self) -> MarketsClient:
+ """
+ Get the public module, used for interacting with public endpoints.
+
+ Returns:
+ The public module
+ """
+ return self._markets
+
+ @property
+ def account(self) -> AccountClient:
+ """
+ Get the private module, used for interacting with private endpoints.
+
+ Returns:
+ The private module
+ """
+ return self._account
+
+ @property
+ def utility(self) -> StatusClient:
+ """
+ Get the status module, used for interacting with non-market public endpoints.
+ """
+ return self._status
diff --git a/v4-client-py-v2/dydx_v4_client/indexer/rest/modules/__init__.py b/v4-client-py-v2/dydx_v4_client/indexer/rest/modules/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/v4-client-py-v2/dydx_v4_client/indexer/rest/modules/account.py b/v4-client-py-v2/dydx_v4_client/indexer/rest/modules/account.py
new file mode 100644
index 00000000..93c94e4b
--- /dev/null
+++ b/v4-client-py-v2/dydx_v4_client/indexer/rest/modules/account.py
@@ -0,0 +1,337 @@
+from typing import Any, Optional
+
+from dydx_v4_client.indexer.rest.shared.rest import RestClient
+
+from ..constants import (
+ OrderSide,
+ OrderStatus,
+ OrderType,
+ PositionStatus,
+ TickerType,
+ TradingRewardAggregationPeriod,
+)
+
+
+class AccountClient(RestClient):
+
+ async def get_subaccounts(
+ self,
+ address: str,
+ limit: Optional[int] = None,
+ ) -> Any:
+ """
+ Retrieves subaccounts for the specified address.
+
+ Args:
+ address (str): The address.
+ limit (Optional[int]): The maximum number of subaccounts to retrieve.
+
+ Returns:
+ Any: The subaccounts data.
+ """
+ uri = f"/v4/addresses/{address}"
+ return await self.get(uri, params={"limit": limit})
+
+ async def get_subaccount(
+ self,
+ address: str,
+ subaccount_number: int,
+ ) -> Any:
+ """
+ Retrieves a specific subaccount for the specified address and subaccount number.
+
+ Args:
+ address (str): The address.
+ subaccount_number (int): The subaccount number.
+
+ Returns:
+ Any: The subaccount data.
+ """
+ uri = f"/v4/addresses/{address}/subaccountNumber/{subaccount_number}"
+ return await self.get(uri)
+
+ async def get_subaccount_perpetual_positions(
+ self,
+ address: str,
+ subaccount_number: int,
+ status: Optional[PositionStatus] = None,
+ limit: Optional[int] = None,
+ created_before_or_at_height: Optional[int] = None,
+ created_before_or_at: Optional[str] = None,
+ ) -> Any:
+ """
+ Retrieves perpetual positions for a specific subaccount.
+
+ Args:
+ address (str): The address.
+ subaccount_number (int): The subaccount number.
+ status (Optional[PositionStatus]): The position status filter.
+ limit (Optional[int]): The maximum number of positions to retrieve.
+ created_before_or_at_height (Optional[int]): The block height filter for positions created before or at.
+ created_before_or_at (Optional[str]): The timestamp filter for positions created before or at.
+
+ Returns:
+ Any: The perpetual positions data.
+ """
+ uri = "/v4/perpetualPositions"
+ return await self.get(
+ uri,
+ params={
+ "address": address,
+ "subaccountNumber": subaccount_number,
+ "status": status,
+ "limit": limit,
+ "createdBeforeOrAtHeight": created_before_or_at_height,
+ "createdBeforeOrAt": created_before_or_at,
+ },
+ )
+
+ async def get_subaccount_asset_positions(
+ self,
+ address: str,
+ subaccount_number: int,
+ status: Optional[PositionStatus] = None,
+ limit: Optional[int] = None,
+ created_before_or_at_height: Optional[int] = None,
+ created_before_or_at: Optional[str] = None,
+ ) -> Any:
+ """
+ Retrieves asset positions for a specific subaccount.
+
+ Args:
+ address (str): The address.
+ subaccount_number (int): The subaccount number.
+ status (Optional[PositionStatus]): The position status filter.
+ limit (Optional[int]): The maximum number of positions to retrieve.
+ created_before_or_at_height (Optional[int]): The block height filter for positions created before or at.
+ created_before_or_at (Optional[str]): The timestamp filter for positions created before or at.
+
+ Returns:
+ Any: The asset positions data.
+ """
+ uri = "/v4/assetPositions"
+ return await self.get(
+ uri,
+ params={
+ "address": address,
+ "subaccountNumber": subaccount_number,
+ "status": status,
+ "limit": limit,
+ "createdBeforeOrAtHeight": created_before_or_at_height,
+ "createdBeforeOrAt": created_before_or_at,
+ },
+ )
+
+ async def get_subaccount_transfers(
+ self,
+ address: str,
+ subaccount_number: int,
+ limit: Optional[int] = None,
+ created_before_or_at_height: Optional[int] = None,
+ created_before_or_at: Optional[str] = None,
+ ) -> Any:
+ """
+ Retrieves transfers for a specific subaccount.
+
+ Args:
+ address (str): The address.
+ subaccount_number (int): The subaccount number.
+ limit (Optional[int]): The maximum number of transfers to retrieve.
+ created_before_or_at_height (Optional[int]): The block height filter for transfers created before or at.
+ created_before_or_at (Optional[str]): The timestamp filter for transfers created before or at.
+
+ Returns:
+ Any: The transfers data.
+ """
+ uri = "/v4/transfers"
+ return await self.get(
+ uri,
+ params={
+ "address": address,
+ "subaccountNumber": subaccount_number,
+ "limit": limit,
+ "createdBeforeOrAtHeight": created_before_or_at_height,
+ "createdBeforeOrAt": created_before_or_at,
+ },
+ )
+
+ async def get_subaccount_orders(
+ self,
+ address: str,
+ subaccount_number: int,
+ ticker: Optional[str] = None,
+ ticker_type: TickerType = TickerType.PERPETUAL,
+ side: Optional[OrderSide] = None,
+ status: Optional[OrderStatus] = None,
+ type: Optional[OrderType] = None,
+ limit: Optional[int] = None,
+ good_til_block_before_or_at: Optional[int] = None,
+ good_til_block_time_before_or_at: Optional[str] = None,
+ return_latest_orders: Optional[bool] = None,
+ ) -> Any:
+ """
+ Retrieves orders for a specific subaccount.
+
+ Args:
+ address (str): The address.
+ subaccount_number (int): The subaccount number.
+ ticker (Optional[str]): The ticker filter.
+ ticker_type (TickerType): The ticker type filter.
+ side (Optional[OrderSide]): The order side filter.
+ status (Optional[OrderStatus]): The order status filter.
+ type (Optional[OrderType]): The order type filter.
+ limit (Optional[int]): The maximum number of orders to retrieve.
+ good_til_block_before_or_at (Optional[int]): The block number filter for orders good until before or at.
+ good_til_block_time_before_or_at (Optional[str]): The timestamp filter for orders good until before or at.
+ return_latest_orders (Optional[bool]): Whether to return only the latest orders.
+
+ Returns:
+ Any: The orders data.
+ """
+ uri = "/v4/orders"
+ return await self.get(
+ uri,
+ params={
+ "address": address,
+ "subaccountNumber": subaccount_number,
+ "ticker": ticker,
+ "tickerType": ticker_type,
+ "side": side,
+ "status": status,
+ "type": type,
+ "limit": limit,
+ "goodTilBlockBeforeOrAt": good_til_block_before_or_at,
+ "goodTilBlockTimeBeforeOrAt": good_til_block_time_before_or_at,
+ "returnLatestOrders": return_latest_orders,
+ },
+ )
+
+ async def get_order(self, order_id: str) -> Any:
+ """
+ Retrieves a specific order by its ID.
+
+ Args:
+ order_id (str): The order ID.
+
+ Returns:
+ Any: The order data.
+ """
+ uri = f"/v4/orders/{order_id}"
+ return await self.get(uri)
+
+ async def get_subaccount_fills(
+ self,
+ address: str,
+ subaccount_number: int,
+ ticker: Optional[str] = None,
+ ticker_type: TickerType = TickerType.PERPETUAL,
+ limit: Optional[int] = None,
+ created_before_or_at_height: Optional[int] = None,
+ created_before_or_at: Optional[str] = None,
+ ) -> Any:
+ """
+ Retrieves fills for a specific subaccount.
+
+ Args:
+ address (str): The address.
+ subaccount_number (int): The subaccount number.
+ ticker (Optional[str]): The ticker filter.
+ ticker_type (TickerType): The ticker type filter.
+ limit (Optional[int]): The maximum number of fills to retrieve.
+ created_before_or_at_height (Optional[int]): The block height filter for fills created before or at.
+ created_before_or_at (Optional[str]): The timestamp filter for fills created before or at.
+
+ Returns:
+ Any: The fills data.
+ """
+ uri = "/v4/fills"
+ return await self.get(
+ uri,
+ params={
+ "address": address,
+ "subaccountNumber": subaccount_number,
+ "ticker": ticker,
+ "tickerType": ticker_type,
+ "limit": limit,
+ "createdBeforeOrAtHeight": created_before_or_at_height,
+ "createdBeforeOrAt": created_before_or_at,
+ },
+ )
+
+ async def get_subaccount_historical_pnls(
+ self,
+ address: str,
+ subaccount_number: int,
+ effective_before_or_at: Optional[str] = None,
+ effective_at_or_after: Optional[str] = None,
+ ) -> Any:
+ """
+ Retrieves historical PnLs for a specific subaccount.
+
+ Args:
+ address (str): The address.
+ subaccount_number (int): The subaccount number.
+ effective_before_or_at (Optional[str]): The timestamp filter for PnLs effective before or at.
+ effective_at_or_after (Optional[str]): The timestamp filter for PnLs effective at or after.
+
+ Returns:
+ Any: The historical PnLs data.
+ """
+ uri = "/v4/historical-pnl"
+ return await self.get(
+ uri,
+ params={
+ "address": address,
+ "subaccountNumber": subaccount_number,
+ "effectiveBeforeOrAt": effective_before_or_at,
+ "effectiveAtOrAfter": effective_at_or_after,
+ },
+ )
+
+ async def get_historical_block_trading_rewards(
+ self,
+ address: str,
+ limit: Optional[int] = None,
+ ) -> Any:
+ """
+ Retrieves historical block trading rewards for the specified address.
+
+ Args:
+ address (str): The address.
+ limit (Optional[int]): The maximum number of rewards to retrieve.
+
+ Returns:
+ Any: The historical block trading rewards data.
+ """
+ uri = f"/v4/historicalBlockTradingRewards/{address}"
+ return await self.get(uri, params={"limit": limit})
+
+ async def get_historical_trading_rewards_aggregated(
+ self,
+ address: str,
+ period: TradingRewardAggregationPeriod = TradingRewardAggregationPeriod.DAILY,
+ limit: Optional[int] = None,
+ starting_before_or_at: Optional[str] = None,
+ starting_before_or_at_height: Optional[int] = None,
+ ) -> Any:
+ """
+ Retrieves aggregated historical trading rewards for the specified address.
+
+ Args:
+ address (str): The address.
+ period (TradingRewardAggregationPeriod): The aggregation period.
+ limit (Optional[int]): The maximum number of aggregated rewards to retrieve.
+ starting_before_or_at (Optional[str]): The timestamp filter for rewards starting before or at.
+ starting_before_or_at_height (Optional[int]): The block height filter for rewards starting before or at.
+
+ Returns:
+ Any: The aggregated historical trading rewards data.
+ """
+ uri = f"/v4/historicalTradingRewardAggregations/{address}"
+ params = {
+ "period": period,
+ "limit": limit,
+ "startingBeforeOrAt": starting_before_or_at,
+ "startingBeforeOrAtHeight": starting_before_or_at_height,
+ }
+ return await self.get(uri, params=params)
diff --git a/v4-client-py-v2/dydx_v4_client/indexer/rest/modules/markets.py b/v4-client-py-v2/dydx_v4_client/indexer/rest/modules/markets.py
new file mode 100644
index 00000000..79153352
--- /dev/null
+++ b/v4-client-py-v2/dydx_v4_client/indexer/rest/modules/markets.py
@@ -0,0 +1,135 @@
+from typing import Optional
+
+from dydx_v4_client.indexer.rest.constants import TimePeriod
+from dydx_v4_client.indexer.rest.shared.rest import RestClient
+
+
+class MarketsClient(RestClient):
+
+ async def get_perpetual_markets(self, market: Optional[str] = None) -> dict:
+ """
+ Retrieves perpetual markets.
+
+ Args:
+ market (Optional[str]): The specific market ticker to retrieve. If not provided, all markets are returned.
+
+ Returns:
+ dict: The perpetual markets data.
+ """
+ uri = "/v4/perpetualMarkets"
+ return await self.get(uri, params={"ticker": market})
+
+ async def get_perpetual_market_orderbook(self, market: str) -> dict:
+ """
+ Retrieves the orderbook for a specific perpetual market.
+
+ Args:
+ market (str): The market ticker.
+
+ Returns:
+ dict: The orderbook data.
+ """
+ uri = f"/v4/orderbooks/perpetualMarket/{market}"
+ return await self.get(uri)
+
+ async def get_perpetual_market_trades(
+ self,
+ market: str,
+ starting_before_or_at_height: Optional[int] = None,
+ limit: Optional[int] = None,
+ ) -> dict:
+ """
+ Retrieves trades for a specific perpetual market.
+
+ Args:
+ market (str): The market ticker.
+ starting_before_or_at_height (Optional[int]): The block height to start retrieving trades from.
+ limit (Optional[int]): The maximum number of trades to retrieve.
+
+ Returns:
+ dict: The trades data.
+ """
+ uri = f"/v4/trades/perpetualMarket/{market}"
+ return await self.get(
+ uri,
+ params={
+ "createdBeforeOrAtHeight": starting_before_or_at_height,
+ "limit": limit,
+ },
+ )
+
+ async def get_perpetual_market_candles(
+ self,
+ market: str,
+ resolution: str,
+ from_iso: Optional[str] = None,
+ to_iso: Optional[str] = None,
+ limit: Optional[int] = None,
+ ) -> dict:
+ """
+ Retrieves candle data for a specific perpetual market.
+
+ Args:
+ market (str): The market ticker.
+ resolution (str): The candle resolution (e.g., "1DAY", "1HOUR", "1MIN").
+ from_iso (Optional[str]): The start timestamp in ISO format.
+ to_iso (Optional[str]): The end timestamp in ISO format.
+ limit (Optional[int]): The maximum number of candles to retrieve.
+
+ Returns:
+ dict: The candle data.
+ """
+ uri = f"/v4/candles/perpetualMarkets/{market}"
+ return await self.get(
+ uri,
+ params={
+ "resolution": resolution,
+ "fromISO": from_iso,
+ "toISO": to_iso,
+ "limit": limit,
+ },
+ )
+
+ async def get_perpetual_market_historical_funding(
+ self,
+ market: str,
+ effective_before_or_at: Optional[str] = None,
+ effective_before_or_at_height: Optional[int] = None,
+ limit: Optional[int] = None,
+ ) -> dict:
+ """
+ Retrieves historical funding rates for a specific perpetual market.
+
+ Args:
+ market (str): The market ticker.
+ effective_before_or_at (Optional[str]): The timestamp to retrieve funding rates effective before or at.
+ effective_before_or_at_height (Optional[int]): The block height to retrieve funding rates effective before or at.
+ limit (Optional[int]): The maximum number of funding rates to retrieve.
+
+ Returns:
+ dict: The historical funding rates data.
+ """
+ uri = f"/v4/historicalFunding/{market}"
+ return await self.get(
+ uri,
+ params={
+ "effectiveBeforeOrAt": effective_before_or_at,
+ "effectiveBeforeOrAtHeight": effective_before_or_at_height,
+ "limit": limit,
+ },
+ )
+
+ async def get_perpetual_market_sparklines(
+ self, period: str = TimePeriod.ONE_DAY
+ ) -> dict:
+ """
+ Retrieves sparkline data for perpetual markets.
+
+ Args:
+ period (str): The time period for the sparkline data (e.g., "ONE_DAY", "SEVEN_DAYS").
+
+ Returns:
+ dict: The sparkline data.
+ """
+ uri = "/v4/sparklines"
+ return await self.get(uri, params={"timePeriod": period})
diff --git a/v4-client-py-v2/dydx_v4_client/indexer/rest/modules/status.py b/v4-client-py-v2/dydx_v4_client/indexer/rest/modules/status.py
new file mode 100644
index 00000000..db859aa0
--- /dev/null
+++ b/v4-client-py-v2/dydx_v4_client/indexer/rest/modules/status.py
@@ -0,0 +1,38 @@
+from typing import Dict
+
+from dydx_v4_client.indexer.rest.shared.rest import RestClient
+
+
+class StatusClient(RestClient):
+ async def get_time(self) -> Dict[str, str]:
+ """
+ Get the current time of the Indexer.
+
+ Returns:
+ Dict[str, str]: A dictionary containing the isoString and epoch time.
+ """
+ uri = "/v4/time"
+ return await self.get(uri)
+
+ async def get_height(self) -> Dict[str, str]:
+ """
+ Get the block height of the most recent block processed by the Indexer.
+
+ Returns:
+ Dict[str, str]: A dictionary containing the block height and time.
+ """
+ uri = "/v4/height"
+ return await self.get(uri)
+
+ async def screen(self, address: str) -> Dict[str, bool]:
+ """
+ Screen an address to see if it is restricted.
+
+ Args:
+ address (str): The EVM or dYdX address to screen.
+
+ Returns:
+ Dict[str, bool]: A dictionary indicating whether the specified address is restricted.
+ """
+ uri = "/v4/screen"
+ return await self.get(uri, params={"address": address})
diff --git a/v4-client-py-v2/dydx_v4_client/indexer/rest/noble_client.py b/v4-client-py-v2/dydx_v4_client/indexer/rest/noble_client.py
new file mode 100644
index 00000000..714a151a
--- /dev/null
+++ b/v4-client-py-v2/dydx_v4_client/indexer/rest/noble_client.py
@@ -0,0 +1,261 @@
+from typing import List, Optional
+
+import grpc
+from ecdsa.util import sigencode_string_canonize
+from v4_proto.cosmos.auth.v1beta1 import query_pb2_grpc as auth
+from v4_proto.cosmos.auth.v1beta1.auth_pb2 import BaseAccount
+from v4_proto.cosmos.auth.v1beta1.query_pb2 import QueryAccountRequest
+from v4_proto.cosmos.bank.v1beta1 import query_pb2 as bank_query
+from v4_proto.cosmos.bank.v1beta1 import query_pb2_grpc as bank_query_grpc
+from v4_proto.cosmos.base.abci.v1beta1.abci_pb2 import TxResponse
+from v4_proto.cosmos.base.v1beta1.coin_pb2 import Coin
+from v4_proto.cosmos.crypto.secp256k1.keys_pb2 import PubKey
+from v4_proto.cosmos.tx.signing.v1beta1.signing_pb2 import SignMode
+from v4_proto.cosmos.tx.v1beta1 import service_pb2_grpc
+from v4_proto.cosmos.tx.v1beta1.service_pb2 import (
+ BroadcastMode,
+ BroadcastTxRequest,
+ SimulateRequest,
+)
+from v4_proto.cosmos.tx.v1beta1.tx_pb2 import (
+ AuthInfo,
+ Fee,
+ ModeInfo,
+ SignDoc,
+ SignerInfo,
+ Tx,
+ TxBody,
+)
+
+from dydx_v4_client.config import GAS_MULTIPLIER
+from dydx_v4_client.node.builder import as_any
+from dydx_v4_client.wallet import from_mnemonic
+
+
+class NobleClient:
+ """
+ A client for interacting with the Noble blockchain.
+ """
+
+ def __init__(self, rest_endpoint: str, default_client_memo: Optional[str] = None):
+ """
+ Initializes a new instance of the NobleClient.
+
+ Args:
+ rest_endpoint (str): The REST endpoint URL for the Noble blockchain.
+ default_client_memo (Optional[str]): The default client memo for transactions.
+ """
+ self.rest_endpoint = rest_endpoint
+ self.default_client_memo = default_client_memo
+ self.wallet = None
+ self.channel = None
+
+ @property
+ def is_connected(self) -> bool:
+ """
+ Checks if the client is connected to the Noble blockchain.
+
+ Returns:
+ bool: True if connected, False otherwise.
+ """
+ return self.channel is not None
+
+ async def connect(self, mnemonic: str):
+ """
+ Connects the client to the Noble blockchain using the provided mnemonic.
+
+ Args:
+ mnemonic (str): The mnemonic phrase for the wallet.
+
+ Raises:
+ ValueError: If the mnemonic is not provided.
+ """
+ if not mnemonic:
+ raise ValueError("Mnemonic not provided")
+ private_key = from_mnemonic(mnemonic)
+ self.wallet = private_key
+ self.channel = grpc.secure_channel(
+ self.rest_endpoint,
+ grpc.ssl_channel_credentials(),
+ )
+
+ async def get_account_balances(
+ self, address: str
+ ) -> bank_query.QueryAllBalancesResponse:
+ """
+ Retrieves the account balances for the specified address.
+
+ Args:
+ address (str): The account address.
+
+ Returns:
+ bank_query.QueryAllBalancesResponse: The response containing the account balances.
+
+ Raises:
+ ValueError: If the client channel is not initialized.
+ """
+ if self.channel is None:
+ raise ValueError("NobleClient channel not initialized")
+ stub = bank_query_grpc.QueryStub(self.channel)
+ return stub.AllBalances(bank_query.QueryAllBalancesRequest(address=address))
+
+ async def get_account_balance(
+ self, address: str, denom: str
+ ) -> bank_query.QueryBalanceResponse:
+ """
+ Retrieves the account balance for the specified address and denomination.
+
+ Args:
+ address (str): The account address.
+ denom (str): The balance denomination.
+
+ Returns:
+ bank_query.QueryBalanceResponse: The response containing the account balance.
+
+ Raises:
+ ValueError: If the client channel is not initialized.
+ """
+ if self.channel is None:
+ raise ValueError("NobleClient channel not initialized")
+ stub = bank_query_grpc.QueryStub(self.channel)
+ return stub.Balance(
+ bank_query.QueryBalanceRequest(address=address, denom=denom)
+ )
+
+ async def get_account(self, address: str) -> BaseAccount:
+ """
+ Retrieves the account information for the specified address.
+
+ Args:
+ address (str): The account address.
+
+ Returns:
+ BaseAccount: The account information.
+
+ Raises:
+ ValueError: If the client channel is not initialized.
+ Exception: If the account unpacking fails.
+ """
+ if self.channel is None:
+ raise ValueError("NobleClient channel not initialized")
+ account = BaseAccount()
+ response = auth.QueryStub(self.channel).Account(
+ QueryAccountRequest(address=address)
+ )
+ if not response.account.Unpack(account):
+ raise Exception("Failed to unpack account")
+ return account
+
+ async def send(
+ self,
+ messages: List[dict],
+ gas_price: str = "0.025uusdc",
+ memo: Optional[str] = None,
+ ) -> TxResponse:
+ """
+ Sends a transaction with the specified messages.
+
+ Args:
+ messages (List[dict]): The list of transaction messages.
+ gas_price (str): The gas price for the transaction (default: "0.025uusdc").
+ memo (Optional[str]): The transaction memo.
+
+ Returns:
+ TxResponse: The transaction response.
+
+ Raises:
+ ValueError: If the client channel or wallet is not initialized.
+ """
+ if self.channel is None:
+ raise ValueError("NobleClient channel not initialized")
+ if self.wallet is None:
+ raise ValueError("NobleClient wallet not initialized")
+
+ # Simulate to get the gas estimate
+ fee = await self.simulate_transaction(
+ messages, gas_price, memo or self.default_client_memo
+ )
+
+ # Sign and broadcast the transaction
+ signer_info = SignerInfo(
+ public_key=as_any(
+ PubKey(key=self.wallet.get_verifying_key().to_string("compressed"))
+ ),
+ mode_info=ModeInfo(single=ModeInfo.Single(mode=SignMode.SIGN_MODE_DIRECT)),
+ sequence=self.get_account(
+ self.wallet.get_verifying_key().to_string()
+ ).sequence,
+ )
+ body = TxBody(messages=messages, memo=memo or self.default_client_memo)
+ auth_info = AuthInfo(signer_infos=[signer_info], fee=fee)
+ signature = self.wallet.sign(
+ SignDoc(
+ body_bytes=body.SerializeToString(),
+ auth_info_bytes=auth_info.SerializeToString(),
+ account_number=self.get_account(
+ self.wallet.get_verifying_key().to_string()
+ ).account_number,
+ chain_id=self.chain_id,
+ ).SerializeToString(),
+ sigencode=sigencode_string_canonize,
+ )
+
+ tx = Tx(body=body, auth_info=auth_info, signatures=[signature])
+ request = BroadcastTxRequest(
+ tx_bytes=tx.SerializeToString(), mode=BroadcastMode.BROADCAST_MODE_SYNC
+ )
+ return service_pb2_grpc.ServiceStub(self.channel).BroadcastTx(request)
+
+ async def simulate_transaction(
+ self,
+ messages: List[dict],
+ gas_price: str = "0.025uusdc",
+ memo: Optional[str] = None,
+ ) -> Fee:
+ """
+ Simulates a transaction to estimate the gas fee.
+
+ Args:
+ messages (List[dict]): The list of transaction messages.
+ gas_price (str): The gas price for the transaction (default: "0.025uusdc").
+ memo (Optional[str]): The transaction memo.
+
+ Returns:
+ Fee: The estimated gas fee.
+
+ Raises:
+ ValueError: If the client channel or wallet is not initialized.
+ """
+ if self.channel is None:
+ raise ValueError("NobleClient channel not initialized")
+ if self.wallet is None:
+ raise ValueError("NobleClient wallet not initialized")
+
+ # Get simulated response
+ signer_info = SignerInfo(
+ public_key=as_any(
+ PubKey(key=self.wallet.get_verifying_key().to_string("compressed"))
+ ),
+ mode_info=ModeInfo(single=ModeInfo.Single(mode=SignMode.SIGN_MODE_DIRECT)),
+ sequence=self.get_account(
+ self.wallet.get_verifying_key().to_string()
+ ).sequence,
+ )
+ body = TxBody(messages=messages, memo=memo or self.default_client_memo)
+ auth_info = AuthInfo(signer_infos=[signer_info], fee=Fee(gas_limit=0))
+ request = SimulateRequest(
+ tx=Tx(body=body, auth_info=auth_info),
+ )
+ response = service_pb2_grpc.ServiceStub(self.channel).Simulate(request)
+
+ # Calculate and return the fee
+ gas_limit = int(response.gas_info.gas_used * GAS_MULTIPLIER)
+ return Fee(
+ amount=[
+ Coin(
+ amount=str(int(gas_limit * float(gas_price.split("u")[0]))),
+ denom=gas_price.split("u")[1],
+ )
+ ],
+ gas_limit=gas_limit,
+ )
diff --git a/v4-client-py-v2/dydx_v4_client/indexer/rest/shared/__init__.py b/v4-client-py-v2/dydx_v4_client/indexer/rest/shared/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/v4-client-py-v2/dydx_v4_client/indexer/rest/shared/rest.py b/v4-client-py-v2/dydx_v4_client/indexer/rest/shared/rest.py
new file mode 100644
index 00000000..ef4a80c6
--- /dev/null
+++ b/v4-client-py-v2/dydx_v4_client/indexer/rest/shared/rest.py
@@ -0,0 +1,37 @@
+from typing import Any, Dict, Optional
+
+import httpx
+
+from dydx_v4_client.indexer.rest.constants import DEFAULT_API_TIMEOUT
+from dydx_v4_client.indexer.rest.utils.request_helpers import generate_query_path
+
+
+class RestClient:
+ def __init__(self, host: str, api_timeout: Optional[float] = None):
+ if host.endswith("/"):
+ self.host = host[:-1]
+ else:
+ self.host = host
+ self.api_timeout = api_timeout or DEFAULT_API_TIMEOUT
+
+ async def get(self, request_path: str, params: Dict = {}) -> Dict[str, Any]:
+ url = f"{self.host}{generate_query_path(request_path, params)}"
+ async with httpx.AsyncClient() as client:
+ response = await client.get(url, timeout=self.api_timeout)
+ response.raise_for_status()
+ return response.json()
+
+ async def post(
+ self,
+ request_path: str,
+ params: Dict = {},
+ body: Optional[Any] = None,
+ headers: Dict = {},
+ ) -> httpx.Response:
+ url = f"{self.host}{generate_query_path(request_path, params)}"
+ async with httpx.AsyncClient() as client:
+ response = await client.post(
+ url, json=body, headers=headers, timeout=self.api_timeout
+ )
+ response.raise_for_status()
+ return response
diff --git a/v4-client-py-v2/dydx_v4_client/indexer/rest/utils/__init__.py b/v4-client-py-v2/dydx_v4_client/indexer/rest/utils/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/v4-client-py-v2/dydx_v4_client/indexer/rest/utils/request_helpers.py b/v4-client-py-v2/dydx_v4_client/indexer/rest/utils/request_helpers.py
new file mode 100644
index 00000000..4b8885df
--- /dev/null
+++ b/v4-client-py-v2/dydx_v4_client/indexer/rest/utils/request_helpers.py
@@ -0,0 +1,23 @@
+from typing import Any, Dict
+
+
+def generate_query_path(url: str, params: Dict[str, Any]) -> str:
+ """
+ Generate the query path by appending the provided parameters to the given URL.
+
+ Args:
+ url (str): The base URL.
+ params (Dict[str, Any]): A dictionary of query parameters.
+
+ Returns:
+ str: The URL with the query parameters appended.
+ """
+ defined_entries = [
+ (key, value) for key, value in params.items() if value is not None
+ ]
+
+ if not defined_entries:
+ return url
+
+ params_string = "&".join(f"{key}={value}" for key, value in defined_entries)
+ return f"{url}?{params_string}"
diff --git a/v4-client-py-v2/dydx_v4_client/indexer/socket/__init__.py b/v4-client-py-v2/dydx_v4_client/indexer/socket/__init__.py
new file mode 100644
index 00000000..43986a97
--- /dev/null
+++ b/v4-client-py-v2/dydx_v4_client/indexer/socket/__init__.py
@@ -0,0 +1 @@
+from dydx_v4_client.indexer.socket.websocket import CandlesResolution, IndexerSocket
diff --git a/v4-client-py-v2/dydx_v4_client/indexer/socket/websocket.py b/v4-client-py-v2/dydx_v4_client/indexer/socket/websocket.py
new file mode 100644
index 00000000..b854fd9f
--- /dev/null
+++ b/v4-client-py-v2/dydx_v4_client/indexer/socket/websocket.py
@@ -0,0 +1,133 @@
+import json
+import ssl
+from dataclasses import dataclass, field
+from enum import Enum
+from typing import Any, Callable, Optional, Self, Union
+
+import websocket
+
+
+class CandlesResolution(Enum):
+ ONE_MINUTE = "1MIN"
+ FIVE_MINUTES = "5MINS"
+ FIFTEEN_MINUTES = "15MINS"
+ THIRTY_MINUTES = "30MINS"
+ ONE_HOUR = "1HOUR"
+ FOUR_HOURS = "4HOURS"
+ ONE_DAY = "1DAY"
+
+
+@dataclass
+class Channel:
+ channel: str = field(init=False)
+ app: websocket.WebSocketApp
+
+ def subscribe(self, **kwargs) -> Self:
+ self.app.send(
+ json.dumps({"type": "subscribe", "channel": self.channel, **kwargs})
+ )
+ return self
+
+ def unsubscribe(self, **kwargs):
+ self.app.send(
+ json.dumps({"type": "unsubscribe", "channel": self.channel, **kwargs})
+ )
+
+ def process(self, message):
+ """
+ WIP.
+ An idea to provide per-channel processing instead of `on_message` for all messages.
+
+ Maybe allow creating standalone channels? Ie.:
+ order_book = OrderBook()
+ order_book.subscribe(id="BTC-USD")
+ """
+ raise NotImplementedError()
+
+
+class OrderBook(Channel):
+ channel = "v4_orderbook"
+
+ def subscribe(self, id, batched=True) -> Self:
+ return super().subscribe(id=id, batched=batched)
+
+ def unsubscribe(self, id):
+ return super().unsubscribe(id=id)
+
+
+class Trades(Channel):
+ channel = "v4_trades"
+
+ def subscribe(self, id, batched=True) -> Self:
+ return super().subscribe(id=id, batched=batched)
+
+ def unsubscribe(self, id):
+ return super().unsubscribe(id=id)
+
+
+class Markets(Channel):
+ channel = "v4_markets"
+
+ def subscribe(self, batched=True) -> Self:
+ return super().subscribe(batched=batched)
+
+ def unsubscribe(self):
+ return super().unsubscribe()
+
+
+class Candles(Channel):
+ channel = "v4_candles"
+
+ def subscribe(self, id, resolution: CandlesResolution, batched=True) -> Self:
+ return super().subscribe(id=f"{id}/{resolution}", batched=batched)
+
+ def unsubscribe(self, id, resolution: CandlesResolution):
+ return super().unsubscribe(id=f"{id}/{resolution}")
+
+
+class Subaccounts(Channel):
+ channel = "v4_subaccounts"
+
+ def subscribe(self, address, subaccount_number) -> Self:
+ subaccount_id = f"{address}/{subaccount_number}"
+ return super().subscribe(id=subaccount_id)
+
+ def unsubscribe(self, address, subaccount_number):
+ subaccount_id = f"{address}/{subaccount_number}"
+ return super().unsubscribe(id=subaccount_id)
+
+
+def as_json(on_message):
+ def wrapper(ws, message):
+ return on_message(ws, json.loads(message))
+
+ return wrapper
+
+
+class IndexerSocket(websocket.WebSocketApp):
+ def __init__(
+ self,
+ url: str,
+ header: Union[list, dict, Callable, None] = None,
+ on_open: Optional[Callable[[websocket.WebSocket], None]] = None,
+ on_message: Optional[Callable[[websocket.WebSocket, Any], None]] = None,
+ *args,
+ **kwargs,
+ ):
+ self.order_book = OrderBook(self)
+ self.trades = Trades(self)
+ self.markets = Markets(self)
+ self.candles = Candles(self)
+ self.subaccounts = Subaccounts(self)
+
+ super().__init__(
+ url=url,
+ header=header,
+ on_open=on_open,
+ on_message=as_json(on_message),
+ *args,
+ **kwargs,
+ )
+
+ async def connect(self, sslopt={"cert_reqs": ssl.CERT_NONE}) -> None:
+ self.run_forever(sslopt=sslopt)
diff --git a/v4-client-py-v2/dydx_v4_client/network.py b/v4-client-py-v2/dydx_v4_client/network.py
new file mode 100644
index 00000000..2e169bfc
--- /dev/null
+++ b/v4-client-py-v2/dydx_v4_client/network.py
@@ -0,0 +1,52 @@
+from dataclasses import dataclass
+
+
+@dataclass
+class NodeConfig:
+ chain_id: str
+ url: str
+ chaintoken_denom: str
+ usdc_denom: str
+
+
+@dataclass
+class Network:
+ node: NodeConfig
+ rest_indexer: str
+ websocket_indexer: str
+
+
+MAINNET = Network(
+ NodeConfig(
+ "dydx-mainnet-1",
+ "https://dydx-ops-rpc.kingnodes.com:443",
+ "adydx",
+ "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
+ ),
+ "https://indexer.dydx.trade",
+ "wss://indexer.dydx.trade/v4/ws",
+)
+
+TESTNET = Network(
+ NodeConfig(
+ "dydx-testnet-4",
+ "test-dydx-grpc.kingnodes.com",
+ "adv4tnt",
+ "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
+ ),
+ "https://dydx-testnet.imperator.co",
+ "wss://indexer.v4testnet.dydx.exchange/v4/ws",
+)
+TESTNET_FAUCET = "https://faucet.v4testnet.dydx.exchange"
+TESTNET_NOBLE = "https://rpc.testnet.noble.strange.love"
+
+LOCAL = Network(
+ NodeConfig(
+ "localdydxprotocol",
+ "http://localhost:26657",
+ "adv4tnt",
+ "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
+ ),
+ "http://localhost:3002",
+ "ws://localhost:3003",
+)
diff --git a/v4-client-py-v2/dydx_v4_client/node/__init__.py b/v4-client-py-v2/dydx_v4_client/node/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/v4-client-py-v2/dydx_v4_client/node/builder.py b/v4-client-py-v2/dydx_v4_client/node/builder.py
new file mode 100644
index 00000000..6822d8c5
--- /dev/null
+++ b/v4-client-py-v2/dydx_v4_client/node/builder.py
@@ -0,0 +1,88 @@
+from dataclasses import dataclass
+from typing import List
+
+import google
+from ecdsa.util import sigencode_string_canonize
+from google.protobuf.message import Message
+from v4_proto.cosmos.base.v1beta1.coin_pb2 import Coin
+from v4_proto.cosmos.tx.signing.v1beta1.signing_pb2 import SignMode
+from v4_proto.cosmos.tx.v1beta1.tx_pb2 import (
+ AuthInfo,
+ Fee,
+ ModeInfo,
+ SignDoc,
+ SignerInfo,
+ Tx,
+ TxBody,
+)
+
+from dydx_v4_client.node.fee import calculate_fee
+from dydx_v4_client.wallet import Wallet
+
+
+def as_any(message: Message):
+ packed = google.protobuf.any_pb2.Any()
+ packed.Pack(message, type_url_prefix="/")
+ return packed
+
+
+def get_signer_info(public_key, sequence):
+ return SignerInfo(
+ public_key=as_any(public_key),
+ mode_info=ModeInfo(single=ModeInfo.Single(mode=SignMode.SIGN_MODE_DIRECT)),
+ sequence=sequence,
+ )
+
+
+def get_signature(private_key, body, auth_info, account_number, chain_id):
+ signdoc = SignDoc(
+ body_bytes=body.SerializeToString(),
+ auth_info_bytes=auth_info.SerializeToString(),
+ account_number=account_number,
+ chain_id=chain_id,
+ )
+
+ return private_key.sign(
+ signdoc.SerializeToString(), sigencode=sigencode_string_canonize
+ )
+
+
+DEFAULT_FEE = Fee(
+ amount=[],
+ gas_limit=1000000,
+)
+
+
+@dataclass
+class Builder:
+ chain_id: str
+ denomination: str
+ memo: str = "Client Example"
+
+ def calculate_fee(self, gas_used) -> Fee:
+ gas_limit, amount = calculate_fee(gas_used)
+ return self.fee(gas_limit, self.coin(amount))
+
+ def coin(self, amount: int) -> Coin:
+ return Coin(amount=str(amount), denom=self.denomination)
+
+ def fee(self, gas_limit: int, *amount: List[Coin]) -> Fee:
+ return Fee(
+ amount=amount,
+ gas_limit=gas_limit,
+ )
+
+ def build_transaction(self, wallet: Wallet, messages: List[Message], fee: Fee):
+ body = TxBody(messages=messages, memo=self.memo)
+ auth_info = AuthInfo(
+ signer_infos=[get_signer_info(wallet.public_key, wallet.sequence)],
+ fee=fee,
+ )
+ signature = get_signature(
+ wallet.key, body, auth_info, wallet.account_number, self.chain_id
+ )
+
+ return Tx(body=body, auth_info=auth_info, signatures=[signature])
+
+ def build(self, wallet: Wallet, message: Message, fee: Fee = DEFAULT_FEE):
+ return self.build_transaction(wallet, [as_any(message)], fee)
diff --git a/v4-client-py-v2/dydx_v4_client/node/client.py b/v4-client-py-v2/dydx_v4_client/node/client.py
new file mode 100644
index 00000000..5625e6a7
--- /dev/null
+++ b/v4-client-py-v2/dydx_v4_client/node/client.py
@@ -0,0 +1,678 @@
+from dataclasses import dataclass, field
+from functools import partial
+from typing import List, Optional, Self
+
+import grpc
+from google.protobuf.message import Message
+from v4_proto.cosmos.auth.v1beta1 import query_pb2_grpc as auth
+from v4_proto.cosmos.auth.v1beta1.auth_pb2 import BaseAccount
+from v4_proto.cosmos.auth.v1beta1.query_pb2 import QueryAccountRequest
+from v4_proto.cosmos.bank.v1beta1 import query_pb2 as bank_query
+from v4_proto.cosmos.bank.v1beta1 import query_pb2_grpc as bank_query_grpc
+from v4_proto.cosmos.base.tendermint.v1beta1 import query_pb2 as tendermint_query
+from v4_proto.cosmos.base.tendermint.v1beta1 import (
+ query_pb2_grpc as tendermint_query_grpc,
+)
+from v4_proto.cosmos.staking.v1beta1 import query_pb2 as staking_query
+from v4_proto.cosmos.staking.v1beta1 import query_pb2_grpc as staking_query_grpc
+from v4_proto.cosmos.tx.v1beta1 import service_pb2_grpc
+from v4_proto.cosmos.tx.v1beta1.service_pb2 import (
+ BroadcastMode,
+ BroadcastTxRequest,
+ SimulateRequest,
+)
+from v4_proto.cosmos.tx.v1beta1.tx_pb2 import Tx
+from v4_proto.dydxprotocol.bridge import query_pb2 as bridge_query
+from v4_proto.dydxprotocol.bridge import query_pb2_grpc as bridge_query_grpc
+from v4_proto.dydxprotocol.clob import clob_pair_pb2 as clob_pair_type
+from v4_proto.dydxprotocol.clob import (
+ equity_tier_limit_config_pb2 as equity_tier_limit_config_type,
+)
+from v4_proto.dydxprotocol.clob import query_pb2 as clob_query
+from v4_proto.dydxprotocol.clob import query_pb2_grpc as clob_query_grpc
+from v4_proto.dydxprotocol.clob.order_pb2 import Order, OrderId
+from v4_proto.dydxprotocol.clob.query_pb2 import (
+ QueryAllClobPairRequest,
+ QueryClobPairAllResponse,
+)
+from v4_proto.dydxprotocol.feetiers import query_pb2 as fee_tier_query
+from v4_proto.dydxprotocol.feetiers import query_pb2_grpc as fee_tier_query_grpc
+from v4_proto.dydxprotocol.perpetuals import query_pb2_grpc as perpetuals_query_grpc
+from v4_proto.dydxprotocol.perpetuals.query_pb2 import (
+ QueryAllPerpetualsRequest,
+ QueryAllPerpetualsResponse,
+ QueryPerpetualRequest,
+ QueryPerpetualResponse,
+)
+from v4_proto.dydxprotocol.prices import market_price_pb2 as market_price_type
+from v4_proto.dydxprotocol.prices import query_pb2_grpc as prices_query_grpc
+from v4_proto.dydxprotocol.prices.query_pb2 import (
+ QueryAllMarketPricesRequest,
+ QueryAllMarketPricesResponse,
+ QueryMarketPriceRequest,
+)
+from v4_proto.dydxprotocol.rewards import query_pb2 as rewards_query
+from v4_proto.dydxprotocol.rewards import query_pb2_grpc as rewards_query_grpc
+from v4_proto.dydxprotocol.stats import query_pb2 as stats_query
+from v4_proto.dydxprotocol.stats import query_pb2_grpc as stats_query_grpc
+from v4_proto.dydxprotocol.subaccounts import query_pb2_grpc as subaccounts_query_grpc
+from v4_proto.dydxprotocol.subaccounts import subaccount_pb2 as subaccount_type
+from v4_proto.dydxprotocol.subaccounts.query_pb2 import (
+ QueryAllSubaccountRequest,
+ QueryGetSubaccountRequest,
+ QuerySubaccountAllResponse,
+)
+from v4_proto.dydxprotocol.subaccounts.subaccount_pb2 import SubaccountId
+
+from dydx_v4_client.network import NodeConfig
+from dydx_v4_client.node.builder import Builder
+from dydx_v4_client.node.fee import Coin, Fee, calculate_fee
+from dydx_v4_client.node.message import (
+ cancel_order,
+ deposit,
+ place_order,
+ send_token,
+ transfer,
+ withdraw,
+)
+from dydx_v4_client.wallet import Wallet
+
+secure_channel = partial(
+ grpc.secure_channel, credentials=grpc.ssl_channel_credentials()
+)
+
+
+@dataclass
+class QueryNodeClient:
+ channel: grpc.Channel = field()
+
+ async def connect(url: str) -> Self:
+ return QueryNodeClient(secure_channel(url))
+
+ async def get_account_balances(
+ self, address: str
+ ) -> bank_query.QueryAllBalancesResponse:
+ """
+ Retrieves all account balances for a given address.
+
+ Args:
+ address (str): The account address.
+
+ Returns:
+ bank_query.QueryAllBalancesResponse: The response containing all account balances.
+ """
+ stub = bank_query_grpc.QueryStub(self.channel)
+ return stub.AllBalances(bank_query.QueryAllBalancesRequest(address=address))
+
+ async def get_account_balance(
+ self, address: str, denom: str
+ ) -> bank_query.QueryBalanceResponse:
+ """
+ Retrieves the account balance for a specific denomination.
+
+ Args:
+ address (str): The account address.
+ denom (str): The denomination of the balance.
+
+ Returns:
+ bank_query.QueryBalanceResponse: The response containing the account balance.
+ """
+ stub = bank_query_grpc.QueryStub(self.channel)
+ return stub.Balance(
+ bank_query.QueryBalanceRequest(address=address, denom=denom)
+ )
+
+ async def get_account(self, address: str) -> BaseAccount:
+ """
+ Retrieves the account information for a given address.
+
+ Args:
+ address (str): The account address.
+
+ Returns:
+ BaseAccount: The base account information.
+ """
+ account = BaseAccount()
+ response = auth.QueryStub(self.channel).Account(
+ QueryAccountRequest(address=address)
+ )
+ if not response.account.Unpack(account):
+ raise Exception("Failed to unpack account")
+ return account
+
+ async def latest_block(self) -> tendermint_query.GetLatestBlockResponse:
+ """
+ Retrieves the latest block information.
+
+ Returns:
+ tendermint_query.GetLatestBlockResponse: The response containing the latest block information.
+ """
+ return tendermint_query_grpc.ServiceStub(self.channel).GetLatestBlock(
+ tendermint_query.GetLatestBlockRequest()
+ )
+
+ async def latest_block_height(self) -> int:
+ """
+ Retrieves the height of the latest block.
+
+ Returns:
+ int: The height of the latest block.
+ """
+ block = await self.latest_block()
+ return block.block.header.height
+
+ async def get_user_stats(self, address: str) -> stats_query.QueryUserStatsResponse:
+ """
+ Retrieves the user stats for a given address.
+
+ Args:
+ address (str): The user address.
+
+ Returns:
+ stats_query.QueryUserStatsResponse: The response containing the user stats.
+ """
+ stub = stats_query_grpc.QueryStub(self.channel)
+ return stub.UserStats(stats_query.QueryUserStatsRequest(user=address))
+
+ async def get_all_validators(
+ self, status: str = ""
+ ) -> staking_query.QueryValidatorsResponse:
+ """
+ Retrieves all validators with an optional status filter.
+
+ Args:
+ status (str, optional): The validator status filter. Defaults to an empty string.
+
+ Returns:
+ staking_query.QueryValidatorsResponse: The response containing all validators.
+ """
+ stub = staking_query_grpc.QueryStub(self.channel)
+ return stub.Validators(staking_query.QueryValidatorsRequest(status=status))
+
+ async def get_subaccount(
+ self, address: str, account_number: int
+ ) -> Optional[subaccount_type.Subaccount]:
+ """
+ Retrieves a subaccount for a given address and account number.
+
+ Args:
+ address (str): The owner address.
+ account_number (int): The subaccount number.
+
+ Returns:
+ Optional[subaccount_type.Subaccount]: The subaccount, if found.
+ """
+ stub = subaccounts_query_grpc.QueryStub(self.channel)
+ response = stub.Subaccount(
+ QueryGetSubaccountRequest(owner=address, number=account_number)
+ )
+ return response.subaccount
+
+ async def get_subaccounts(self) -> QuerySubaccountAllResponse:
+ """
+ Retrieves all subaccounts.
+
+ Returns:
+ QuerySubaccountAllResponse: The response containing all subaccounts.
+ """
+ stub = subaccounts_query_grpc.QueryStub(self.channel)
+ return stub.SubaccountAll(QueryAllSubaccountRequest())
+
+ async def get_clob_pair(self, pair_id: int) -> clob_pair_type.ClobPair:
+ """
+ Retrieves a CLOB pair by its ID.
+
+ Args:
+ pair_id (int): The CLOB pair ID.
+
+ Returns:
+ clob_pair_type.ClobPair: The CLOB pair.
+ """
+ stub = clob_query_grpc.QueryStub(self.channel)
+ response = stub.ClobPair(clob_query.QueryGetClobPairRequest(id=pair_id))
+ return response.clob_pair
+
+ async def get_clob_pairs(self) -> QueryClobPairAllResponse:
+ """
+ Retrieves all CLOB pairs.
+
+ Returns:
+ QueryClobPairAllResponse: The response containing all CLOB pairs.
+ """
+ stub = clob_query_grpc.QueryStub(self.channel)
+ return stub.ClobPairAll(QueryAllClobPairRequest())
+
+ async def get_price(self, market_id: int) -> market_price_type.MarketPrice:
+ """
+ Retrieves the market price for a given market ID.
+
+ Args:
+ market_id (int): The market ID.
+
+ Returns:
+ market_price_type.MarketPrice: The market price.
+ """
+ stub = prices_query_grpc.QueryStub(self.channel)
+ response = stub.MarketPrice(QueryMarketPriceRequest(id=market_id))
+ return response.market_price
+
+ async def get_prices(self) -> QueryAllMarketPricesResponse:
+ """
+ Retrieves all market prices.
+
+ Returns:
+ QueryAllMarketPricesResponse: The response containing all market prices.
+ """
+ stub = prices_query_grpc.QueryStub(self.channel)
+ return stub.AllMarketPrices(QueryAllMarketPricesRequest())
+
+ async def get_perpetual(self, perpetual_id: int) -> QueryPerpetualResponse:
+ """
+ Retrieves a perpetual by its ID.
+
+ Args:
+ perpetual_id (int): The perpetual ID.
+
+ Returns:
+ QueryPerpetualResponse: The response containing the perpetual.
+ """
+ stub = perpetuals_query_grpc.QueryStub(self.channel)
+ return stub.Perpetual(QueryPerpetualRequest(id=perpetual_id))
+
+ async def get_perpetuals(self) -> QueryAllPerpetualsResponse:
+ """
+ Retrieves all perpetuals.
+
+ Returns:
+ QueryAllPerpetualsResponse: The response containing all perpetuals.
+ """
+ stub = perpetuals_query_grpc.QueryStub(self.channel)
+ return stub.AllPerpetuals(QueryAllPerpetualsRequest())
+
+ async def get_equity_tier_limit_config(
+ self,
+ ) -> equity_tier_limit_config_type.EquityTierLimitConfiguration:
+ """
+ Retrieves the equity tier limit configuration.
+
+ Returns:
+ equity_tier_limit_config_type.EquityTierLimitConfiguration: The equity tier limit configuration.
+ """
+ stub = clob_query_grpc.QueryStub(self.channel)
+ response = stub.EquityTierLimitConfiguration(
+ clob_query.QueryEquityTierLimitConfigurationRequest()
+ )
+ return response.equity_tier_limit_config
+
+ async def get_delegator_delegations(
+ self, delegator_addr: str
+ ) -> staking_query.QueryDelegatorDelegationsResponse:
+ """
+ Retrieves the delegations for a given delegator address.
+
+ Args:
+ delegator_addr (str): The delegator address.
+
+ Returns:
+ staking_query.QueryDelegatorDelegationsResponse: The response containing the delegator delegations.
+ """
+ stub = staking_query_grpc.QueryStub(self.channel)
+ return stub.DelegatorDelegations(
+ staking_query.QueryDelegatorDelegationsRequest(
+ delegator_addr=delegator_addr
+ )
+ )
+
+ async def get_delegator_unbonding_delegations(
+ self, delegator_addr: str
+ ) -> staking_query.QueryDelegatorUnbondingDelegationsResponse:
+ """
+ Retrieves the unbonding delegations for a given delegator address.
+
+ Args:
+ delegator_addr (str): The delegator address.
+
+ Returns:
+ staking_query.QueryDelegatorUnbondingDelegationsResponse: The response containing the delegator unbonding delegations.
+ """
+ stub = staking_query_grpc.QueryStub(self.channel)
+ return stub.DelegatorUnbondingDelegations(
+ staking_query.QueryDelegatorUnbondingDelegationsRequest(
+ delegator_addr=delegator_addr
+ )
+ )
+
+ async def get_delayed_complete_bridge_messages(
+ self, address: str = ""
+ ) -> bridge_query.QueryDelayedCompleteBridgeMessagesResponse:
+ """
+ Retrieves the delayed complete bridge messages for a given address.
+
+ Args:
+ address (str, optional): The address. Defaults to an empty string.
+
+ Returns:
+ bridge_query.QueryDelayedCompleteBridgeMessagesResponse: The response containing the delayed complete bridge messages.
+ """
+ stub = bridge_query_grpc.QueryStub(self.channel)
+ return stub.DelayedCompleteBridgeMessages(
+ bridge_query.QueryDelayedCompleteBridgeMessagesRequest(address=address)
+ )
+
+ async def get_fee_tiers(self) -> fee_tier_query.QueryPerpetualFeeParamsResponse:
+ """
+ Retrieves the perpetual fee parameters.
+
+ Returns:
+ fee_tier_query.QueryPerpetualFeeParamsResponse: The response containing the perpetual fee parameters.
+ """
+ stub = fee_tier_query_grpc.QueryStub(self.channel)
+ return stub.PerpetualFeeParams(fee_tier_query.QueryPerpetualFeeParamsRequest())
+
+ async def get_user_fee_tier(
+ self, address: str
+ ) -> fee_tier_query.QueryUserFeeTierResponse:
+ """
+ Retrieves the user fee tier for a given address.
+
+ Args:
+ address (str): The user address.
+
+ Returns:
+ fee_tier_query.QueryUserFeeTierResponse: The response containing the user fee tier.
+ """
+ stub = fee_tier_query_grpc.QueryStub(self.channel)
+ return stub.UserFeeTier(fee_tier_query.QueryUserFeeTierRequest(user=address))
+
+ async def get_rewards_params(self) -> rewards_query.QueryParamsResponse:
+ """
+ Retrieves the rewards parameters.
+
+ Returns:
+ rewards_query.QueryParamsResponse: The response containing the rewards parameters.
+ """
+ stub = rewards_query_grpc.QueryStub(self.channel)
+ return stub.Params(rewards_query.QueryParamsRequest())
+
+
+@dataclass
+class MutatingNodeClient(QueryNodeClient):
+ builder: Builder
+
+ async def broadcast(self, transaction: Tx, mode=BroadcastMode.BROADCAST_MODE_SYNC):
+ """
+ Broadcasts a transaction.
+
+ Args:
+ transaction (Tx): The transaction to broadcast.
+ mode (BroadcastMode, optional): The broadcast mode. Defaults to BroadcastMode.BROADCAST_MODE_SYNC.
+
+ Returns:
+ The response from the broadcast.
+ """
+ request = BroadcastTxRequest(
+ tx_bytes=transaction.SerializeToString(), mode=mode
+ )
+
+ return service_pb2_grpc.ServiceStub(self.channel).BroadcastTx(request)
+
+ async def simulate(self, transaction: Tx):
+ """
+ Simulates a transaction.
+
+ Args:
+ transaction (Tx): The transaction to simulate.
+
+ Returns:
+ The response from the simulation.
+ """
+ request = SimulateRequest(tx=transaction)
+
+ return service_pb2_grpc.ServiceStub(self.channel).Simulate(request)
+
+ async def send(
+ self, wallet: Wallet, transaction: Tx, mode=BroadcastMode.BROADCAST_MODE_SYNC
+ ):
+ """
+ Sends a transaction.
+
+ Args:
+ wallet (Wallet): The wallet to use for signing the transaction.
+ transaction (Tx): The transaction to send.
+ mode (BroadcastMode, optional): The broadcast mode. Defaults to BroadcastMode.BROADCAST_MODE_SYNC.
+
+ Returns:
+ The response from the broadcast.
+ """
+ builder = self.builder
+ simulated = await self.simulate(transaction)
+
+ fee = self.builder.calculate_fee(simulated.gas_info.gas_used)
+
+ transaction = builder.build_transaction(wallet, transaction.body.messages, fee)
+
+ return await self.broadcast(transaction, mode)
+
+ async def send_message(
+ self, wallet: Wallet, message: Message, mode=BroadcastMode.BROADCAST_MODE_SYNC
+ ):
+ """
+ Sends a message.
+
+ Args:
+ wallet (Wallet): The wallet to use for signing the transaction.
+ message (Message): The message to send.
+ mode (BroadcastMode, optional): The broadcast mode. Defaults to BroadcastMode.BROADCAST_MODE_SYNC.
+
+ Returns:
+ The response from the broadcast.
+ """
+ return await self.send(wallet, self.builder.build(wallet, message), mode)
+
+ async def broadcast_message(
+ self, wallet: Wallet, message: Message, mode=BroadcastMode.BROADCAST_MODE_SYNC
+ ):
+ """
+ Broadcasts a message.
+
+ Args:
+ wallet (Wallet): The wallet to use for signing the transaction.
+ message (Message): The message to broadcast.
+ mode (BroadcastMode, optional): The broadcast mode. Defaults to BroadcastMode.BROADCAST_MODE_SYNC.
+
+ Returns:
+ The response from the broadcast.
+ """
+ return await self.broadcast(self.builder.build(wallet, message), mode)
+
+ def build_transaction(self, wallet: Wallet, messages: List[Message], fee: Fee):
+ """
+ Builds a transaction.
+
+ Args:
+ wallet (Wallet): The wallet to use for signing the transaction.
+ messages (List[Message]): The list of messages to include in the transaction.
+ fee (Fee): The fee to use for the transaction.
+
+ Returns:
+ The built transaction.
+ """
+ return self.builder.build_transaction(wallet, messages, fee.as_proto())
+
+ def build(self, wallet: Wallet, message: Message, fee: Fee):
+ """
+ Builds a transaction with a single message.
+
+ Args:
+ wallet (Wallet): The wallet to use for signing the transaction.
+ message (Message): The message to include in the transaction.
+ fee (Fee): The fee to use for the transaction.
+
+ Returns:
+ The built transaction.
+ """
+ return self.builder.build(wallet, message, fee.as_proto())
+
+ def calculate_fee(self, gas_used) -> Fee:
+ """
+ Calculates the fee based on the gas used.
+
+ Args:
+ gas_used: The amount of gas used.
+
+ Returns:
+ Fee: The calculated fee.
+ """
+ gas_limit, amount = calculate_fee(gas_used)
+ return Fee(gas_limit, [Coin(amount, self.builder.denomination)])
+
+
+@dataclass
+class NodeClient(MutatingNodeClient):
+ @staticmethod
+ async def connect(config: NodeConfig) -> Self:
+ return NodeClient(
+ secure_channel(config.url), Builder(config.chain_id, config.usdc_denom)
+ )
+
+ async def deposit(
+ self,
+ wallet: Wallet,
+ sender: str,
+ recipient_subaccount: SubaccountId,
+ asset_id: int,
+ quantums: int,
+ ):
+ """
+ Deposits funds into a subaccount.
+
+ Args:
+ wallet (Wallet): The wallet to use for signing the transaction.
+ sender (str): The sender address.
+ recipient_subaccount (SubaccountId): The recipient subaccount ID.
+ asset_id (int): The asset ID.
+ quantums (int): The amount of quantums to deposit.
+
+ Returns:
+ The response from the transaction broadcast.
+ """
+ return await self.send_message(
+ wallet, deposit(sender, recipient_subaccount, asset_id, quantums)
+ )
+
+ async def withdraw(
+ self,
+ wallet: Wallet,
+ sender_subaccount: SubaccountId,
+ recipient: str,
+ asset_id: int,
+ quantums: int,
+ ):
+ """
+ Withdraws funds from a subaccount.
+
+ Args:
+ wallet (Wallet): The wallet to use for signing the transaction.
+ sender_subaccount (SubaccountId): The sender subaccount ID.
+ recipient (str): The recipient address.
+ asset_id (int): The asset ID.
+ quantums (int): The amount of quantums to withdraw.
+
+ Returns:
+ The response from the transaction broadcast.
+ """
+ return await self.send_message(
+ wallet, withdraw(sender_subaccount, recipient, asset_id, quantums)
+ )
+
+ async def send_token(
+ self,
+ wallet: Wallet,
+ sender: str,
+ recipient: str,
+ quantums: int,
+ denomination: str,
+ ):
+ """
+ Sends tokens from one address to another.
+
+ Args:
+ wallet (Wallet): The wallet to use for signing the transaction.
+ sender (str): The sender address.
+ recipient (str): The recipient address.
+ quantums (int): The amount of quantums to send.
+ denomination (str): The denomination of the token.
+
+ Returns:
+ The response from the transaction broadcast.
+ """
+ return await self.send_message(
+ wallet, send_token(sender, recipient, quantums, denomination)
+ )
+
+ async def transfer(
+ self,
+ wallet: Wallet,
+ sender_subaccount: SubaccountId,
+ recipient_subaccount: SubaccountId,
+ asset_id: int,
+ amount: int,
+ ):
+ """
+ Transfers funds between subaccounts.
+
+ Args:
+ wallet (Wallet): The wallet to use for signing the transaction.
+ sender_subaccount (SubaccountId): The sender subaccount ID.
+ recipient_subaccount (SubaccountId): The recipient subaccount ID.
+ asset_id (int): The asset ID.
+ amount (int): The amount to transfer.
+
+ Returns:
+ The response from the transaction broadcast.
+ """
+ return await self.send_message(
+ wallet,
+ transfer(
+ sender_subaccount,
+ recipient_subaccount,
+ asset_id,
+ amount,
+ ),
+ )
+
+ async def place_order(self, wallet: Wallet, order: Order):
+ """
+ Places an order.
+
+ Args:
+ wallet (Wallet): The wallet to use for signing the transaction.
+ order (Order): The order to place.
+
+ Returns:
+ The response from the transaction broadcast.
+ """
+ return await self.broadcast_message(wallet, place_order(order))
+
+ async def cancel_order(
+ self,
+ wallet: Wallet,
+ order_id: OrderId,
+ good_til_block: int = None,
+ good_til_block_time: int = None,
+ ):
+ """
+ Cancels an order.
+
+ Args:
+ wallet (Wallet): The wallet to use for signing the transaction.
+ order_id (OrderId): The ID of the order to cancel.
+ good_til_block (int, optional): The block number until which the order is valid. Defaults to None.
+ good_til_block_time (int, optional): The block time until which the order is valid. Defaults to None.
+
+ Returns:
+ The response from the transaction broadcast.
+ """
+ return await self.broadcast_message(
+ wallet, cancel_order(order_id, good_til_block, good_til_block_time)
+ )
diff --git a/v4-client-py-v2/dydx_v4_client/node/fee.py b/v4-client-py-v2/dydx_v4_client/node/fee.py
new file mode 100644
index 00000000..1783eb9d
--- /dev/null
+++ b/v4-client-py-v2/dydx_v4_client/node/fee.py
@@ -0,0 +1,37 @@
+from dataclasses import dataclass
+from math import ceil, floor
+from typing import List, Tuple
+
+from v4_proto.cosmos.base.v1beta1.coin_pb2 import Coin as ProtoCoin
+from v4_proto.cosmos.tx.v1beta1.tx_pb2 import Fee as ProtoFee
+
+from dydx_v4_client.config import GAS_MULTIPLIER
+
+GAS_PRICE = 0.025
+DYDX_GAS_PRICE = 25000000000
+
+
+@dataclass
+class Coin:
+ amount: int
+ denomination: str
+
+ def as_proto(self) -> ProtoCoin:
+ return ProtoCoin(amount=str(self.amount), denom=self.denomination)
+
+
+@dataclass
+class Fee:
+ gas_limit: int
+ amount: List[Coin]
+
+ def as_proto(self) -> ProtoFee:
+ return ProtoFee(
+ gas_limit=self.gas_limit,
+ amount=list(map(lambda coin: coin.as_proto(), self.amount)),
+ )
+
+
+def calculate_fee(gas_used) -> Tuple[int, int]:
+ gas_limit = floor(gas_used * GAS_MULTIPLIER)
+ return gas_limit, ceil(gas_limit * GAS_PRICE)
diff --git a/v4-client-py-v2/dydx_v4_client/node/market.py b/v4-client-py-v2/dydx_v4_client/node/market.py
new file mode 100644
index 00000000..7c73c3a2
--- /dev/null
+++ b/v4-client-py-v2/dydx_v4_client/node/market.py
@@ -0,0 +1,78 @@
+import math
+from dataclasses import dataclass
+from datetime import datetime, timedelta
+
+from v4_proto.dydxprotocol.clob.order_pb2 import Order, OrderId
+
+from dydx_v4_client.node.message import order, order_id
+
+
+def since_now(*args, **kwargs) -> int:
+ return int(round((datetime.now() + timedelta(*args, **kwargs)).timestamp()))
+
+
+def round_down(input_value: float, base: float) -> float:
+ return math.floor(input_value / base) * base
+
+
+@dataclass
+class Market:
+ market: dict
+
+ def calculate_quantums(self, size: float) -> int:
+ raw_quantums = size * 10 ** (-self.market["atomicResolution"])
+ quantums = round_down(raw_quantums, self.market["stepBaseQuantums"])
+
+ result = max(quantums, self.market["stepBaseQuantums"])
+ return result
+
+ def calculate_subticks(self, price: float) -> int:
+ QUOTE_QUANTUMS_ATOMIC_RESOLUTION = -6
+ exponent = (
+ self.market["atomicResolution"]
+ - self.market["quantumConversionExponent"]
+ - QUOTE_QUANTUMS_ATOMIC_RESOLUTION
+ )
+ raw_subticks = price * 10**exponent
+ subticks = round_down(raw_subticks, self.market["subticksPerTick"])
+ result = max(subticks, self.market["subticksPerTick"])
+ return result
+
+ def order_id(
+ self, address: str, subaccount_number: int, client_id: int, order_flags: int
+ ) -> OrderId:
+ return order_id(
+ address,
+ subaccount_number,
+ client_id,
+ int(self.market["clobPairId"]),
+ order_flags,
+ )
+
+ def order(
+ self,
+ order_id: Order,
+ side: Order.Side,
+ size: float,
+ price: int,
+ time_in_force: Order.TimeInForce,
+ reduce_only: bool,
+ good_til_block: int = None,
+ good_til_block_time: int = None,
+ client_metadata: int = 0,
+ condition_type: Order.ConditionType = Order.ConditionType.CONDITION_TYPE_UNSPECIFIED,
+ conditional_order_trigger_subticks: int = 0,
+ ) -> Order:
+ return order(
+ order_id,
+ side,
+ self.calculate_quantums(size),
+ self.calculate_subticks(price),
+ time_in_force,
+ reduce_only,
+ good_til_block,
+ good_til_block_time,
+ client_metadata,
+ condition_type,
+ conditional_order_trigger_subticks,
+ )
diff --git a/v4-client-py-v2/dydx_v4_client/node/message.py b/v4-client-py-v2/dydx_v4_client/node/message.py
new file mode 100644
index 00000000..2c8a0d0b
--- /dev/null
+++ b/v4-client-py-v2/dydx_v4_client/node/message.py
@@ -0,0 +1,131 @@
+from v4_proto.cosmos.bank.v1beta1.tx_pb2 import MsgSend
+from v4_proto.cosmos.base.v1beta1.coin_pb2 import Coin
+from v4_proto.dydxprotocol.clob.order_pb2 import Order, OrderId
+from v4_proto.dydxprotocol.clob.tx_pb2 import MsgCancelOrder, MsgPlaceOrder
+from v4_proto.dydxprotocol.sending.transfer_pb2 import (
+ MsgDepositToSubaccount,
+ MsgWithdrawFromSubaccount,
+ Transfer,
+)
+from v4_proto.dydxprotocol.sending.tx_pb2 import MsgCreateTransfer
+from v4_proto.dydxprotocol.subaccounts.subaccount_pb2 import SubaccountId
+
+
+def order(
+ order_id: OrderId,
+ side: Order.Side,
+ quantums: int,
+ subticks: int,
+ time_in_force: Order.TimeInForce,
+ reduce_only: bool,
+ good_til_block: int = None,
+ good_til_block_time: int = None,
+ client_metadata: int = 0,
+ condition_type: Order.ConditionType = Order.ConditionType.CONDITION_TYPE_UNSPECIFIED,
+ conditional_order_trigger_subticks: int = 0,
+):
+ return Order(
+ order_id=order_id,
+ side=side,
+ quantums=quantums,
+ subticks=subticks,
+ good_til_block=good_til_block,
+ good_til_block_time=good_til_block_time,
+ time_in_force=time_in_force,
+ reduce_only=reduce_only,
+ client_metadata=client_metadata,
+ condition_type=condition_type,
+ conditional_order_trigger_subticks=conditional_order_trigger_subticks,
+ )
+
+
+def order_id(
+ address: str,
+ subaccount_number: int,
+ client_id: int,
+ clob_pair_id: int,
+ order_flags: int,
+) -> OrderId:
+ return OrderId(
+ subaccount_id=subaccount(owner=address, number=subaccount_number),
+ client_id=client_id,
+ order_flags=order_flags,
+ clob_pair_id=clob_pair_id,
+ )
+
+
+def subaccount(owner: str, number: int):
+ return SubaccountId(owner=owner, number=number)
+
+
+def place_order(order: Order):
+ return MsgPlaceOrder(order=order)
+
+
+def cancel_order(
+ order_id,
+ good_til_block: int = None,
+ good_til_block_time: int = None,
+):
+ message = MsgCancelOrder(
+ order_id=order_id,
+ good_til_block=good_til_block,
+ good_til_block_time=good_til_block_time,
+ )
+ return message
+
+
+def transfer(
+ sender_subaccount: SubaccountId,
+ recipient_subaccount: SubaccountId,
+ asset_id: int,
+ amount: int,
+):
+
+ msg = Transfer(
+ sender=sender_subaccount,
+ recipient=recipient_subaccount,
+ asset_id=asset_id,
+ amount=amount,
+ )
+
+ return MsgCreateTransfer(transfer=msg)
+
+
+def deposit(
+ sender: str,
+ recipient_subaccount: SubaccountId,
+ asset_id: int,
+ quantums: int,
+):
+ message = MsgDepositToSubaccount(
+ sender=sender,
+ recipient=recipient_subaccount,
+ asset_id=asset_id,
+ quantums=quantums,
+ )
+ return message
+
+
+def withdraw(
+ sender_subaccount: SubaccountId,
+ recipient: str,
+ asset_id: int,
+ quantums: int,
+):
+ message = MsgWithdrawFromSubaccount(
+ sender=sender_subaccount,
+ recipient=recipient,
+ asset_id=asset_id,
+ quantums=quantums,
+ )
+ return message
+
+
+def send_token(sender: str, recipient: str, quantums: int, denomination: str):
+ message = MsgSend(
+ from_address=sender,
+ to_address=recipient,
+ amount=[Coin(amount=str(quantums), denom=denomination)],
+ )
+ return message
diff --git a/v4-client-py-v2/dydx_v4_client/wallet.py b/v4-client-py-v2/dydx_v4_client/wallet.py
new file mode 100644
index 00000000..fa767408
--- /dev/null
+++ b/v4-client-py-v2/dydx_v4_client/wallet.py
@@ -0,0 +1,47 @@
+import hashlib
+from dataclasses import dataclass
+from functools import partial
+from typing import TYPE_CHECKING
+
+import ecdsa
+from bip_utils import Bip39SeedGenerator, Bip44, Bip44Coins
+from v4_proto.cosmos.crypto.secp256k1.keys_pb2 import PubKey
+
+if TYPE_CHECKING:
+ from dydx_v4_client.node.client import NodeClient
+
+
+from_string = partial(
+ ecdsa.SigningKey.from_string, curve=ecdsa.SECP256k1, hashfunc=hashlib.sha256
+)
+
+
+def bytes_from_mnemonic(mnemonic: str) -> bytes:
+ seed = Bip39SeedGenerator(mnemonic).Generate()
+ return (
+ Bip44.FromSeed(seed, Bip44Coins.COSMOS)
+ .DeriveDefaultPath()
+ .PrivateKey()
+ .Raw()
+ .ToBytes()
+ )
+
+
+def from_mnemonic(mnemonic: str) -> ecdsa.SigningKey:
+ return from_string(bytes_from_mnemonic(mnemonic))
+
+
+@dataclass
+class Wallet:
+ key: ecdsa.SigningKey
+ account_number: int
+ sequence: int
+
+ @staticmethod
+ async def from_mnemonic(node: "NodeClient", mnemonic: str, address: str):
+ account = await node.get_account(address)
+ return Wallet(from_mnemonic(mnemonic), account.account_number, account.sequence)
+
+ @property
+ def public_key(self) -> PubKey:
+ return PubKey(key=self.key.get_verifying_key().to_string("compressed"))
diff --git a/v4-client-py-v2/examples/account_endpoints.py b/v4-client-py-v2/examples/account_endpoints.py
new file mode 100644
index 00000000..4e9f5552
--- /dev/null
+++ b/v4-client-py-v2/examples/account_endpoints.py
@@ -0,0 +1,185 @@
+import asyncio
+
+from dydx_v4_client.indexer.rest import IndexerClient
+from dydx_v4_client.indexer.rest.constants import TradingRewardAggregationPeriod
+from dydx_v4_client.network import TESTNET
+from tests.conftest import TEST_ADDRESS
+
+
+async def test_account():
+ indexer = IndexerClient(TESTNET.rest_indexer)
+ test_address = TEST_ADDRESS
+
+ try:
+ response = await indexer.account.get_subaccounts(test_address)
+ subaccounts = response["subaccounts"]
+ print(f"Subaccounts: {subaccounts}")
+ if subaccounts is None:
+ print("Subaccounts is None")
+ else:
+ print(f"Number of subaccounts: {len(subaccounts)}")
+ if len(subaccounts) > 0:
+ subaccount0 = subaccounts[0]
+ subaccount_number = subaccount0["subaccountNumber"]
+ print(f"Subaccount number: {subaccount_number}")
+ except Exception as e:
+ print(f"Error: {e}")
+
+ try:
+ response = await indexer.account.get_subaccount(test_address, 0)
+ subaccount = response["subaccount"]
+ print(f"Subaccount: {subaccount}")
+ if subaccount is None:
+ print("Subaccount is None")
+ else:
+ subaccount_number = subaccount["subaccountNumber"]
+ print(f"Subaccount number: {subaccount_number}")
+ except Exception as e:
+ print(f"Error: {e}")
+
+ try:
+ response = await indexer.account.get_subaccount_asset_positions(test_address, 0)
+ print(f"Response: {response}")
+ if response is None:
+ print("Response is None")
+ else:
+ positions = response["positions"]
+ print(f"Positions: {positions}")
+ if positions is None:
+ print("Positions is None")
+ elif len(positions) > 0:
+ position = positions[0]
+ print(f"Position: {position}")
+ except Exception as e:
+ print(f"Error: {e}")
+
+ try:
+ response = await indexer.account.get_subaccount_perpetual_positions(
+ test_address, 0
+ )
+ print(f"Response: {response}")
+ if response is None:
+ print("Response is None")
+ else:
+ positions = response["positions"]
+ print(f"Positions: {positions}")
+ if positions is None:
+ print("Positions is None")
+ elif len(positions) > 0:
+ position = positions[0]
+ print(f"Position: {position}")
+ except Exception as e:
+ print(f"Error: {e}")
+
+ try:
+ response = await indexer.account.get_subaccount_transfers(test_address, 0)
+ print(f"Response: {response}")
+ if response is None:
+ print("Response is None")
+ else:
+ transfers = response["transfers"]
+ print(f"Transfers: {transfers}")
+ if transfers is None:
+ print("Transfers is None")
+ elif len(transfers) > 0:
+ transfer = transfers[0]
+ print(f"Transfer: {transfer}")
+ except Exception as e:
+ print(f"Error: {e}")
+
+ try:
+ response = await indexer.account.get_subaccount_orders(test_address, 0)
+ print(f"Response: {response}")
+ if response is None:
+ print("Response is None")
+ else:
+ orders = response
+ print(f"Orders: {orders}")
+ if orders is None:
+ print("Orders is None")
+ elif len(orders) > 0:
+ order = orders[0]
+ print(f"Order: {order}")
+ except Exception as e:
+ print(f"Error: {e}")
+
+ try:
+ response = await indexer.account.get_subaccount_fills(test_address, 0)
+ print(f"Response: {response}")
+ if response is None:
+ print("Response is None")
+ else:
+ fills = response["fills"]
+ print(f"Fills: {fills}")
+ if fills is None:
+ print("Fills is None")
+ elif len(fills) > 0:
+ fill = fills[0]
+ print(f"Fill: {fill}")
+ except Exception as e:
+ print(f"Error: {e}")
+
+ try:
+ response = await indexer.account.get_subaccount_historical_pnls(test_address, 0)
+ print(f"Response: {response}")
+ if response is None:
+ print("Response is None")
+ else:
+ historical_pnl = response["historicalPnl"]
+ print(f"Historical PnL: {historical_pnl}")
+ if historical_pnl is None:
+ print("Historical PnL is None")
+ elif len(historical_pnl) > 0:
+ historical_pnl0 = historical_pnl[0]
+ print(f"Historical PnL (first entry): {historical_pnl0}")
+ except Exception as e:
+ print(f"Error: {e}")
+
+ try:
+ limit = 10
+ response = await indexer.account.get_historical_block_trading_rewards(
+ test_address, limit
+ )
+ historical_rewards = response["rewards"]
+ print(f"Historical rewards: {historical_rewards}")
+ if historical_rewards is None:
+ print("Historical rewards is None")
+ else:
+ print(f"Historical rewards type: {type(historical_rewards)}")
+ print(f"Number of historical rewards: {len(historical_rewards)}")
+ if len(historical_rewards) > 0:
+ first_reward = historical_rewards[0]
+ print(f"First historical reward: {first_reward}")
+ print(f"createdAt: {first_reward.get('createdAt')}")
+ print(f"createdAtHeight: {first_reward.get('createdAtHeight')}")
+ print(f"tradingReward: {first_reward.get('tradingReward')}")
+ except Exception as e:
+ print(f"Error: {e}")
+
+ try:
+ period = TradingRewardAggregationPeriod.DAILY
+ limit = 10
+ response = await indexer.account.get_historical_trading_rewards_aggregated(
+ test_address, period, limit
+ )
+ aggregations = response["rewards"]
+ print(f"Aggregations: {aggregations}")
+ if aggregations is None:
+ print("Aggregations is None")
+ else:
+ print(f"Aggregations type: {type(aggregations)}")
+ print(f"Number of aggregations: {len(aggregations)}")
+ if len(aggregations) > 0:
+ for aggregation in aggregations:
+ print(f"Aggregation: {aggregation}")
+ print(f"Period: {aggregation.get('period')}")
+ print(f"Trading reward: {aggregation.get('tradingReward')}")
+ print(f"Started at: {aggregation.get('startedAt')}")
+ print(f"Ended at: {aggregation.get('endedAt')}")
+ print(f"Started at height: {aggregation.get('startedAtHeight')}")
+ print(f"Ended at height: {aggregation.get('endedAtHeight')}")
+ except Exception as e:
+ print(f"Error: {e}")
+
+
+asyncio.run(test_account())
diff --git a/v4-client-py-v2/examples/basic_adder.py b/v4-client-py-v2/examples/basic_adder.py
new file mode 100644
index 00000000..d346e65f
--- /dev/null
+++ b/v4-client-py-v2/examples/basic_adder.py
@@ -0,0 +1,229 @@
+import asyncio
+import logging
+from decimal import Decimal
+
+from v4_proto.dydxprotocol.clob.order_pb2 import OrderId
+
+from dydx_v4_client.indexer.rest.constants import OrderSide, OrderTimeInForce
+from dydx_v4_client.indexer.socket.websocket import (
+ IndexerSocket,
+ OrderBook,
+ Subaccounts,
+)
+from dydx_v4_client.network import TESTNET
+from dydx_v4_client.node.client import NodeClient
+from dydx_v4_client.node.message import order, order_id
+from dydx_v4_client.wallet import Wallet, from_string
+from tests.conftest import DYDX_TEST_PRIVATE_KEY
+
+logging.basicConfig(
+ level=logging.INFO,
+ format="%(asctime)s - %(levelname)s - %(message)s",
+ handlers=[
+ logging.StreamHandler(),
+ ],
+)
+
+
+COIN = "ETH"
+MARKET = f"{COIN}-USD"
+DEPTH = Decimal("0.003")
+ALLOWABLE_DEVIATION = Decimal("0.5")
+MAX_POSITION = Decimal("1.0")
+
+
+class BasicAdder:
+ def __init__(
+ self, node_client: NodeClient, address: str, key: str, subaccount_number: int
+ ):
+ self.address = address
+ self.key = from_string(bytes.fromhex(key))
+ self.subaccount_number = subaccount_number
+ self.node_client = node_client
+ self.testnet_indexer_socket = IndexerSocket(
+ TESTNET.websocket_indexer,
+ on_open=self.on_open,
+ on_message=self.on_message,
+ )
+ self.position = None
+ self.provide_state = {
+ OrderSide.BUY: {"type": "cancelled"},
+ OrderSide.SELL: {"type": "cancelled"},
+ }
+
+ def on_open(self, ws):
+ ws.subaccounts.subscribe(
+ address=self.address, subaccount_number=self.subaccount_number
+ )
+ ws.markets.subscribe()
+ ws.trades.subscribe(id=MARKET)
+ ws.order_book.subscribe(id=MARKET)
+ logging.info(
+ "Testnet WebSocket is subscribed to subaccounts, markets, trades, order_book"
+ )
+
+ def on_message(self, ws, message):
+ if message.get("channel") == Subaccounts.channel:
+ asyncio.run(self.on_subaccount_update(message))
+ if message.get("channel") == OrderBook.channel:
+ asyncio.run(self.on_order_book_update(message))
+
+ async def on_order_book_update(self, message):
+ logging.info(f"Order book update: {message}")
+ if message["id"] != MARKET:
+ return
+
+ if message["type"] == "subscribed":
+ bids = [
+ (Decimal(item["price"]), Decimal(item["size"]))
+ for item in message["contents"]["bids"]
+ ]
+ asks = [
+ (Decimal(item["price"]), Decimal(item["size"]))
+ for item in message["contents"]["asks"]
+ ]
+ elif message["type"] == "channel_batch_data":
+ bids = []
+ asks = []
+ for item in message["contents"]:
+ if "bids" in item:
+ bids.append(
+ (Decimal(item["bids"][0][0]), Decimal(item["bids"][0][1]))
+ )
+ elif "asks" in item:
+ asks.append(
+ (Decimal(item["asks"][0][0]), Decimal(item["asks"][0][1]))
+ )
+ else:
+ logging.warning(f"Unsupported order book message type: {message['type']}")
+ return
+
+ for side, book in [(OrderSide.BUY, bids), (OrderSide.SELL, asks)]:
+ if book:
+ book_price, _ = book[0]
+ ideal_distance = book_price * DEPTH
+ ideal_price = book_price + (
+ ideal_distance if side == OrderSide.BUY else -ideal_distance
+ )
+
+ provide_state = self.provide_state[side]
+ if provide_state["type"] == "resting":
+ distance = abs(ideal_price - Decimal(provide_state["px"]))
+ if distance > ALLOWABLE_DEVIATION * ideal_distance:
+ oid = provide_state["oid"]
+ logging.info(
+ f"Cancelling order due to deviation oid:{oid} side:{side} "
+ f"ideal_price:{ideal_price} px:{provide_state['px']}"
+ )
+ await self.cancel_order(oid)
+ self.provide_state[side] = {"type": "cancelled"}
+ elif provide_state["type"] == "in_flight_order":
+ logging.info("Not placing an order because in flight")
+ continue
+ elif provide_state["type"] == "cancelled":
+ if self.position is None:
+ logging.info(
+ "Not placing an order because waiting for next position refresh"
+ )
+ continue
+ size = MAX_POSITION + (
+ self.position if side == OrderSide.BUY else -self.position
+ )
+ if size * ideal_price < Decimal("10"):
+ logging.info("Not placing an order because at position limit")
+ continue
+ px = str(ideal_price)
+ logging.info(f"Placing order size:{size} px:{px} side:{side}")
+ self.provide_state[side] = {"type": "in_flight_order"}
+ oid = await self.place_order(side, size, px)
+ if oid:
+ self.provide_state[side] = {
+ "type": "resting",
+ "px": px,
+ "oid": oid,
+ }
+ else:
+ self.provide_state[side] = {"type": "cancelled"}
+
+ async def on_subaccount_update(self, message):
+ data = message["contents"]["subaccount"]
+ self.position = sum(
+ Decimal(position["size"])
+ for position in data["openPerpetualPositions"].values()
+ if position["market"] == MARKET
+ )
+ logging.info(f"Updated position for {MARKET}: {self.position}")
+
+ async def place_order(self, side: OrderSide, size: float, px: Decimal):
+ account = await self.node_client.get_account(self.address)
+ oid = order_id(
+ address=self.address,
+ subaccount_number=self.subaccount_number,
+ client_id=0,
+ clob_pair_id=0,
+ order_flags=0,
+ )
+ time_in_force = (
+ OrderTimeInForce.GTT if side == OrderSide.BUY else OrderTimeInForce.IOC
+ )
+ quantums = int(Decimal(size) * Decimal("1e18"))
+ subticks = int(Decimal(px) * Decimal("1e5"))
+
+ clob_pair = await self.node_client.get_clob_pair(0)
+ step_base_quantums = clob_pair.step_base_quantums
+ subticks_per_tick = clob_pair.subticks_per_tick
+
+ current_block = await self.node_client.latest_block_height()
+ new_order = order(
+ order_id=oid,
+ side=side,
+ quantums=(quantums // step_base_quantums) * step_base_quantums,
+ subticks=(subticks // subticks_per_tick) * subticks_per_tick,
+ time_in_force=time_in_force,
+ reduce_only=False,
+ good_til_block=current_block + 10,
+ )
+
+ transaction = await self.node_client.place_order(
+ wallet=Wallet(
+ key=self.key,
+ sequence=account.sequence,
+ account_number=account.account_number,
+ ),
+ order=new_order,
+ )
+ logging.info(f"Placed order transaction: {transaction}")
+
+ async def cancel_order(self, oid: OrderId):
+ account = await self.node_client.get_account(self.address)
+ current_block = await self.node_client.latest_block_height()
+ transaction = await self.node_client.cancel_order(
+ wallet=Wallet(
+ key=self.key,
+ sequence=account.sequence,
+ account_number=account.account_number,
+ ),
+ order_id=oid,
+ good_til_block=current_block + 10,
+ )
+ logging.info(f"Cancelled order transaction: {transaction}")
+
+
+async def main():
+ address: str = "dydx14zzueazeh0hj67cghhf9jypslcf9sh2n5k6art"
+ key: str = DYDX_TEST_PRIVATE_KEY
+ subaccount_number: int = 0
+
+ node = await NodeClient.connect(TESTNET.node)
+
+ adder = BasicAdder(node, address, key, subaccount_number)
+
+ await adder.testnet_indexer_socket.connect()
+
+
+if __name__ == "__main__":
+ import nest_asyncio
+
+ nest_asyncio.apply()
+ logging.info("Starting Basic Adder")
+ asyncio.run(main())
diff --git a/v4-client-py-v2/examples/faucet_endpoint.py b/v4-client-py-v2/examples/faucet_endpoint.py
new file mode 100644
index 00000000..7397d288
--- /dev/null
+++ b/v4-client-py-v2/examples/faucet_endpoint.py
@@ -0,0 +1,15 @@
+import asyncio
+
+from dydx_v4_client.faucet_client import FaucetClient
+from dydx_v4_client.network import TESTNET_FAUCET
+from tests.conftest import TEST_ADDRESS
+
+
+async def test():
+ faucet = FaucetClient(TESTNET_FAUCET)
+ response = await faucet.fill(TEST_ADDRESS, 0, 2000)
+ print(response)
+ print(response.status)
+
+
+asyncio.run(test())
diff --git a/v4-client-py-v2/examples/human_readable_short_term_orders.json b/v4-client-py-v2/examples/human_readable_short_term_orders.json
new file mode 100644
index 00000000..ee4d77fe
--- /dev/null
+++ b/v4-client-py-v2/examples/human_readable_short_term_orders.json
@@ -0,0 +1,43 @@
+[
+ {
+ "timeInForce": "DEFAULT",
+ "side": "BUY",
+ "price": 40000
+ },
+ {
+ "timeInForce": "DEFAULT",
+ "side": "SELL",
+ "price": 1000
+ },
+ {
+ "timeInForce": "FOK",
+ "side": "BUY",
+ "price": 1000
+ },
+ {
+ "timeInForce": "FOK",
+ "side": "SELL",
+ "price": 40000
+ },
+ {
+ "timeInForce": "IOC",
+ "side": "BUY",
+ "price": 40000
+ },
+ {
+ "timeInForce": "IOC",
+ "side": "SELL",
+ "price": 1000
+ },
+ {
+ "timeInForce": "POST_ONLY",
+ "side": "BUY",
+ "price": 1000
+ },
+ {
+ "timeInForce": "POST_ONLY",
+ "side": "SELL",
+ "price": 40000
+ }
+ ]
+
\ No newline at end of file
diff --git a/v4-client-py-v2/examples/long_term_order_cancel_example.py b/v4-client-py-v2/examples/long_term_order_cancel_example.py
new file mode 100644
index 00000000..2cd78a1b
--- /dev/null
+++ b/v4-client-py-v2/examples/long_term_order_cancel_example.py
@@ -0,0 +1,52 @@
+import asyncio
+import random
+
+from dydx_v4_client import MAX_CLIENT_ID, NodeClient, Order, OrderFlags
+from dydx_v4_client.indexer.rest.indexer_client import IndexerClient
+from dydx_v4_client.network import TESTNET
+from dydx_v4_client.node.market import Market, since_now
+from dydx_v4_client.wallet import Wallet
+from tests.conftest import DYDX_TEST_MNEMONIC, TEST_ADDRESS
+
+MARKET_ID = "ETH-USD"
+
+
+async def test():
+
+ node = await NodeClient.connect(TESTNET.node)
+ indexer = IndexerClient(TESTNET.rest_indexer)
+
+ market = Market(
+ (await indexer.markets.get_perpetual_markets(MARKET_ID))["markets"][MARKET_ID]
+ )
+ wallet = await Wallet.from_mnemonic(node, DYDX_TEST_MNEMONIC, TEST_ADDRESS)
+
+ order_id = market.order_id(
+ TEST_ADDRESS, 0, random.randint(0, MAX_CLIENT_ID), OrderFlags.LONG_TERM
+ )
+
+ place = await node.place_order(
+ wallet,
+ market.order(
+ order_id,
+ Order.Side.SIDE_SELL,
+ size=0.01,
+ price=40000,
+ time_in_force=Order.TIME_IN_FORCE_UNSPECIFIED,
+ reduce_only=False,
+ good_til_block_time=since_now(seconds=60),
+ ),
+ )
+ print(place)
+ # FIXME(piwonskp): Remove
+ wallet.sequence += 1
+
+ cancel = await node.cancel_order(
+ wallet,
+ order_id,
+ good_til_block_time=since_now(seconds=120),
+ )
+ print(cancel)
+
+
+asyncio.run(test())
diff --git a/v4-client-py-v2/examples/raw_orders.json b/v4-client-py-v2/examples/raw_orders.json
new file mode 100644
index 00000000..c9bc297b
--- /dev/null
+++ b/v4-client-py-v2/examples/raw_orders.json
@@ -0,0 +1,130 @@
+[
+ {
+ "timeInForce": 0,
+ "reduceOnly": false,
+ "orderFlags": 64,
+ "side": 1,
+ "quantums": 10000000,
+ "subticks": 40000000000
+ },
+ {
+ "timeInForce": 2,
+ "reduceOnly": false,
+ "orderFlags": 64 ,
+ "side": 1,
+ "quantums": 10000000,
+ "subticks": 40000000000
+ },
+ {
+ "timeInForce": 0,
+ "reduceOnly": true,
+ "orderFlags": 64,
+ "side": 1,
+ "quantums": 10000000,
+ "subticks": 40000000000
+ },
+ {
+ "timeInForce": 2,
+ "reduceOnly": true,
+ "orderFlags": 64 ,
+ "side": 1,
+ "quantums": 10000000,
+ "subticks": 40000000000
+ },
+ {
+ "timeInForce": 1,
+ "reduceOnly": false,
+ "orderFlags": 0,
+ "side": 1,
+ "quantums": 10000000,
+ "subticks": 40000000000
+ },
+ {
+ "timeInForce": 1,
+ "reduceOnly": true,
+ "orderFlags": 0 ,
+ "side": 1,
+ "quantums": 10000000,
+ "subticks": 40000000000
+ },
+ {
+ "timeInForce": 3,
+ "reduceOnly": false,
+ "orderFlags": 0,
+ "side": 1,
+ "quantums": 10000000,
+ "subticks": 40000000000
+ },
+ {
+ "timeInForce": 3,
+ "reduceOnly": true,
+ "orderFlags": 0 ,
+ "side": 1,
+ "quantums": 10000000,
+ "subticks": 40000000000
+ },
+ {
+ "timeInForce": 0,
+ "reduceOnly": false,
+ "orderFlags": 64,
+ "side": 2,
+ "quantums": 10000000,
+ "subticks": 1000000000
+ },
+ {
+ "timeInForce": 2,
+ "reduceOnly": false,
+ "orderFlags": 64 ,
+ "side": 2,
+ "quantums": 10000000,
+ "subticks": 1000000000
+ },
+ {
+ "timeInForce": 0,
+ "reduceOnly": true,
+ "orderFlags": 64,
+ "side": 2,
+ "quantums": 10000000,
+ "subticks": 1000000000
+ },
+ {
+ "timeInForce": 2,
+ "reduceOnly": true,
+ "orderFlags": 64 ,
+ "side": 2,
+ "quantums": 10000000,
+ "subticks": 1000000000
+ },
+ {
+ "timeInForce": 1,
+ "reduceOnly": false,
+ "orderFlags": 0,
+ "side": 2,
+ "quantums": 10000000,
+ "subticks": 1000000000
+ },
+ {
+ "timeInForce": 1,
+ "reduceOnly": true,
+ "orderFlags": 0 ,
+ "side": 2,
+ "quantums": 10000000,
+ "subticks": 1000000000
+ },
+ {
+ "timeInForce": 3,
+ "reduceOnly": false,
+ "orderFlags": 0,
+ "side": 2,
+ "quantums": 10000000,
+ "subticks": 1000000000
+ },
+ {
+ "timeInForce": 3,
+ "reduceOnly": true,
+ "orderFlags": 0 ,
+ "side": 2,
+ "quantums": 10000000,
+ "subticks": 1000000000
+ }
+]
diff --git a/v4-client-py-v2/examples/short_term_order_cancel_example.py b/v4-client-py-v2/examples/short_term_order_cancel_example.py
new file mode 100644
index 00000000..2630d6a4
--- /dev/null
+++ b/v4-client-py-v2/examples/short_term_order_cancel_example.py
@@ -0,0 +1,54 @@
+import asyncio
+import random
+import time
+
+from dydx_v4_client import MAX_CLIENT_ID, NodeClient, Order, OrderFlags, Wallet
+from dydx_v4_client.indexer.rest.indexer_client import IndexerClient
+from dydx_v4_client.network import TESTNET
+from dydx_v4_client.node.market import Market
+from tests.conftest import DYDX_TEST_MNEMONIC, TEST_ADDRESS
+
+MARKET_ID = "ETH-USD"
+
+
+async def test():
+
+ node = await NodeClient.connect(TESTNET.node)
+ indexer = IndexerClient(TESTNET.rest_indexer)
+
+ market = Market(
+ (await indexer.markets.get_perpetual_markets(MARKET_ID))["markets"][MARKET_ID]
+ )
+ wallet = await Wallet.from_mnemonic(node, DYDX_TEST_MNEMONIC, TEST_ADDRESS)
+
+ current_block = await node.latest_block_height()
+ good_til_block = current_block + 1 + 10
+
+ order_id = market.order_id(
+ TEST_ADDRESS, 0, random.randint(0, MAX_CLIENT_ID), OrderFlags.SHORT_TERM
+ )
+
+ place = await node.place_order(
+ wallet,
+ market.order(
+ order_id,
+ Order.Side.SIDE_SELL,
+ size=0.01,
+ price=40000,
+ time_in_force=Order.TIME_IN_FORCE_UNSPECIFIED,
+ reduce_only=False,
+ good_til_block=good_til_block,
+ ),
+ )
+ print(place)
+ # FIXME(piwonskp): Remove
+ wallet.sequence += 1
+ time.sleep(5)
+
+ cancel = await node.cancel_order(
+ wallet, order_id, good_til_block=good_til_block + 10
+ )
+ print(cancel)
+
+
+asyncio.run(test())
diff --git a/v4-client-py-v2/examples/short_term_order_composite_example.py b/v4-client-py-v2/examples/short_term_order_composite_example.py
new file mode 100644
index 00000000..bf873655
--- /dev/null
+++ b/v4-client-py-v2/examples/short_term_order_composite_example.py
@@ -0,0 +1,65 @@
+import asyncio
+import json
+import random
+import time
+from pathlib import Path
+
+from dydx_v4_client import MAX_CLIENT_ID, NodeClient, Order, OrderFlags, Wallet
+from dydx_v4_client.indexer.rest.indexer_client import IndexerClient
+from dydx_v4_client.network import TESTNET
+from dydx_v4_client.node.market import Market
+from tests.conftest import DYDX_TEST_MNEMONIC, TEST_ADDRESS
+
+MARKET_ID = "ETH-USD"
+
+order_execution_to_time_in_force = {
+ "DEFAULT": Order.TIME_IN_FORCE_UNSPECIFIED,
+ "FOK": Order.TIME_IN_FORCE_FILL_OR_KILL,
+ "IOC": Order.TIME_IN_FORCE_IOC,
+ "POST_ONLY": Order.TIME_IN_FORCE_POST_ONLY,
+}
+
+to_order_side = {"BUY": Order.Side.SIDE_BUY, "SELL": Order.Side.SIDE_SELL}
+
+
+with open(Path(__file__).parent / "human_readable_short_term_orders.json", "r") as file:
+ orders = json.load(file)
+
+
+async def test():
+
+ node = await NodeClient.connect(TESTNET.node)
+ indexer = IndexerClient(TESTNET.rest_indexer)
+
+ market = Market(
+ (await indexer.markets.get_perpetual_markets(MARKET_ID))["markets"][MARKET_ID]
+ )
+ wallet = await Wallet.from_mnemonic(node, DYDX_TEST_MNEMONIC, TEST_ADDRESS)
+
+ for order in orders:
+ current_block = await node.latest_block_height()
+ good_til_block = current_block + 1 + 10
+
+ order_id = market.order_id(
+ TEST_ADDRESS, 0, random.randint(0, MAX_CLIENT_ID), OrderFlags.SHORT_TERM
+ )
+
+ place = await node.place_order(
+ wallet,
+ market.order(
+ order_id,
+ to_order_side[order["side"]],
+ size=0.01,
+ price=order.get("price", 1350),
+ time_in_force=order_execution_to_time_in_force[order["timeInForce"]],
+ reduce_only=False,
+ good_til_block=good_til_block,
+ ),
+ )
+ print(place)
+ # FIXME(piwonskp): Remove
+ wallet.sequence += 1
+ time.sleep(5)
+
+
+asyncio.run(test())
diff --git a/v4-client-py-v2/examples/transfer_example_deposit.py b/v4-client-py-v2/examples/transfer_example_deposit.py
new file mode 100644
index 00000000..d0deeb6d
--- /dev/null
+++ b/v4-client-py-v2/examples/transfer_example_deposit.py
@@ -0,0 +1,24 @@
+import asyncio
+
+from dydx_v4_client import NodeClient
+from dydx_v4_client.network import TESTNET
+from dydx_v4_client.node.message import subaccount
+from dydx_v4_client.wallet import Wallet
+from tests.conftest import DYDX_TEST_MNEMONIC, TEST_ADDRESS
+
+
+async def test():
+ # Create the client
+ node = await NodeClient.connect(TESTNET.node)
+
+ # Create the wallet
+ wallet = await Wallet.from_mnemonic(node, DYDX_TEST_MNEMONIC, TEST_ADDRESS)
+
+ # Call the API - deposit funds
+ response = await node.deposit(
+ wallet, TEST_ADDRESS, subaccount(TEST_ADDRESS, 0), 0, 10_000_000
+ )
+ print(response)
+
+
+asyncio.run(test())
diff --git a/v4-client-py-v2/examples/transfer_example_transfer.py b/v4-client-py-v2/examples/transfer_example_transfer.py
new file mode 100644
index 00000000..a415b298
--- /dev/null
+++ b/v4-client-py-v2/examples/transfer_example_transfer.py
@@ -0,0 +1,23 @@
+import asyncio
+
+from dydx_v4_client import NodeClient
+from dydx_v4_client.network import TESTNET
+from dydx_v4_client.node.message import subaccount
+from dydx_v4_client.wallet import Wallet
+from tests.conftest import DYDX_TEST_MNEMONIC, TEST_ADDRESS
+
+
+async def test():
+ node = await NodeClient.connect(TESTNET.node)
+
+ wallet = await Wallet.from_mnemonic(node, DYDX_TEST_MNEMONIC, TEST_ADDRESS)
+ sender_subaccount = subaccount(TEST_ADDRESS, 0)
+ recipient_subaccount = subaccount(TEST_ADDRESS, 1)
+
+ response = await node.transfer(
+ wallet, sender_subaccount, recipient_subaccount, 0, 10_000_000
+ )
+ print(response)
+
+
+asyncio.run(test())
diff --git a/v4-client-py-v2/examples/transfer_example_withdraw.py b/v4-client-py-v2/examples/transfer_example_withdraw.py
new file mode 100644
index 00000000..aec9dc2b
--- /dev/null
+++ b/v4-client-py-v2/examples/transfer_example_withdraw.py
@@ -0,0 +1,21 @@
+import asyncio
+
+from dydx_v4_client import NodeClient
+from dydx_v4_client.network import TESTNET
+from dydx_v4_client.node.message import subaccount
+from dydx_v4_client.wallet import Wallet
+from tests.conftest import DYDX_TEST_MNEMONIC, TEST_ADDRESS
+
+
+async def test():
+ node = await NodeClient.connect(TESTNET.node)
+
+ wallet = await Wallet.from_mnemonic(node, DYDX_TEST_MNEMONIC, TEST_ADDRESS)
+
+ response = await node.withdraw(
+ wallet, subaccount(TEST_ADDRESS, 0), TEST_ADDRESS, 0, 1_00_000_000
+ )
+ print(response)
+
+
+asyncio.run(test())
diff --git a/v4-client-py-v2/examples/transfer_example_withdraw_other.py b/v4-client-py-v2/examples/transfer_example_withdraw_other.py
new file mode 100644
index 00000000..af3194c8
--- /dev/null
+++ b/v4-client-py-v2/examples/transfer_example_withdraw_other.py
@@ -0,0 +1,36 @@
+import asyncio
+from functools import partial
+
+from dydx_v4_client import NodeClient
+from dydx_v4_client.network import TESTNET
+from dydx_v4_client.node.message import subaccount, withdraw
+from dydx_v4_client.wallet import Wallet
+from tests.conftest import DYDX_TEST_MNEMONIC, TEST_ADDRESS
+
+
+async def test():
+ node = await NodeClient.connect(TESTNET.node)
+
+ wallet = await Wallet.from_mnemonic(node, DYDX_TEST_MNEMONIC, TEST_ADDRESS)
+
+ message = partial(withdraw, subaccount(TEST_ADDRESS, 0), TEST_ADDRESS, 0)
+ amount = 100_000_000
+
+ simulated = node.builder.build(wallet, message(amount))
+
+ simulation = await node.simulate(simulated)
+ print("**Simulate**")
+ print(simulation)
+
+ fee = node.calculate_fee(simulation.gas_info.gas_used)
+ print("**Total Fee**")
+ print(fee)
+
+ response = await node.broadcast(
+ node.build(wallet, message(amount - fee.amount[0].amount), fee)
+ )
+ print("**Withdraw and Send**")
+ print(response)
+
+
+asyncio.run(test())
diff --git a/v4-client-py-v2/examples/validator_get_example.py b/v4-client-py-v2/examples/validator_get_example.py
new file mode 100644
index 00000000..e07216f5
--- /dev/null
+++ b/v4-client-py-v2/examples/validator_get_example.py
@@ -0,0 +1,197 @@
+import asyncio
+
+from dydx_v4_client.network import TESTNET
+from dydx_v4_client.node.client import NodeClient
+from tests.conftest import TEST_ADDRESS
+
+
+async def test():
+ node_client = await NodeClient.connect(TESTNET.node)
+ try:
+ result = await node_client.get_account_balances(TEST_ADDRESS)
+ print("Get Account Balances:")
+ print(result)
+ except Exception as e:
+ print("Error in get_account_balances:")
+ print(f"Error: {e}")
+
+ try:
+ account = await node_client.get_account(TEST_ADDRESS)
+ print("Get Account:")
+ print(account)
+ except Exception as e:
+ print("Error in get_account:")
+ print(f"Error: {e}")
+
+ try:
+ response = await node_client.get_account_balances(TEST_ADDRESS)
+ print("Get Account Balances:")
+ print(response)
+ except Exception as e:
+ print("Error in get_account_balances:")
+ print(f"Error: {e}")
+
+ try:
+ response = await node_client.get_account_balance(TEST_ADDRESS, "usdc")
+ print("Get Account Balance:")
+ print(response)
+ except Exception as e:
+ print("Error in get_account_balance:")
+ print(f"Error: {e}")
+
+ try:
+ block = await node_client.latest_block()
+ print("Get Latest Block:")
+ print(block)
+ except Exception as e:
+ print("Error in latest_block:")
+ print(f"Error: {e}")
+
+ try:
+ height = await node_client.latest_block_height()
+ print("Get Latest Block Height:")
+ print(height)
+ except Exception as e:
+ print("Error in latest_block_height:")
+ print(f"Error: {e}")
+
+ try:
+ stats = await node_client.get_user_stats(TEST_ADDRESS)
+ print("Get User Stats:")
+ print(stats)
+ except Exception as e:
+ print("Error in get_user_stats:")
+ print(f"Error: {e}")
+
+ try:
+ validators = await node_client.get_all_validators()
+ print("Get All Validators:")
+ print(validators)
+ except Exception as e:
+ print("Error in get_all_validators:")
+ print(f"Error: {e}")
+
+ try:
+ subaccount = await node_client.get_subaccount(TEST_ADDRESS, 0)
+ print("Get Subaccount:")
+ print(subaccount)
+ except Exception as e:
+ print("Error in get_subaccount:")
+ print(f"Error: {e}")
+
+ try:
+ subaccounts = await node_client.get_subaccounts()
+ print("Get Subaccounts:")
+ print(subaccounts)
+ except Exception as e:
+ print("Error in get_subaccounts:")
+ print(f"Error: {e}")
+
+ try:
+ clob_pair = await node_client.get_clob_pair(1)
+ print("Get CLOB Pair:")
+ print(clob_pair)
+ except Exception as e:
+ print("Error in get_clob_pair:")
+ print(f"Error: {e}")
+
+ try:
+ clob_pairs = await node_client.get_clob_pairs()
+ print("Get CLOB Pairs:")
+ print(clob_pairs)
+ except Exception as e:
+ print("Error in get_clob_pairs:")
+ print(f"Error: {e}")
+
+ try:
+ price = await node_client.get_price(1)
+ print("Get Price:")
+ print(price)
+ except Exception as e:
+ print("Error in get_price:")
+ print(f"Error: {e}")
+
+ try:
+ prices = await node_client.get_prices()
+ print("Get Prices:")
+ print(prices)
+ except Exception as e:
+ print("Error in get_prices:")
+ print(f"Error: {e}")
+
+ try:
+ perpetual = await node_client.get_perpetual(1)
+ print("Get Perpetual:")
+ print(perpetual)
+ except Exception as e:
+ print("Error in get_perpetual:")
+ print(f"Error: {e}")
+
+ try:
+ perpetuals = await node_client.get_perpetuals()
+ print("Get Perpetuals:")
+ print(perpetuals)
+ except Exception as e:
+ print("Error in get_perpetuals:")
+ print(f"Error: {e}")
+
+ try:
+ config = await node_client.get_equity_tier_limit_config()
+ print("Get Equity Tier Limit Config:")
+ print(config)
+ except Exception as e:
+ print("Error in get_equity_tier_limit_config:")
+ print(f"Error: {e}")
+
+ try:
+ delegations = await node_client.get_delegator_delegations(TEST_ADDRESS)
+ print("Get Delegator Delegations:")
+ print(delegations)
+ except Exception as e:
+ print("Error in get_delegator_delegations:")
+ print(f"Error: {e}")
+
+ try:
+ unbonding_delegations = await node_client.get_delegator_unbonding_delegations(
+ TEST_ADDRESS
+ )
+ print("Get Delegator Unbonding Delegations:")
+ print(unbonding_delegations)
+ except Exception as e:
+ print("Error in get_delegator_unbonding_delegations:")
+ print(f"Error: {e}")
+
+ try:
+ bridge_messages = await node_client.get_delayed_complete_bridge_messages()
+ print("Get Delayed Complete Bridge Messages:")
+ print(bridge_messages)
+ except Exception as e:
+ print("Error in get_delayed_complete_bridge_messages:")
+ print(f"Error: {e}")
+
+ try:
+ fee_tiers = await node_client.get_fee_tiers()
+ print("Get Fee Tiers:")
+ print(fee_tiers)
+ except Exception as e:
+ print("Error in get_fee_tiers:")
+ print(f"Error: {e}")
+
+ try:
+ user_fee_tier = await node_client.get_user_fee_tier(TEST_ADDRESS)
+ print("Get User Fee Tier:")
+ print(user_fee_tier)
+ except Exception as e:
+ print("Error in get_user_fee_tier:")
+ print(f"Error: {e}")
+
+ try:
+ rewards_params = await node_client.get_rewards_params()
+ print("Get Rewards Params:")
+ print(rewards_params)
+ except Exception as e:
+ print("Error in get_rewards_params:")
+ print(f"Error: {e}")
+
+
+asyncio.run(test())
diff --git a/v4-client-py-v2/examples/validator_post_example.py b/v4-client-py-v2/examples/validator_post_example.py
new file mode 100644
index 00000000..6044e56f
--- /dev/null
+++ b/v4-client-py-v2/examples/validator_post_example.py
@@ -0,0 +1,60 @@
+import asyncio
+import json
+import random
+import time
+from pathlib import Path
+
+from dydx_v4_client import MAX_CLIENT_ID, NodeClient, Order, OrderFlags, Wallet
+from dydx_v4_client.indexer.rest.indexer_client import IndexerClient
+from dydx_v4_client.network import TESTNET
+from dydx_v4_client.node.market import Market
+from dydx_v4_client.node.message import order, order_id
+from tests.conftest import DYDX_TEST_MNEMONIC, TEST_ADDRESS
+
+PERPETUAL_PAIR_BTC_USD = 0
+
+with open(Path(__file__).parent / "raw_orders.json", "r") as file:
+ orders = json.load(file)
+
+
+async def test():
+ node = await NodeClient.connect(TESTNET.node)
+ wallet = await Wallet.from_mnemonic(node, DYDX_TEST_MNEMONIC, TEST_ADDRESS)
+
+ for order_dict in orders:
+ id = order_id(
+ TEST_ADDRESS,
+ 0,
+ random.randint(0, MAX_CLIENT_ID),
+ PERPETUAL_PAIR_BTC_USD,
+ order_dict["orderFlags"],
+ )
+
+ good_til_block = None
+ good_til_block_time = round(time.time() + 60)
+ if order_dict["orderFlags"] == 0:
+ good_til_block_time = None
+ current_block = await node.latest_block_height()
+ good_til_block = current_block + 3
+
+ place = await node.place_order(
+ wallet,
+ order(
+ id,
+ order_dict["side"],
+ quantums=order_dict["quantums"],
+ subticks=order_dict["subticks"],
+ time_in_force=order_dict["timeInForce"],
+ reduce_only=False,
+ good_til_block=good_til_block,
+ good_til_block_time=good_til_block_time,
+ ),
+ )
+ print("**Order Tx**")
+ print(place)
+ # FIXME: Remove
+ wallet.sequence += 1
+ time.sleep(5)
+
+
+asyncio.run(test())
diff --git a/v4-client-py-v2/examples/websocket_example.py b/v4-client-py-v2/examples/websocket_example.py
new file mode 100644
index 00000000..b8ed7aa2
--- /dev/null
+++ b/v4-client-py-v2/examples/websocket_example.py
@@ -0,0 +1,24 @@
+import asyncio
+
+from dydx_v4_client.indexer.socket.websocket import CandlesResolution, IndexerSocket
+from dydx_v4_client.network import TESTNET
+from tests.conftest import TEST_ADDRESS
+
+ETH_USD = "ETH-USD"
+
+
+def handle_message(ws: IndexerSocket, message: dict):
+ if message["type"] == "connected":
+ ws.markets.subscribe()
+ ws.order_book.subscribe(ETH_USD)
+ ws.trades.subscribe(ETH_USD)
+ ws.candles.subscribe(ETH_USD, CandlesResolution.FIFTEEN_MINUTES)
+ ws.subaccounts.subscribe(TEST_ADDRESS, 0)
+ print(message)
+
+
+async def test():
+ await IndexerSocket(TESTNET.websocket_indexer, on_message=handle_message).connect()
+
+
+asyncio.run(test())
diff --git a/v4-client-py-v2/poetry.lock b/v4-client-py-v2/poetry.lock
new file mode 100644
index 00000000..d66ae602
--- /dev/null
+++ b/v4-client-py-v2/poetry.lock
@@ -0,0 +1,1014 @@
+# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand.
+
+[[package]]
+name = "anyio"
+version = "4.3.0"
+description = "High level compatibility layer for multiple asynchronous event loop implementations"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8"},
+ {file = "anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6"},
+]
+
+[package.dependencies]
+idna = ">=2.8"
+sniffio = ">=1.1"
+
+[package.extras]
+doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"]
+test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"]
+trio = ["trio (>=0.23)"]
+
+[[package]]
+name = "asn1crypto"
+version = "1.5.1"
+description = "Fast ASN.1 parser and serializer with definitions for private keys, public keys, certificates, CRL, OCSP, CMS, PKCS#3, PKCS#7, PKCS#8, PKCS#12, PKCS#5, X.509 and TSP"
+optional = false
+python-versions = "*"
+files = [
+ {file = "asn1crypto-1.5.1-py2.py3-none-any.whl", hash = "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67"},
+ {file = "asn1crypto-1.5.1.tar.gz", hash = "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c"},
+]
+
+[[package]]
+name = "bip-utils"
+version = "2.9.3"
+description = "Generation of mnemonics, seeds, private/public keys and addresses for different types of cryptocurrencies"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "bip_utils-2.9.3-py3-none-any.whl", hash = "sha256:ee26b8417a576c7f89b847da37316db01a5cece1994c1609d37fbeefb91ad45e"},
+ {file = "bip_utils-2.9.3.tar.gz", hash = "sha256:72a8c95484b57e92311b0b2a3d5195b0ce4395c19a0b157d4a289e8b1300f48a"},
+]
+
+[package.dependencies]
+cbor2 = ">=5.1.2,<6.0.0"
+coincurve = {version = ">=19.0.1", markers = "python_version >= \"3.12\""}
+crcmod = ">=1.7,<2.0"
+ecdsa = ">=0.17,<1.0"
+ed25519-blake2b = {version = ">=1.4.1,<2.0.0", markers = "python_version >= \"3.12\""}
+py-sr25519-bindings = {version = ">=0.2.0,<2.0.0", markers = "python_version >= \"3.11\""}
+pycryptodome = ">=3.15,<4.0"
+pynacl = ">=1.5,<2.0"
+
+[package.extras]
+develop = ["coverage (>=5.3)", "flake8 (>=3.8)", "isort (>=5.8)", "mypy (>=0.900)", "prospector[with-mypy,with-pyroma] (>=1.7)", "pytest (>=7.0)", "pytest-cov (>=2.10)"]
+
+[[package]]
+name = "cbor2"
+version = "5.6.3"
+description = "CBOR (de)serializer with extensive tag support"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "cbor2-5.6.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0201d5e8d9ad1557aeb50d35b907c0f170de0ae9ebb484b2894bcee3b2e13b80"},
+ {file = "cbor2-5.6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:eda6965cca276d4c2cebdbee14572dec65b991c5359fc32a793f03f052e35985"},
+ {file = "cbor2-5.6.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:14561038b8eaab3fd5e867f09bc43f7525a1405e41ade14066925ea3d42513a8"},
+ {file = "cbor2-5.6.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a3cf6b339a005031e4b8c79b9541856e3b0077ea4c33d7bb6a019885136f53a"},
+ {file = "cbor2-5.6.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4b7636d39de203ee30ac13575ed3e9a0510e993fa1671022b84b9e35e369825f"},
+ {file = "cbor2-5.6.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:23847075ce1bcda871c7698e5db0635685995ae470098a5e4c9a26c00f65f21a"},
+ {file = "cbor2-5.6.3-cp310-cp310-win_amd64.whl", hash = "sha256:ca15be7142e861fb9f918e0248620b4d4153b9ff14ef6034f7204db5db2924a1"},
+ {file = "cbor2-5.6.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b07ee755ae5b0dfad608dab37364b35895cab5d1222653da1fea32a10330c4b0"},
+ {file = "cbor2-5.6.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9fc063843c14e9e95181faf8d807a53c958d77bb9d360eb4f2344d075ecfed36"},
+ {file = "cbor2-5.6.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c66d4c227c2ed6c63ec5c2d50eb8ec0e1c41c07b452a867544e48ca41d4f0b64"},
+ {file = "cbor2-5.6.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3af60ac82a733bfdfb2b1079c850fefea2621bdb8c8f87f4c5d12802d48a8c55"},
+ {file = "cbor2-5.6.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:acb93292843aa72768f089a135bfeec4c9b745132e8dc22f1b149490fc77cb0a"},
+ {file = "cbor2-5.6.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:193d1abdffd52893710d39389daa5c03e1569421cdf53585a28033689aef7aec"},
+ {file = "cbor2-5.6.3-cp311-cp311-win_amd64.whl", hash = "sha256:d0c915db92b441f505f8a14a521c9461439ac8e5d959454845eb92f93db0bb3b"},
+ {file = "cbor2-5.6.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:9eaec8c04618124a6b597fe4471035cb7cb0d5114f43aaf2062821ad480ef57c"},
+ {file = "cbor2-5.6.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d1e5181d4f858237ab4e1a28e21bdcaf31dab2657ab60a8d4a0701a078fe5926"},
+ {file = "cbor2-5.6.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:277997127402710a3abdf4372ac75e8f8bb2e75a303cd789312e515c8ef657dd"},
+ {file = "cbor2-5.6.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:add01e4b4663199940d10f8c8e1d926e70823d1b2f3f981cc097a4764125f110"},
+ {file = "cbor2-5.6.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:adc87485ffd7a4dad481e08e6819eebfcfbafc0918fffcca47aee4cdf8c6de04"},
+ {file = "cbor2-5.6.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ea4a0412426155c3b78763449db56cf5c72c48788a37d7e60bd66c844b9c8634"},
+ {file = "cbor2-5.6.3-cp312-cp312-win_amd64.whl", hash = "sha256:18b3dee4eddde9761c60298ce21c0cd4e770237978034c5ee1d4242e255683ec"},
+ {file = "cbor2-5.6.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ff6fd1c54b97ee322c0b7180092305ca3b012ff78fddadad97b33490f5f8881f"},
+ {file = "cbor2-5.6.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ac6f10b9d25f2d61c036f86238bf23e3ea0253f98faa8ab00f67228bf3c0ce2a"},
+ {file = "cbor2-5.6.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7be74f2cbda547fdd57c83ee5b3470804f02c660db28efcf9d4016f001b66f40"},
+ {file = "cbor2-5.6.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:64ea120206f82492a4385bbc5e2639f9b67c8bc7bdc57bffcbe9a8fee8cd6342"},
+ {file = "cbor2-5.6.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:c3d2902e1aed155d56cdcae99cd4a9dae843e3fff6978148d2d5d5f9a0b986cd"},
+ {file = "cbor2-5.6.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2d4f95a567e26d8d9d62db234cd089525c52f19e7fdd59152629d9f03bd94b4f"},
+ {file = "cbor2-5.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:33efbe7103bac090430d291fca2fe1c444b0ec55c4716e8051b72a81377e8b79"},
+ {file = "cbor2-5.6.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:81e619a2a59ae966cedb5fd3ea8a9487a3d4430824bbeacdcf5f74ad6112cc57"},
+ {file = "cbor2-5.6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2b7755b93d32638f4d79a0fa0744b423787f6faa3c96ccccac68b6dbf1848368"},
+ {file = "cbor2-5.6.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f0e95011ae8460265ef348fe380664fa22c51015fd52344ebd781579fa9552a"},
+ {file = "cbor2-5.6.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7693e53c3ba0b2ad4e46b610f8d69159ffdbcb6ebe75ea1c1f5f40c3283639ca"},
+ {file = "cbor2-5.6.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e3ec251db32516d383fc587874b15f4b5fb4e9049d9436b8696f5767b11c149b"},
+ {file = "cbor2-5.6.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:6081c1ab9791d5973a40b95ecb8b04b0fbf9fc04be170d89a3ad77d5964f52d5"},
+ {file = "cbor2-5.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:2aba8b75e36c9f84a42a7026271da8fd759035a871c1b799028439059527276b"},
+ {file = "cbor2-5.6.3-py3-none-any.whl", hash = "sha256:8a4b7404af6da719092a4ee5953d1930d095b93b684bf99e1ab74512be1910a4"},
+ {file = "cbor2-5.6.3.tar.gz", hash = "sha256:e6f0ae2751c2d333a960e0807c0611494eb1245631a167965acbc100509455d3"},
+]
+
+[package.extras]
+benchmarks = ["pytest-benchmark (==4.0.0)"]
+doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.3.0)", "typing-extensions"]
+test = ["coverage (>=7)", "hypothesis", "pytest"]
+
+[[package]]
+name = "certifi"
+version = "2024.2.2"
+description = "Python package for providing Mozilla's CA Bundle."
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"},
+ {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"},
+]
+
+[[package]]
+name = "cffi"
+version = "1.16.0"
+description = "Foreign Function Interface for Python calling C code."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"},
+ {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"},
+ {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"},
+ {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"},
+ {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"},
+ {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"},
+ {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"},
+ {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"},
+ {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"},
+ {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"},
+ {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"},
+ {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"},
+ {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"},
+ {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"},
+ {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"},
+ {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"},
+ {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"},
+ {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"},
+ {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"},
+ {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"},
+ {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"},
+ {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"},
+ {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"},
+ {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"},
+ {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"},
+ {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"},
+ {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"},
+ {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"},
+ {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"},
+ {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"},
+ {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"},
+ {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"},
+]
+
+[package.dependencies]
+pycparser = "*"
+
+[[package]]
+name = "cfgv"
+version = "3.4.0"
+description = "Validate configuration and produce human readable error messages."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"},
+ {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"},
+]
+
+[[package]]
+name = "coincurve"
+version = "19.0.1"
+description = "Cross-platform Python CFFI bindings for libsecp256k1"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "coincurve-19.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2fe158bde4f45f08d68fc373d78fecd119d3f4179b83d57a7c8541f070e87c4f"},
+ {file = "coincurve-19.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:976be3a67d1d3940a53aa05513d8a7124432bfb7cd8d2eb0ea5ba3c2e06fd940"},
+ {file = "coincurve-19.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ca607dfae705b90d5060b57cdf73e79f7c949e272cf540398bb4c10d1af4956"},
+ {file = "coincurve-19.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1356308449462275546ec5b8b2bf2ef8ba46a031dd3f7de29ea8840046b50412"},
+ {file = "coincurve-19.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:88adad6a06618a9ce647758a2a303b399982b3360e68efabd9a772497894ddb3"},
+ {file = "coincurve-19.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:1eef347d2446a78dd249dfbf298d4b053d3ca00e6514e6589107dd5e326d9ac8"},
+ {file = "coincurve-19.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:9472600e6bc32ec27efbec906e15da822ae345091d2b0f3c19b42a90041ae077"},
+ {file = "coincurve-19.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:ca1b8f986ba24d4c8ea6ae396fc931291dbc44d9e2f5ea830d463e5c45c84357"},
+ {file = "coincurve-19.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a5ea7d27168c4f626e03057abfcc5116545d7729ec679d1d542739c441a10d42"},
+ {file = "coincurve-19.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b6b07ae76fed2c83970fc694222ada50feab9e404db6b99d3a20e191874acb80"},
+ {file = "coincurve-19.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ab8332db832913e04a112d0c6ec33f99f31a723f67243da9a5501d284a4e003b"},
+ {file = "coincurve-19.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8af61945ba74ba6b498fcd05a21368d01b6c4bf7cda896791876aee5aa830acc"},
+ {file = "coincurve-19.0.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:662cc62d743466efc0cf2cc11cbfd81f1ef4d16cba9d554ff81dacd7989e6af5"},
+ {file = "coincurve-19.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4194222745f1f5ca224e7bf52a9b2710650558e0de1e05f16e23f2b5ffb114a0"},
+ {file = "coincurve-19.0.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:64ec50fbea52fbb6b7972ae62caf2ae18888e3a99e1fe095073d5d09ff5f87fa"},
+ {file = "coincurve-19.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:75d971c3ba7171b0711652684f8cc471594fecb94915409080fbc8af6d1332e7"},
+ {file = "coincurve-19.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7d2449cf35c6d1a4edbd73b58e7db1c29934d0b1750569bb9725dfb591c08510"},
+ {file = "coincurve-19.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:512385b546366889be713d0ea613a805467e99630f57f9379976650129cad752"},
+ {file = "coincurve-19.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8769eec71e6881f2eb0efd37604eca40384bd92f055a413fe5156787fc2733b"},
+ {file = "coincurve-19.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4364ef547d82d8075107e6a54e82a81f08a71acaa9722478f3ed3c8a8a042e00"},
+ {file = "coincurve-19.0.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3de1dca6a657060575f9290b52dcf825f1e6c38a07dfc6a3535152e4846fbf2b"},
+ {file = "coincurve-19.0.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3e5dfad921fc0de04f7117edae5d81639ec76bae3d6d1fe571eb394be85656be"},
+ {file = "coincurve-19.0.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bd0bb1d1f31e42fb4eea96344febf7d2108dffeede6f6b6c9bc0482d0f911c7d"},
+ {file = "coincurve-19.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:6f25c9360c6d770559073333553a695c42b051576a44b8276c07cfc74eacf8df"},
+ {file = "coincurve-19.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2784cb217777bbd247e9090385689da9baf5613d1a5c9c9bd4b22e43d04c1956"},
+ {file = "coincurve-19.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7bae24f65cf5121ec708bb29004473a4c157eec8c2569067b02015bf729aae8c"},
+ {file = "coincurve-19.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d56bc6458bd72ef307e805d0ad4c686fff94b4ae8340b7998c954b86d2af8ee6"},
+ {file = "coincurve-19.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:992897fe0b37634aaf76b2f94933d7011f88725b436249e64f1f809110d39d1c"},
+ {file = "coincurve-19.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a960657b570a33a0a02089c9e122e0a0deec09e95f79d71fb5256760ceb8eb0c"},
+ {file = "coincurve-19.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:744f394e6ce9db96eb2dd374448e56c497d99da52cba8b1ccecf410dc004552f"},
+ {file = "coincurve-19.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ef0dbb63c0c55a86ef320d9a4a0ba08782ed07e741f2c1900309b2d9a35116be"},
+ {file = "coincurve-19.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2fd44bbb937f28c3b7eec72ae4d090ab8fd6894c43b62b38b48d3a3623ca9066"},
+ {file = "coincurve-19.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:19a9c10847590baca83191e62a6d7cff3d9853edc42235c44dff0548a7d0a14e"},
+ {file = "coincurve-19.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:825771d13023335d372f8e7f9f212f44527a00b0845e2f35da2e53ac3d3bc320"},
+ {file = "coincurve-19.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b65ecb9ca384db928e7ca24b82632d3ed50d0be84d0f4eafb4b6e8b04940eaf3"},
+ {file = "coincurve-19.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3a50b9b1f242d14363cd2504cdb72eb8a24012082c691a7ed4cbc81d5b9c01a"},
+ {file = "coincurve-19.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a31556af9199b5a84a787a17578fc60eff2edf87ba1b68b5a2d7bf5795b556c"},
+ {file = "coincurve-19.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a66d4fc6f782ba8fa444c8027548a0314af1ec5176d8fca5122f954bbd9ef4b6"},
+ {file = "coincurve-19.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:37045959e9b15902704e342e2a99e84df85021be7368c7d9c59f7ff7fb6aaf95"},
+ {file = "coincurve-19.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4cc0c380a0a0f6ea1589ab9216059c3ea1c5b153cc283b2b603102de66f75a65"},
+ {file = "coincurve-19.0.1-py3-none-win32.whl", hash = "sha256:f6f4f1eebcdc8037242b90ab1255fc398456dd8e383718485f7716228fa5e8f9"},
+ {file = "coincurve-19.0.1-py3-none-win_amd64.whl", hash = "sha256:346b44755ae505d2f64f9880a294d5fcad4d3fd8a315296dd39c3509f1dc555d"},
+ {file = "coincurve-19.0.1.tar.gz", hash = "sha256:c2191049e66792a1a19ca2e64b4d057be3ef1553105e5b9713a92b740c7809ee"},
+]
+
+[package.dependencies]
+asn1crypto = "*"
+cffi = ">=1.3.0"
+
+[[package]]
+name = "colorama"
+version = "0.4.6"
+description = "Cross-platform colored terminal text."
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7"
+files = [
+ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"},
+ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"},
+]
+
+[[package]]
+name = "crcmod"
+version = "1.7"
+description = "CRC Generator"
+optional = false
+python-versions = "*"
+files = [
+ {file = "crcmod-1.7.tar.gz", hash = "sha256:dc7051a0db5f2bd48665a990d3ec1cc305a466a77358ca4492826f41f283601e"},
+]
+
+[[package]]
+name = "distlib"
+version = "0.3.8"
+description = "Distribution utilities"
+optional = false
+python-versions = "*"
+files = [
+ {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"},
+ {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"},
+]
+
+[[package]]
+name = "ecdsa"
+version = "0.19.0"
+description = "ECDSA cryptographic signature library (pure python)"
+optional = false
+python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.6"
+files = [
+ {file = "ecdsa-0.19.0-py2.py3-none-any.whl", hash = "sha256:2cea9b88407fdac7bbeca0833b189e4c9c53f2ef1e1eaa29f6224dbc809b707a"},
+ {file = "ecdsa-0.19.0.tar.gz", hash = "sha256:60eaad1199659900dd0af521ed462b793bbdf867432b3948e87416ae4caf6bf8"},
+]
+
+[package.dependencies]
+six = ">=1.9.0"
+
+[package.extras]
+gmpy = ["gmpy"]
+gmpy2 = ["gmpy2"]
+
+[[package]]
+name = "ed25519-blake2b"
+version = "1.4.1"
+description = "Ed25519 public-key signatures (BLAKE2b fork)"
+optional = false
+python-versions = "*"
+files = [
+ {file = "ed25519-blake2b-1.4.1.tar.gz", hash = "sha256:731e9f93cd1ac1a64649575f3519a99ffe0bb1e4cf7bf5f5f0be513a39df7363"},
+]
+
+[[package]]
+name = "filelock"
+version = "3.14.0"
+description = "A platform independent file lock."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "filelock-3.14.0-py3-none-any.whl", hash = "sha256:43339835842f110ca7ae60f1e1c160714c5a6afd15a2873419ab185334975c0f"},
+ {file = "filelock-3.14.0.tar.gz", hash = "sha256:6ea72da3be9b8c82afd3edcf99f2fffbb5076335a5ae4d03248bb5b6c3eae78a"},
+]
+
+[package.extras]
+docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"]
+testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"]
+typing = ["typing-extensions (>=4.8)"]
+
+[[package]]
+name = "grpcio"
+version = "1.63.0"
+description = "HTTP/2-based RPC framework"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "grpcio-1.63.0-cp310-cp310-linux_armv7l.whl", hash = "sha256:2e93aca840c29d4ab5db93f94ed0a0ca899e241f2e8aec6334ab3575dc46125c"},
+ {file = "grpcio-1.63.0-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:91b73d3f1340fefa1e1716c8c1ec9930c676d6b10a3513ab6c26004cb02d8b3f"},
+ {file = "grpcio-1.63.0-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:b3afbd9d6827fa6f475a4f91db55e441113f6d3eb9b7ebb8fb806e5bb6d6bd0d"},
+ {file = "grpcio-1.63.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8f3f6883ce54a7a5f47db43289a0a4c776487912de1a0e2cc83fdaec9685cc9f"},
+ {file = "grpcio-1.63.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf8dae9cc0412cb86c8de5a8f3be395c5119a370f3ce2e69c8b7d46bb9872c8d"},
+ {file = "grpcio-1.63.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:08e1559fd3b3b4468486b26b0af64a3904a8dbc78d8d936af9c1cf9636eb3e8b"},
+ {file = "grpcio-1.63.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5c039ef01516039fa39da8a8a43a95b64e288f79f42a17e6c2904a02a319b357"},
+ {file = "grpcio-1.63.0-cp310-cp310-win32.whl", hash = "sha256:ad2ac8903b2eae071055a927ef74121ed52d69468e91d9bcbd028bd0e554be6d"},
+ {file = "grpcio-1.63.0-cp310-cp310-win_amd64.whl", hash = "sha256:b2e44f59316716532a993ca2966636df6fbe7be4ab6f099de6815570ebe4383a"},
+ {file = "grpcio-1.63.0-cp311-cp311-linux_armv7l.whl", hash = "sha256:f28f8b2db7b86c77916829d64ab21ff49a9d8289ea1564a2b2a3a8ed9ffcccd3"},
+ {file = "grpcio-1.63.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:65bf975639a1f93bee63ca60d2e4951f1b543f498d581869922910a476ead2f5"},
+ {file = "grpcio-1.63.0-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:b5194775fec7dc3dbd6a935102bb156cd2c35efe1685b0a46c67b927c74f0cfb"},
+ {file = "grpcio-1.63.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4cbb2100ee46d024c45920d16e888ee5d3cf47c66e316210bc236d5bebc42b3"},
+ {file = "grpcio-1.63.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ff737cf29b5b801619f10e59b581869e32f400159e8b12d7a97e7e3bdeee6a2"},
+ {file = "grpcio-1.63.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cd1e68776262dd44dedd7381b1a0ad09d9930ffb405f737d64f505eb7f77d6c7"},
+ {file = "grpcio-1.63.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:93f45f27f516548e23e4ec3fbab21b060416007dbe768a111fc4611464cc773f"},
+ {file = "grpcio-1.63.0-cp311-cp311-win32.whl", hash = "sha256:878b1d88d0137df60e6b09b74cdb73db123f9579232c8456f53e9abc4f62eb3c"},
+ {file = "grpcio-1.63.0-cp311-cp311-win_amd64.whl", hash = "sha256:756fed02dacd24e8f488f295a913f250b56b98fb793f41d5b2de6c44fb762434"},
+ {file = "grpcio-1.63.0-cp312-cp312-linux_armv7l.whl", hash = "sha256:93a46794cc96c3a674cdfb59ef9ce84d46185fe9421baf2268ccb556f8f81f57"},
+ {file = "grpcio-1.63.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a7b19dfc74d0be7032ca1eda0ed545e582ee46cd65c162f9e9fc6b26ef827dc6"},
+ {file = "grpcio-1.63.0-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:8064d986d3a64ba21e498b9a376cbc5d6ab2e8ab0e288d39f266f0fca169b90d"},
+ {file = "grpcio-1.63.0-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:219bb1848cd2c90348c79ed0a6b0ea51866bc7e72fa6e205e459fedab5770172"},
+ {file = "grpcio-1.63.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a2d60cd1d58817bc5985fae6168d8b5655c4981d448d0f5b6194bbcc038090d2"},
+ {file = "grpcio-1.63.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:9e350cb096e5c67832e9b6e018cf8a0d2a53b2a958f6251615173165269a91b0"},
+ {file = "grpcio-1.63.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:56cdf96ff82e3cc90dbe8bac260352993f23e8e256e063c327b6cf9c88daf7a9"},
+ {file = "grpcio-1.63.0-cp312-cp312-win32.whl", hash = "sha256:3a6d1f9ea965e750db7b4ee6f9fdef5fdf135abe8a249e75d84b0a3e0c668a1b"},
+ {file = "grpcio-1.63.0-cp312-cp312-win_amd64.whl", hash = "sha256:d2497769895bb03efe3187fb1888fc20e98a5f18b3d14b606167dacda5789434"},
+ {file = "grpcio-1.63.0-cp38-cp38-linux_armv7l.whl", hash = "sha256:fdf348ae69c6ff484402cfdb14e18c1b0054ac2420079d575c53a60b9b2853ae"},
+ {file = "grpcio-1.63.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a3abfe0b0f6798dedd2e9e92e881d9acd0fdb62ae27dcbbfa7654a57e24060c0"},
+ {file = "grpcio-1.63.0-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:6ef0ad92873672a2a3767cb827b64741c363ebaa27e7f21659e4e31f4d750280"},
+ {file = "grpcio-1.63.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b416252ac5588d9dfb8a30a191451adbf534e9ce5f56bb02cd193f12d8845b7f"},
+ {file = "grpcio-1.63.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3b77eaefc74d7eb861d3ffbdf91b50a1bb1639514ebe764c47773b833fa2d91"},
+ {file = "grpcio-1.63.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b005292369d9c1f80bf70c1db1c17c6c342da7576f1c689e8eee4fb0c256af85"},
+ {file = "grpcio-1.63.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cdcda1156dcc41e042d1e899ba1f5c2e9f3cd7625b3d6ebfa619806a4c1aadda"},
+ {file = "grpcio-1.63.0-cp38-cp38-win32.whl", hash = "sha256:01799e8649f9e94ba7db1aeb3452188048b0019dc37696b0f5ce212c87c560c3"},
+ {file = "grpcio-1.63.0-cp38-cp38-win_amd64.whl", hash = "sha256:6a1a3642d76f887aa4009d92f71eb37809abceb3b7b5a1eec9c554a246f20e3a"},
+ {file = "grpcio-1.63.0-cp39-cp39-linux_armv7l.whl", hash = "sha256:75f701ff645858a2b16bc8c9fc68af215a8bb2d5a9b647448129de6e85d52bce"},
+ {file = "grpcio-1.63.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cacdef0348a08e475a721967f48206a2254a1b26ee7637638d9e081761a5ba86"},
+ {file = "grpcio-1.63.0-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:0697563d1d84d6985e40ec5ec596ff41b52abb3fd91ec240e8cb44a63b895094"},
+ {file = "grpcio-1.63.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6426e1fb92d006e47476d42b8f240c1d916a6d4423c5258ccc5b105e43438f61"},
+ {file = "grpcio-1.63.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e48cee31bc5f5a31fb2f3b573764bd563aaa5472342860edcc7039525b53e46a"},
+ {file = "grpcio-1.63.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:50344663068041b34a992c19c600236e7abb42d6ec32567916b87b4c8b8833b3"},
+ {file = "grpcio-1.63.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:259e11932230d70ef24a21b9fb5bb947eb4703f57865a404054400ee92f42f5d"},
+ {file = "grpcio-1.63.0-cp39-cp39-win32.whl", hash = "sha256:a44624aad77bf8ca198c55af811fd28f2b3eaf0a50ec5b57b06c034416ef2d0a"},
+ {file = "grpcio-1.63.0-cp39-cp39-win_amd64.whl", hash = "sha256:166e5c460e5d7d4656ff9e63b13e1f6029b122104c1633d5f37eaea348d7356d"},
+ {file = "grpcio-1.63.0.tar.gz", hash = "sha256:f3023e14805c61bc439fb40ca545ac3d5740ce66120a678a3c6c2c55b70343d1"},
+]
+
+[package.extras]
+protobuf = ["grpcio-tools (>=1.63.0)"]
+
+[[package]]
+name = "grpcio-tools"
+version = "1.62.2"
+description = "Protobuf code generator for gRPC"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "grpcio-tools-1.62.2.tar.gz", hash = "sha256:5fd5e1582b678e6b941ee5f5809340be5e0724691df5299aae8226640f94e18f"},
+ {file = "grpcio_tools-1.62.2-cp310-cp310-linux_armv7l.whl", hash = "sha256:1679b4903aed2dc5bd8cb22a452225b05dc8470a076f14fd703581efc0740cdb"},
+ {file = "grpcio_tools-1.62.2-cp310-cp310-macosx_12_0_universal2.whl", hash = "sha256:9d41e0e47dd075c075bb8f103422968a65dd0d8dc8613288f573ae91eb1053ba"},
+ {file = "grpcio_tools-1.62.2-cp310-cp310-manylinux_2_17_aarch64.whl", hash = "sha256:987e774f74296842bbffd55ea8826370f70c499e5b5f71a8cf3103838b6ee9c3"},
+ {file = "grpcio_tools-1.62.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40cd4eeea4b25bcb6903b82930d579027d034ba944393c4751cdefd9c49e6989"},
+ {file = "grpcio_tools-1.62.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6746bc823958499a3cf8963cc1de00072962fb5e629f26d658882d3f4c35095"},
+ {file = "grpcio_tools-1.62.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2ed775e844566ce9ce089be9a81a8b928623b8ee5820f5e4d58c1a9d33dfc5ae"},
+ {file = "grpcio_tools-1.62.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:bdc5dd3f57b5368d5d661d5d3703bcaa38bceca59d25955dff66244dbc987271"},
+ {file = "grpcio_tools-1.62.2-cp310-cp310-win32.whl", hash = "sha256:3a8d6f07e64c0c7756f4e0c4781d9d5a2b9cc9cbd28f7032a6fb8d4f847d0445"},
+ {file = "grpcio_tools-1.62.2-cp310-cp310-win_amd64.whl", hash = "sha256:e33b59fb3efdddeb97ded988a871710033e8638534c826567738d3edce528752"},
+ {file = "grpcio_tools-1.62.2-cp311-cp311-linux_armv7l.whl", hash = "sha256:472505d030135d73afe4143b0873efe0dcb385bd6d847553b4f3afe07679af00"},
+ {file = "grpcio_tools-1.62.2-cp311-cp311-macosx_10_10_universal2.whl", hash = "sha256:ec674b4440ef4311ac1245a709e87b36aca493ddc6850eebe0b278d1f2b6e7d1"},
+ {file = "grpcio_tools-1.62.2-cp311-cp311-manylinux_2_17_aarch64.whl", hash = "sha256:184b4174d4bd82089d706e8223e46c42390a6ebac191073b9772abc77308f9fa"},
+ {file = "grpcio_tools-1.62.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c195d74fe98541178ece7a50dad2197d43991e0f77372b9a88da438be2486f12"},
+ {file = "grpcio_tools-1.62.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a34d97c62e61bfe9e6cff0410fe144ac8cca2fc979ad0be46b7edf026339d161"},
+ {file = "grpcio_tools-1.62.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cbb8453ae83a1db2452b7fe0f4b78e4a8dd32be0f2b2b73591ae620d4d784d3d"},
+ {file = "grpcio_tools-1.62.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4f989e5cebead3ae92c6abf6bf7b19949e1563a776aea896ac5933f143f0c45d"},
+ {file = "grpcio_tools-1.62.2-cp311-cp311-win32.whl", hash = "sha256:c48fabe40b9170f4e3d7dd2c252e4f1ff395dc24e49ac15fc724b1b6f11724da"},
+ {file = "grpcio_tools-1.62.2-cp311-cp311-win_amd64.whl", hash = "sha256:8c616d0ad872e3780693fce6a3ac8ef00fc0963e6d7815ce9dcfae68ba0fc287"},
+ {file = "grpcio_tools-1.62.2-cp312-cp312-linux_armv7l.whl", hash = "sha256:10cc3321704ecd17c93cf68c99c35467a8a97ffaaed53207e9b2da6ae0308ee1"},
+ {file = "grpcio_tools-1.62.2-cp312-cp312-macosx_10_10_universal2.whl", hash = "sha256:9be84ff6d47fd61462be7523b49d7ba01adf67ce4e1447eae37721ab32464dd8"},
+ {file = "grpcio_tools-1.62.2-cp312-cp312-manylinux_2_17_aarch64.whl", hash = "sha256:d82f681c9a9d933a9d8068e8e382977768e7779ddb8870fa0cf918d8250d1532"},
+ {file = "grpcio_tools-1.62.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:04c607029ae3660fb1624ed273811ffe09d57d84287d37e63b5b802a35897329"},
+ {file = "grpcio_tools-1.62.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:72b61332f1b439c14cbd3815174a8f1d35067a02047c32decd406b3a09bb9890"},
+ {file = "grpcio_tools-1.62.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8214820990d01b52845f9fbcb92d2b7384a0c321b303e3ac614c219dc7d1d3af"},
+ {file = "grpcio_tools-1.62.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:462e0ab8dd7c7b70bfd6e3195eebc177549ede5cf3189814850c76f9a340d7ce"},
+ {file = "grpcio_tools-1.62.2-cp312-cp312-win32.whl", hash = "sha256:fa107460c842e4c1a6266150881694fefd4f33baa544ea9489601810c2210ef8"},
+ {file = "grpcio_tools-1.62.2-cp312-cp312-win_amd64.whl", hash = "sha256:759c60f24c33a181bbbc1232a6752f9b49fbb1583312a4917e2b389fea0fb0f2"},
+ {file = "grpcio_tools-1.62.2-cp37-cp37m-linux_armv7l.whl", hash = "sha256:45db5da2bcfa88f2b86b57ef35daaae85c60bd6754a051d35d9449c959925b57"},
+ {file = "grpcio_tools-1.62.2-cp37-cp37m-macosx_10_10_universal2.whl", hash = "sha256:ab84bae88597133f6ea7a2bdc57b2fda98a266fe8d8d4763652cbefd20e73ad7"},
+ {file = "grpcio_tools-1.62.2-cp37-cp37m-manylinux_2_17_aarch64.whl", hash = "sha256:7a49bccae1c7d154b78e991885c3111c9ad8c8fa98e91233de425718f47c6139"},
+ {file = "grpcio_tools-1.62.2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7e439476b29d6dac363b321781a113794397afceeb97dad85349db5f1cb5e9a"},
+ {file = "grpcio_tools-1.62.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7ea369c4d1567d1acdf69c8ea74144f4ccad9e545df7f9a4fc64c94fa7684ba3"},
+ {file = "grpcio_tools-1.62.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4f955702dc4b530696375251319d05223b729ed24e8673c2129f7a75d2caefbb"},
+ {file = "grpcio_tools-1.62.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3708a747aa4b6b505727282ca887041174e146ae030ebcadaf4c1d346858df62"},
+ {file = "grpcio_tools-1.62.2-cp37-cp37m-win_amd64.whl", hash = "sha256:2ce149ea55eadb486a7fb75a20f63ef3ac065ee6a0240ed25f3549ce7954c653"},
+ {file = "grpcio_tools-1.62.2-cp38-cp38-linux_armv7l.whl", hash = "sha256:58cbb24b3fa6ae35aa9c210fcea3a51aa5fef0cd25618eb4fd94f746d5a9b703"},
+ {file = "grpcio_tools-1.62.2-cp38-cp38-macosx_10_10_universal2.whl", hash = "sha256:6413581e14a80e0b4532577766cf0586de4dd33766a31b3eb5374a746771c07d"},
+ {file = "grpcio_tools-1.62.2-cp38-cp38-manylinux_2_17_aarch64.whl", hash = "sha256:47117c8a7e861382470d0e22d336e5a91fdc5f851d1db44fa784b9acea190d87"},
+ {file = "grpcio_tools-1.62.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9f1ba79a253df9e553d20319c615fa2b429684580fa042dba618d7f6649ac7e4"},
+ {file = "grpcio_tools-1.62.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:04a394cf5e51ba9be412eb9f6c482b6270bd81016e033e8eb7d21b8cc28fe8b5"},
+ {file = "grpcio_tools-1.62.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:3c53b221378b035ae2f1881cbc3aca42a6075a8e90e1a342c2f205eb1d1aa6a1"},
+ {file = "grpcio_tools-1.62.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c384c838b34d1b67068e51b5bbe49caa6aa3633acd158f1ab16b5da8d226bc53"},
+ {file = "grpcio_tools-1.62.2-cp38-cp38-win32.whl", hash = "sha256:19ea69e41c3565932aa28a202d1875ec56786aea46a2eab54a3b28e8a27f9517"},
+ {file = "grpcio_tools-1.62.2-cp38-cp38-win_amd64.whl", hash = "sha256:1d768a5c07279a4c461ebf52d0cec1c6ca85c6291c71ec2703fe3c3e7e28e8c4"},
+ {file = "grpcio_tools-1.62.2-cp39-cp39-linux_armv7l.whl", hash = "sha256:5b07b5874187e170edfbd7aa2ca3a54ebf3b2952487653e8c0b0d83601c33035"},
+ {file = "grpcio_tools-1.62.2-cp39-cp39-macosx_10_10_universal2.whl", hash = "sha256:d58389fe8be206ddfb4fa703db1e24c956856fcb9a81da62b13577b3a8f7fda7"},
+ {file = "grpcio_tools-1.62.2-cp39-cp39-manylinux_2_17_aarch64.whl", hash = "sha256:7d8b4e00c3d7237b92260fc18a561cd81f1da82e8be100db1b7d816250defc66"},
+ {file = "grpcio_tools-1.62.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fe08d2038f2b7c53259b5c49e0ad08c8e0ce2b548d8185993e7ef67e8592cca"},
+ {file = "grpcio_tools-1.62.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19216e1fb26dbe23d12a810517e1b3fbb8d4f98b1a3fbebeec9d93a79f092de4"},
+ {file = "grpcio_tools-1.62.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:b8574469ecc4ff41d6bb95f44e0297cdb0d95bade388552a9a444db9cd7485cd"},
+ {file = "grpcio_tools-1.62.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4f6f32d39283ea834a493fccf0ebe9cfddee7577bdcc27736ad4be1732a36399"},
+ {file = "grpcio_tools-1.62.2-cp39-cp39-win32.whl", hash = "sha256:76eb459bdf3fb666e01883270beee18f3f11ed44488486b61cd210b4e0e17cc1"},
+ {file = "grpcio_tools-1.62.2-cp39-cp39-win_amd64.whl", hash = "sha256:217c2ee6a7ce519a55958b8622e21804f6fdb774db08c322f4c9536c35fdce7c"},
+]
+
+[package.dependencies]
+grpcio = ">=1.62.2"
+protobuf = ">=4.21.6,<5.0dev"
+setuptools = "*"
+
+[[package]]
+name = "h11"
+version = "0.14.0"
+description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"},
+ {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"},
+]
+
+[[package]]
+name = "httpcore"
+version = "1.0.5"
+description = "A minimal low-level HTTP client."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "httpcore-1.0.5-py3-none-any.whl", hash = "sha256:421f18bac248b25d310f3cacd198d55b8e6125c107797b609ff9b7a6ba7991b5"},
+ {file = "httpcore-1.0.5.tar.gz", hash = "sha256:34a38e2f9291467ee3b44e89dd52615370e152954ba21721378a87b2960f7a61"},
+]
+
+[package.dependencies]
+certifi = "*"
+h11 = ">=0.13,<0.15"
+
+[package.extras]
+asyncio = ["anyio (>=4.0,<5.0)"]
+http2 = ["h2 (>=3,<5)"]
+socks = ["socksio (==1.*)"]
+trio = ["trio (>=0.22.0,<0.26.0)"]
+
+[[package]]
+name = "httpx"
+version = "0.27.0"
+description = "The next generation HTTP client."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"},
+ {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"},
+]
+
+[package.dependencies]
+anyio = "*"
+certifi = "*"
+httpcore = "==1.*"
+idna = "*"
+sniffio = "*"
+
+[package.extras]
+brotli = ["brotli", "brotlicffi"]
+cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"]
+http2 = ["h2 (>=3,<5)"]
+socks = ["socksio (==1.*)"]
+
+[[package]]
+name = "identify"
+version = "2.5.36"
+description = "File identification library for Python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "identify-2.5.36-py2.py3-none-any.whl", hash = "sha256:37d93f380f4de590500d9dba7db359d0d3da95ffe7f9de1753faa159e71e7dfa"},
+ {file = "identify-2.5.36.tar.gz", hash = "sha256:e5e00f54165f9047fbebeb4a560f9acfb8af4c88232be60a488e9b68d122745d"},
+]
+
+[package.extras]
+license = ["ukkonen"]
+
+[[package]]
+name = "idna"
+version = "3.7"
+description = "Internationalized Domain Names in Applications (IDNA)"
+optional = false
+python-versions = ">=3.5"
+files = [
+ {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"},
+ {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"},
+]
+
+[[package]]
+name = "iniconfig"
+version = "2.0.0"
+description = "brain-dead simple config-ini parsing"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"},
+ {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
+]
+
+[[package]]
+name = "nest-asyncio"
+version = "1.6.0"
+description = "Patch asyncio to allow nested event loops"
+optional = false
+python-versions = ">=3.5"
+files = [
+ {file = "nest_asyncio-1.6.0-py3-none-any.whl", hash = "sha256:87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c"},
+ {file = "nest_asyncio-1.6.0.tar.gz", hash = "sha256:6f172d5449aca15afd6c646851f4e31e02c598d553a667e38cafa997cfec55fe"},
+]
+
+[[package]]
+name = "nodeenv"
+version = "1.8.0"
+description = "Node.js virtual environment builder"
+optional = false
+python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*"
+files = [
+ {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"},
+ {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"},
+]
+
+[package.dependencies]
+setuptools = "*"
+
+[[package]]
+name = "packaging"
+version = "24.0"
+description = "Core utilities for Python packages"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"},
+ {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"},
+]
+
+[[package]]
+name = "platformdirs"
+version = "4.2.1"
+description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "platformdirs-4.2.1-py3-none-any.whl", hash = "sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1"},
+ {file = "platformdirs-4.2.1.tar.gz", hash = "sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf"},
+]
+
+[package.extras]
+docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"]
+test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"]
+type = ["mypy (>=1.8)"]
+
+[[package]]
+name = "pluggy"
+version = "1.5.0"
+description = "plugin and hook calling mechanisms for python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669"},
+ {file = "pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1"},
+]
+
+[package.extras]
+dev = ["pre-commit", "tox"]
+testing = ["pytest", "pytest-benchmark"]
+
+[[package]]
+name = "pre-commit"
+version = "3.7.0"
+description = "A framework for managing and maintaining multi-language pre-commit hooks."
+optional = false
+python-versions = ">=3.9"
+files = [
+ {file = "pre_commit-3.7.0-py2.py3-none-any.whl", hash = "sha256:5eae9e10c2b5ac51577c3452ec0a490455c45a0533f7960f993a0d01e59decab"},
+ {file = "pre_commit-3.7.0.tar.gz", hash = "sha256:e209d61b8acdcf742404408531f0c37d49d2c734fd7cff2d6076083d191cb060"},
+]
+
+[package.dependencies]
+cfgv = ">=2.0.0"
+identify = ">=1.0.0"
+nodeenv = ">=0.11.1"
+pyyaml = ">=5.1"
+virtualenv = ">=20.10.0"
+
+[[package]]
+name = "protobuf"
+version = "4.25.3"
+description = ""
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "protobuf-4.25.3-cp310-abi3-win32.whl", hash = "sha256:d4198877797a83cbfe9bffa3803602bbe1625dc30d8a097365dbc762e5790faa"},
+ {file = "protobuf-4.25.3-cp310-abi3-win_amd64.whl", hash = "sha256:209ba4cc916bab46f64e56b85b090607a676f66b473e6b762e6f1d9d591eb2e8"},
+ {file = "protobuf-4.25.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:f1279ab38ecbfae7e456a108c5c0681e4956d5b1090027c1de0f934dfdb4b35c"},
+ {file = "protobuf-4.25.3-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:e7cb0ae90dd83727f0c0718634ed56837bfeeee29a5f82a7514c03ee1364c019"},
+ {file = "protobuf-4.25.3-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:7c8daa26095f82482307bc717364e7c13f4f1c99659be82890dcfc215194554d"},
+ {file = "protobuf-4.25.3-cp38-cp38-win32.whl", hash = "sha256:f4f118245c4a087776e0a8408be33cf09f6c547442c00395fbfb116fac2f8ac2"},
+ {file = "protobuf-4.25.3-cp38-cp38-win_amd64.whl", hash = "sha256:c053062984e61144385022e53678fbded7aea14ebb3e0305ae3592fb219ccfa4"},
+ {file = "protobuf-4.25.3-cp39-cp39-win32.whl", hash = "sha256:19b270aeaa0099f16d3ca02628546b8baefe2955bbe23224aaf856134eccf1e4"},
+ {file = "protobuf-4.25.3-cp39-cp39-win_amd64.whl", hash = "sha256:e3c97a1555fd6388f857770ff8b9703083de6bf1f9274a002a332d65fbb56c8c"},
+ {file = "protobuf-4.25.3-py3-none-any.whl", hash = "sha256:f0700d54bcf45424477e46a9f0944155b46fb0639d69728739c0e47bab83f2b9"},
+ {file = "protobuf-4.25.3.tar.gz", hash = "sha256:25b5d0b42fd000320bd7830b349e3b696435f3b329810427a6bcce6a5492cc5c"},
+]
+
+[[package]]
+name = "py-sr25519-bindings"
+version = "0.2.0"
+description = "Python bindings for sr25519 library"
+optional = false
+python-versions = "*"
+files = [
+ {file = "py_sr25519_bindings-0.2.0-cp310-cp310-macosx_10_7_x86_64.whl", hash = "sha256:86cc1a571852a4f2ade827ebf211e066b23ab805d3e864cbe213a3d8cd53f7d5"},
+ {file = "py_sr25519_bindings-0.2.0-cp310-cp310-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:453c9088e39dd04b07bf3ada6c473a5349c4dfd965009a35124b2c807117eda8"},
+ {file = "py_sr25519_bindings-0.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f12122a18b688e4a4bf0e74d3969d9e3f6f83d2b01fe88ab5f19c969e95192a2"},
+ {file = "py_sr25519_bindings-0.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f2815ecc958f6edbad79fee76899bd33b8950caa7106c1db08c828ec90e16fa7"},
+ {file = "py_sr25519_bindings-0.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfe52e73d7f0237820f7a935397d5004733a1d890464701f2c3c71be6033c442"},
+ {file = "py_sr25519_bindings-0.2.0-cp310-cp310-manylinux_2_28_armv7l.whl", hash = "sha256:df7e2fad636831919bfa479cd4b6fffdd429cde778da72b1834c1434dadaf982"},
+ {file = "py_sr25519_bindings-0.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f4ebeb2aac26a39160f2fad8ffc40ff98da835af57618c0446637bf182b9c927"},
+ {file = "py_sr25519_bindings-0.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:942a6b52e871d6e152dda80a60ed338dccedc69b6375e080e496bf886f2556c0"},
+ {file = "py_sr25519_bindings-0.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b24307c34a06209d0e34ca15ab4c0275617538dfdac1eac8aa25e792fa9f4108"},
+ {file = "py_sr25519_bindings-0.2.0-cp310-none-win32.whl", hash = "sha256:2e06a2d1119a2ad063f11448bb27ec4f4ba77416043d98ae28ef30624cf0e12d"},
+ {file = "py_sr25519_bindings-0.2.0-cp310-none-win_amd64.whl", hash = "sha256:16b36d9fe8bda873ab8376f3a4d0894b8d4ab2d702665afc3ab3ca69f0dc9495"},
+ {file = "py_sr25519_bindings-0.2.0-cp311-cp311-macosx_10_7_x86_64.whl", hash = "sha256:54e8c41081a4c23eca4b19f52de2514c48ddec6f49844dff7ad4cfac0bc11712"},
+ {file = "py_sr25519_bindings-0.2.0-cp311-cp311-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:6c73bd1a87849db9cd0e664b2d2e14208183dd8d11ac083d70e688fc28283a71"},
+ {file = "py_sr25519_bindings-0.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47d21382ea24f7f25e72cdddaca2f013ce46cc7983bcfebc611c795cea177eff"},
+ {file = "py_sr25519_bindings-0.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c1448cf55bbf6f52d2e24766a8a84ba6d77100a991897e8519711ccd7409830"},
+ {file = "py_sr25519_bindings-0.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:392b8b9875c89c9302930ad3d59567b62176f33adeee96a55ff61ba17fb7aac2"},
+ {file = "py_sr25519_bindings-0.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7b56b5cbbfb36b41ddfa462989a03386590ac036f3a755ef64fffeb2fed88654"},
+ {file = "py_sr25519_bindings-0.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8f06ea3237e06666e3a4ff4719b4fba415472943831b229428753c37d5ecd1b4"},
+ {file = "py_sr25519_bindings-0.2.0-cp311-none-win_amd64.whl", hash = "sha256:d62af30b2022f5fa787e46c06823c35a21abe791bf55012f498f9ba8e4baabc8"},
+ {file = "py_sr25519_bindings-0.2.0-cp312-cp312-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:ceafa0c31b49f2128461eb2c6ea18dc5d0bfae7218a100be7153f271e46bac49"},
+ {file = "py_sr25519_bindings-0.2.0-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:c8dedb8525556591738a64310875df70ea67886e5a40f2799bd96ef8848936cf"},
+ {file = "py_sr25519_bindings-0.2.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ce149796923696f5cfc6263f135674a14fe2d513fd35b2bfa73226b940aff648"},
+ {file = "py_sr25519_bindings-0.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20d71ca3ba22f98f4c208d509f735fe4eb5aa9e3547a507733a95828adde6cab"},
+ {file = "py_sr25519_bindings-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe8e20ee0856e8a60682566df955b81e7631670136607da627ab6892df34790d"},
+ {file = "py_sr25519_bindings-0.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0efd5487e3f6d6053cfc4a891b10f729d69263897270d0354f409ee2106fc9b7"},
+ {file = "py_sr25519_bindings-0.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ae7f2164d510458740145d20912d5d7a5c45e8fcde7cebd4057f60811ecc276f"},
+ {file = "py_sr25519_bindings-0.2.0-cp312-none-win32.whl", hash = "sha256:92382456c6f176c07e0d554c71d483853387885ce17714f8a4b50fdcf7552297"},
+ {file = "py_sr25519_bindings-0.2.0-cp312-none-win_amd64.whl", hash = "sha256:48ee4e14a77f815f3996beecb7d7abf422b756e9163ee4df739c1aded8a3e8ba"},
+ {file = "py_sr25519_bindings-0.2.0-cp36-cp36m-macosx_10_7_x86_64.whl", hash = "sha256:c3de899a1e911b8945f09e6389f8d2df68924c12c78e3e66fedb15f1e4ff56ad"},
+ {file = "py_sr25519_bindings-0.2.0-cp36-cp36m-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:758761b605f90e4238304df7520155a3358b13cc55ee18c5113632da17343163"},
+ {file = "py_sr25519_bindings-0.2.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f63580a224607e68b861eb03421465091c3104b6309e5fca7448f5aa6dbda60"},
+ {file = "py_sr25519_bindings-0.2.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37b999075b76cae8e84d5f19f2c8f28d3f24c93ba858ad49e58bcf22afe0406b"},
+ {file = "py_sr25519_bindings-0.2.0-cp36-cp36m-manylinux_2_28_armv7l.whl", hash = "sha256:5102c94e97d316009ad4482f24d9a933fc0b7eb0bb88e6a784a820cd1bd25827"},
+ {file = "py_sr25519_bindings-0.2.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b477b18940f472d4e25e141f19503a6e55aadff31b4822228a491c9638096baf"},
+ {file = "py_sr25519_bindings-0.2.0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:7e69bf7bdc9920013c1a2bea25a8b02df9588d9856cb20270f4d8d95b8e83f52"},
+ {file = "py_sr25519_bindings-0.2.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:dc436a34e17037833c3909062722ee3d46e28288972c87f619d163d00054d68e"},
+ {file = "py_sr25519_bindings-0.2.0-cp36-none-win32.whl", hash = "sha256:fc27c847dd4df727388aaadc3870aeb472f2d5c35717536d319792fe08f6120a"},
+ {file = "py_sr25519_bindings-0.2.0-cp36-none-win_amd64.whl", hash = "sha256:0441381c2a6f532831d560a1f2ae8a917c7190cf27f5428d9b0528fa28a72e2d"},
+ {file = "py_sr25519_bindings-0.2.0-cp37-cp37m-macosx_10_7_x86_64.whl", hash = "sha256:e1471134450e6189b7e38d245ab16b06f3de900b6d07aa66b1e6973cdbc00d01"},
+ {file = "py_sr25519_bindings-0.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:302bd20e75d900d98e7961934b03958e8acc8784eed594ab48f9bb298396c734"},
+ {file = "py_sr25519_bindings-0.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1e09ac91f4b2e2b9c50e268f6ee292d9fa447c5b7cc6327cfeae7750d716f49e"},
+ {file = "py_sr25519_bindings-0.2.0-cp37-cp37m-manylinux_2_28_armv7l.whl", hash = "sha256:28b904739147c4f797627bd3b44d1e64d061533253abd1882c6d3b8944e7bbd8"},
+ {file = "py_sr25519_bindings-0.2.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0931ac85331aae33bef67460a3cce554ef5c1f7dfec0ebe2f5b9ea57c5bee65c"},
+ {file = "py_sr25519_bindings-0.2.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:cd8da64f9e42ff973b394ed9164f1e9a454279a058eed08ac8d006fcbd61093b"},
+ {file = "py_sr25519_bindings-0.2.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:297ad50e3cace5c89dbf5bd916b714aac3ebe6bc76f85382dac228cbeb71449e"},
+ {file = "py_sr25519_bindings-0.2.0-cp37-none-win32.whl", hash = "sha256:422d62ca74ebe5065eca88607552b9a5f1dc4abff0c597cc3793dd8adfb8c4ea"},
+ {file = "py_sr25519_bindings-0.2.0-cp37-none-win_amd64.whl", hash = "sha256:d1b0ed9a4dded60f671f34fdd81c974dad159e98f43bcab21833f984e05920f9"},
+ {file = "py_sr25519_bindings-0.2.0-cp38-cp38-macosx_10_7_x86_64.whl", hash = "sha256:37f11ffee535c624bf5ddc6109c2cdca9a2dbd10f7d310bcd1dd97f6121c532f"},
+ {file = "py_sr25519_bindings-0.2.0-cp38-cp38-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:4e1b553a6b1cc1b0aa9da2d7157329713cc7f299acb12a052d326f9b594b145c"},
+ {file = "py_sr25519_bindings-0.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f0b6dcf1328027dba1f9236bd3432cc3cce1de55a12c1a3a4ea7a8dc3ab3e857"},
+ {file = "py_sr25519_bindings-0.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:458c7e6d7447bd267a6f870a8801e995d710952566a0a52634f408bf804cf27a"},
+ {file = "py_sr25519_bindings-0.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d64253d7d08fd6073e7b79bba9cff78687e76698cc210d3c6f236b90766b9421"},
+ {file = "py_sr25519_bindings-0.2.0-cp38-cp38-manylinux_2_28_armv7l.whl", hash = "sha256:a9aac20a196416b8daf764704a9cee71ddee16bc705d12b5c6bcb6f51e81ac6e"},
+ {file = "py_sr25519_bindings-0.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e162687189cf765f602178aa195a2be4284107622141ff746e92e14e266cf3b7"},
+ {file = "py_sr25519_bindings-0.2.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d44ab4d150c9bdd1641ccad49942ecf2d0ef61bd66a7da41094bb4a9cbaca529"},
+ {file = "py_sr25519_bindings-0.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:251ff9cef5dafd96ff241b77ff471912c40249b6df31e71c6c32de6a26a8dbc6"},
+ {file = "py_sr25519_bindings-0.2.0-cp38-none-win32.whl", hash = "sha256:ca9794f9d4fc37cdc8cbb6724d5432a064d22c26ecde312928154b6bc691f4d3"},
+ {file = "py_sr25519_bindings-0.2.0-cp38-none-win_amd64.whl", hash = "sha256:6406cb0aeb5cbb8cfaa37d59d15d7640c0d812a1cbb55657bee52fd3d9e92aa9"},
+ {file = "py_sr25519_bindings-0.2.0-cp39-cp39-macosx_10_7_x86_64.whl", hash = "sha256:b9da73447c8f5b8392a8229c2b65d742709c6aa2d0c6b32e39b635fb245145f1"},
+ {file = "py_sr25519_bindings-0.2.0-cp39-cp39-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:7f00236a802d6d3f3705713d5352ba968c0ce353a20519c445e66ce19869bfdc"},
+ {file = "py_sr25519_bindings-0.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d473199c0dbad846b0723c6663b1b6a04040ccdca700cb1609acac3e621f2087"},
+ {file = "py_sr25519_bindings-0.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44bede0dd42f75cf849d3ccb4e443d6425218035bc00a6330b11dc2cc1146f3b"},
+ {file = "py_sr25519_bindings-0.2.0-cp39-cp39-manylinux_2_28_armv7l.whl", hash = "sha256:a8e462d2442726d9db07854dc2eb640b1a8a548948b1ff3aa580771ab739bab8"},
+ {file = "py_sr25519_bindings-0.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:55b1f67fdaeab91481fda54432dffdf87ed516d26461d31e70911c7ea55d6164"},
+ {file = "py_sr25519_bindings-0.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ec11493d59075ba75fe0bc0312d502ffdc45b641a46fb084bf8b04906597688b"},
+ {file = "py_sr25519_bindings-0.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:101ee46368da149ad332aea225d4ff2907dffce574e8f8f7fe56f5c29211f333"},
+ {file = "py_sr25519_bindings-0.2.0-cp39-none-win32.whl", hash = "sha256:909f13f63f67f1e5595d4d495cf8a3c95e392626c08f94550cbf8f0e8ea1c743"},
+ {file = "py_sr25519_bindings-0.2.0-cp39-none-win_amd64.whl", hash = "sha256:b3f86e4aad6c2b8ff74af76f38fde7fbaf9dd83bc4a7c259709092008c3b8e5d"},
+ {file = "py_sr25519_bindings-0.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38db0ee90bd676b9df7ddd03fcb2113b5a5e9d9c984d82426728acc0e9d54277"},
+ {file = "py_sr25519_bindings-0.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dfe767069d5c5e8a313e77b6bd681ea4f6b5988b09b6b4c9399e255fe4a7c53"},
+ {file = "py_sr25519_bindings-0.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8951d1a6e310a682a6253d547e44a9e7a606476dbc18dea3f121d98bdb81042"},
+ {file = "py_sr25519_bindings-0.2.0.tar.gz", hash = "sha256:0c2fe92b7cdcebf6c5611a90054f8ba6ea90b68b8832896d2dc565537bc40b0c"},
+]
+
+[[package]]
+name = "pycparser"
+version = "2.22"
+description = "C parser in Python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"},
+ {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
+]
+
+[[package]]
+name = "pycryptodome"
+version = "3.20.0"
+description = "Cryptographic library for Python"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+files = [
+ {file = "pycryptodome-3.20.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:f0e6d631bae3f231d3634f91ae4da7a960f7ff87f2865b2d2b831af1dfb04e9a"},
+ {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:baee115a9ba6c5d2709a1e88ffe62b73ecc044852a925dcb67713a288c4ec70f"},
+ {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:417a276aaa9cb3be91f9014e9d18d10e840a7a9b9a9be64a42f553c5b50b4d1d"},
+ {file = "pycryptodome-3.20.0-cp27-cp27m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2a1250b7ea809f752b68e3e6f3fd946b5939a52eaeea18c73bdab53e9ba3c2dd"},
+ {file = "pycryptodome-3.20.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:d5954acfe9e00bc83ed9f5cb082ed22c592fbbef86dc48b907238be64ead5c33"},
+ {file = "pycryptodome-3.20.0-cp27-cp27m-win32.whl", hash = "sha256:06d6de87c19f967f03b4cf9b34e538ef46e99a337e9a61a77dbe44b2cbcf0690"},
+ {file = "pycryptodome-3.20.0-cp27-cp27m-win_amd64.whl", hash = "sha256:ec0bb1188c1d13426039af8ffcb4dbe3aad1d7680c35a62d8eaf2a529b5d3d4f"},
+ {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:5601c934c498cd267640b57569e73793cb9a83506f7c73a8ec57a516f5b0b091"},
+ {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:d29daa681517f4bc318cd8a23af87e1f2a7bad2fe361e8aa29c77d652a065de4"},
+ {file = "pycryptodome-3.20.0-cp27-cp27mu-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3427d9e5310af6680678f4cce149f54e0bb4af60101c7f2c16fdf878b39ccccc"},
+ {file = "pycryptodome-3.20.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:3cd3ef3aee1079ae44afaeee13393cf68b1058f70576b11439483e34f93cf818"},
+ {file = "pycryptodome-3.20.0-cp35-abi3-macosx_10_9_universal2.whl", hash = "sha256:ac1c7c0624a862f2e53438a15c9259d1655325fc2ec4392e66dc46cdae24d044"},
+ {file = "pycryptodome-3.20.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:76658f0d942051d12a9bd08ca1b6b34fd762a8ee4240984f7c06ddfb55eaf15a"},
+ {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f35d6cee81fa145333137009d9c8ba90951d7d77b67c79cbe5f03c7eb74d8fe2"},
+ {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76cb39afede7055127e35a444c1c041d2e8d2f1f9c121ecef573757ba4cd2c3c"},
+ {file = "pycryptodome-3.20.0-cp35-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49a4c4dc60b78ec41d2afa392491d788c2e06edf48580fbfb0dd0f828af49d25"},
+ {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:fb3b87461fa35afa19c971b0a2b7456a7b1db7b4eba9a8424666104925b78128"},
+ {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_i686.whl", hash = "sha256:acc2614e2e5346a4a4eab6e199203034924313626f9620b7b4b38e9ad74b7e0c"},
+ {file = "pycryptodome-3.20.0-cp35-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:210ba1b647837bfc42dd5a813cdecb5b86193ae11a3f5d972b9a0ae2c7e9e4b4"},
+ {file = "pycryptodome-3.20.0-cp35-abi3-win32.whl", hash = "sha256:8d6b98d0d83d21fb757a182d52940d028564efe8147baa9ce0f38d057104ae72"},
+ {file = "pycryptodome-3.20.0-cp35-abi3-win_amd64.whl", hash = "sha256:9b3ae153c89a480a0ec402e23db8d8d84a3833b65fa4b15b81b83be9d637aab9"},
+ {file = "pycryptodome-3.20.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:4401564ebf37dfde45d096974c7a159b52eeabd9969135f0426907db367a652a"},
+ {file = "pycryptodome-3.20.0-pp27-pypy_73-win32.whl", hash = "sha256:ec1f93feb3bb93380ab0ebf8b859e8e5678c0f010d2d78367cf6bc30bfeb148e"},
+ {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:acae12b9ede49f38eb0ef76fdec2df2e94aad85ae46ec85be3648a57f0a7db04"},
+ {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f47888542a0633baff535a04726948e876bf1ed880fddb7c10a736fa99146ab3"},
+ {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6e0e4a987d38cfc2e71b4a1b591bae4891eeabe5fa0f56154f576e26287bfdea"},
+ {file = "pycryptodome-3.20.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:c18b381553638414b38705f07d1ef0a7cf301bc78a5f9bc17a957eb19446834b"},
+ {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a60fedd2b37b4cb11ccb5d0399efe26db9e0dd149016c1cc6c8161974ceac2d6"},
+ {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:405002eafad114a2f9a930f5db65feef7b53c4784495dd8758069b89baf68eab"},
+ {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2ab6ab0cb755154ad14e507d1df72de9897e99fd2d4922851a276ccc14f4f1a5"},
+ {file = "pycryptodome-3.20.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:acf6e43fa75aca2d33e93409f2dafe386fe051818ee79ee8a3e21de9caa2ac9e"},
+ {file = "pycryptodome-3.20.0.tar.gz", hash = "sha256:09609209ed7de61c2b560cc5c8c4fbf892f8b15b1faf7e4cbffac97db1fffda7"},
+]
+
+[[package]]
+name = "pynacl"
+version = "1.5.0"
+description = "Python binding to the Networking and Cryptography (NaCl) library"
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"},
+ {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"},
+ {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"},
+ {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"},
+ {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"},
+ {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"},
+ {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"},
+ {file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"},
+ {file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"},
+ {file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"},
+]
+
+[package.dependencies]
+cffi = ">=1.4.1"
+
+[package.extras]
+docs = ["sphinx (>=1.6.5)", "sphinx-rtd-theme"]
+tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"]
+
+[[package]]
+name = "pytest"
+version = "8.2.0"
+description = "pytest: simple powerful testing with Python"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pytest-8.2.0-py3-none-any.whl", hash = "sha256:1733f0620f6cda4095bbf0d9ff8022486e91892245bb9e7d5542c018f612f233"},
+ {file = "pytest-8.2.0.tar.gz", hash = "sha256:d507d4482197eac0ba2bae2e9babf0672eb333017bcedaa5fb1a3d42c1174b3f"},
+]
+
+[package.dependencies]
+colorama = {version = "*", markers = "sys_platform == \"win32\""}
+iniconfig = "*"
+packaging = "*"
+pluggy = ">=1.5,<2.0"
+
+[package.extras]
+dev = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
+
+[[package]]
+name = "pytest-asyncio"
+version = "0.23.6"
+description = "Pytest support for asyncio"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "pytest-asyncio-0.23.6.tar.gz", hash = "sha256:ffe523a89c1c222598c76856e76852b787504ddb72dd5d9b6617ffa8aa2cde5f"},
+ {file = "pytest_asyncio-0.23.6-py3-none-any.whl", hash = "sha256:68516fdd1018ac57b846c9846b954f0393b26f094764a28c955eabb0536a4e8a"},
+]
+
+[package.dependencies]
+pytest = ">=7.0.0,<9"
+
+[package.extras]
+docs = ["sphinx (>=5.3)", "sphinx-rtd-theme (>=1.0)"]
+testing = ["coverage (>=6.2)", "hypothesis (>=5.7.1)"]
+
+[[package]]
+name = "python-dotenv"
+version = "1.0.1"
+description = "Read key-value pairs from a .env file and set them as environment variables"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"},
+ {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"},
+]
+
+[package.extras]
+cli = ["click (>=5.0)"]
+
+[[package]]
+name = "pyyaml"
+version = "6.0.1"
+description = "YAML parser and emitter for Python"
+optional = false
+python-versions = ">=3.6"
+files = [
+ {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"},
+ {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"},
+ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"},
+ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"},
+ {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"},
+ {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"},
+ {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"},
+ {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"},
+ {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"},
+ {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"},
+ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"},
+ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"},
+ {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"},
+ {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"},
+ {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"},
+ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
+ {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
+ {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
+ {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"},
+ {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
+ {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
+ {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
+ {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"},
+ {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"},
+ {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"},
+ {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"},
+ {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"},
+ {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"},
+ {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"},
+ {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"},
+ {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"},
+ {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"},
+ {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"},
+ {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"},
+ {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"},
+ {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"},
+ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"},
+ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"},
+ {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"},
+ {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"},
+ {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"},
+ {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"},
+ {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"},
+ {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"},
+ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"},
+ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"},
+ {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"},
+ {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"},
+ {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"},
+ {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"},
+ {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
+]
+
+[[package]]
+name = "setuptools"
+version = "69.5.1"
+description = "Easily download, build, install, upgrade, and uninstall Python packages"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32"},
+ {file = "setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987"},
+]
+
+[package.extras]
+docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
+testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
+testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
+
+[[package]]
+name = "six"
+version = "1.16.0"
+description = "Python 2 and 3 compatibility utilities"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
+files = [
+ {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
+ {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
+]
+
+[[package]]
+name = "sniffio"
+version = "1.3.1"
+description = "Sniff out which async library your code is running under"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2"},
+ {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"},
+]
+
+[[package]]
+name = "v4-proto"
+version = "4.1.1"
+description = "Protos for dYdX Chain protocol"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "v4-proto-4.1.1.tar.gz", hash = "sha256:a0806c54fe2ecb47e320c2c2783dd38b1115a83d49d62fe55e6ceab4246ec8f0"},
+ {file = "v4_proto-4.1.1-py3-none-any.whl", hash = "sha256:e453a50859c958dd3149fb217dfa9ad813edd8136ff9093ae6dd99b479e49c07"},
+]
+
+[package.dependencies]
+grpcio = ">=1.54"
+grpcio-tools = ">=1.54"
+protobuf = ">=4.23"
+
+[[package]]
+name = "virtualenv"
+version = "20.26.1"
+description = "Virtual Python Environment builder"
+optional = false
+python-versions = ">=3.7"
+files = [
+ {file = "virtualenv-20.26.1-py3-none-any.whl", hash = "sha256:7aa9982a728ae5892558bff6a2839c00b9ed145523ece2274fad6f414690ae75"},
+ {file = "virtualenv-20.26.1.tar.gz", hash = "sha256:604bfdceaeece392802e6ae48e69cec49168b9c5f4a44e483963f9242eb0e78b"},
+]
+
+[package.dependencies]
+distlib = ">=0.3.7,<1"
+filelock = ">=3.12.2,<4"
+platformdirs = ">=3.9.1,<5"
+
+[package.extras]
+docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"]
+test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"]
+
+[[package]]
+name = "websocket-client"
+version = "1.8.0"
+description = "WebSocket client for Python with low level API options"
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526"},
+ {file = "websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da"},
+]
+
+[package.extras]
+docs = ["Sphinx (>=6.0)", "myst-parser (>=2.0.0)", "sphinx-rtd-theme (>=1.1.0)"]
+optional = ["python-socks", "wsaccel"]
+test = ["websockets"]
+
+[metadata]
+lock-version = "2.0"
+python-versions = "^3.12"
+content-hash = "90a2e949004c5213a4a2ab072a549347ff63ca1d9c95a0bbbe0a857caa264d74"
diff --git a/v4-client-py-v2/pyproject.toml b/v4-client-py-v2/pyproject.toml
new file mode 100644
index 00000000..583cd428
--- /dev/null
+++ b/v4-client-py-v2/pyproject.toml
@@ -0,0 +1,32 @@
+[tool.poetry]
+name = "dydx-v4-client"
+version = "0.1.0"
+description = ""
+authors = ["Piotr Piwoński "]
+readme = "README.md"
+
+[tool.poetry.dependencies]
+python = "^3.12"
+v4-proto = "^4.0.1"
+httpx = "^0.27.0"
+websocket-client = "^1.7.0"
+bip-utils = "^2.9.3"
+ecdsa = "^0.19.0"
+
+
+[tool.poetry.group.dev.dependencies]
+pre-commit = "^3.7.0"
+pytest = "^8.1.1"
+pytest-asyncio = "^0.23.6"
+python-dotenv = "^1.0.1"
+
+
+[tool.poetry.group.examples.dependencies]
+nest-asyncio = "^1.6.0"
+
+[build-system]
+requires = ["poetry-core"]
+build-backend = "poetry.core.masonry.api"
+
+[tool.isort]
+profile = "black"
\ No newline at end of file
diff --git a/v4-client-py-v2/pytest.ini b/v4-client-py-v2/pytest.ini
new file mode 100644
index 00000000..6537d198
--- /dev/null
+++ b/v4-client-py-v2/pytest.ini
@@ -0,0 +1,2 @@
+[pytest]
+asyncio_mode=auto
\ No newline at end of file
diff --git a/v4-client-py-v2/tests/__init__.py b/v4-client-py-v2/tests/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/v4-client-py-v2/tests/conftest.py b/v4-client-py-v2/tests/conftest.py
new file mode 100644
index 00000000..d18455bd
--- /dev/null
+++ b/v4-client-py-v2/tests/conftest.py
@@ -0,0 +1,127 @@
+import asyncio
+import random
+import time
+from functools import wraps
+
+import httpx
+import pytest
+
+from dydx_v4_client import FaucetClient, NodeClient
+from dydx_v4_client.indexer.rest.indexer_client import IndexerClient
+from dydx_v4_client.indexer.rest.noble_client import NobleClient
+from dydx_v4_client.indexer.socket.websocket import IndexerSocket
+from dydx_v4_client.network import TESTNET, TESTNET_FAUCET, TESTNET_NOBLE
+from dydx_v4_client.node.message import order, order_id
+from dydx_v4_client.wallet import Wallet, from_mnemonic
+
+pytest_plugins = ("pytest_asyncio",)
+
+DYDX_TEST_PRIVATE_KEY = (
+ "e92a6595c934c991d3b3e987ea9b3125bf61a076deab3a9cb519787b7b3e8d77"
+)
+DYDX_TEST_MNEMONIC = (
+ "mirror actor skill push coach wait confirm orchard lunch mobile athlete gossip awake "
+ "miracle matter bus reopen team ladder lazy list timber render wait"
+)
+TEST_ADDRESS = "dydx14zzueazeh0hj67cghhf9jypslcf9sh2n5k6art"
+RECIPIENT = "dydx1slanxj8x9ntk9knwa6cvfv2tzlsq5gk3dshml0"
+
+
+@pytest.fixture
+def indexer_rest_client():
+ return IndexerClient(TESTNET.rest_indexer)
+
+
+@pytest.fixture
+async def indexer_socket_client():
+ return IndexerSocket(TESTNET.websocket_indexer)
+
+
+@pytest.fixture
+async def faucet_client():
+ return FaucetClient(faucet_url=TESTNET_FAUCET)
+
+
+@pytest.fixture
+async def node_client():
+ return await NodeClient.connect(TESTNET.node)
+
+
+@pytest.fixture
+async def noble_client():
+ client = NobleClient(TESTNET_NOBLE)
+ await client.connect(DYDX_TEST_MNEMONIC)
+ yield client
+
+
+@pytest.fixture
+def test_address():
+ return TEST_ADDRESS
+
+
+@pytest.fixture
+def recipient():
+ return RECIPIENT
+
+
+@pytest.fixture
+def private_key():
+ return from_mnemonic(DYDX_TEST_MNEMONIC)
+
+
+@pytest.fixture
+def test_order_id(test_address):
+ return order_id(
+ test_address,
+ subaccount_number=0,
+ client_id=random.randint(0, 1000000000),
+ clob_pair_id=0,
+ order_flags=64,
+ )
+
+
+@pytest.fixture
+def test_order(test_order_id):
+ return order(
+ test_order_id,
+ time_in_force=0,
+ reduce_only=False,
+ side=1,
+ quantums=10000000,
+ subticks=40000000000,
+ good_til_block_time=int(time.time() + 60),
+ )
+
+
+async def get_wallet(node_client, private_key, test_address):
+ account = await node_client.get_account(test_address)
+ return Wallet(private_key, account.account_number, account.sequence)
+
+
+@pytest.fixture()
+async def wallet(node_client, private_key, test_address):
+ return await get_wallet(node_client, private_key, test_address)
+
+
+def retry_on_forbidden(max_retries=3, delay=1):
+ def decorator(func):
+ @wraps(func)
+ async def wrapper(*args, **kwargs):
+ for attempt in range(max_retries):
+ try:
+ return await func(*args, **kwargs)
+ except httpx.HTTPStatusError as e:
+ if e.response.status_code == 403:
+ if attempt < max_retries - 1:
+ await asyncio.sleep(delay)
+ continue
+ raise
+ raise httpx.HTTPStatusError(
+ request=e.request,
+ response=e.response,
+ message=f"Failed after {max_retries} retries with 403 Forbidden error.",
+ )
+
+ return wrapper
+
+ return decorator
diff --git a/v4-client-py-v2/tests/indexer/__init__.py b/v4-client-py-v2/tests/indexer/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/v4-client-py-v2/tests/indexer/rest/__init__.py b/v4-client-py-v2/tests/indexer/rest/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/v4-client-py-v2/tests/indexer/rest/modules/__init__.py b/v4-client-py-v2/tests/indexer/rest/modules/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/v4-client-py-v2/tests/indexer/rest/modules/test_account_endpoints.py b/v4-client-py-v2/tests/indexer/rest/modules/test_account_endpoints.py
new file mode 100644
index 00000000..d7646ed0
--- /dev/null
+++ b/v4-client-py-v2/tests/indexer/rest/modules/test_account_endpoints.py
@@ -0,0 +1,148 @@
+import pytest
+
+from dydx_v4_client.indexer.rest.constants import TradingRewardAggregationPeriod
+from tests.conftest import retry_on_forbidden
+
+
+@pytest.mark.asyncio
+@retry_on_forbidden(max_retries=3, delay=1)
+async def test_subaccounts(indexer_rest_client, test_address):
+ response = await indexer_rest_client.account.get_subaccounts(test_address)
+ subaccounts = response["subaccounts"]
+ assert subaccounts is not None
+ assert len(subaccounts) > 0
+ subaccount0 = subaccounts[0]
+ subaccount_number = subaccount0["subaccountNumber"]
+ assert subaccount_number is not None
+
+
+@pytest.mark.asyncio
+@retry_on_forbidden(max_retries=3, delay=1)
+async def test_subaccount_0(indexer_rest_client, test_address):
+ response = await indexer_rest_client.account.get_subaccount(test_address, 0)
+ subaccount = response["subaccount"]
+ assert subaccount is not None
+ subaccount_number = subaccount["subaccountNumber"]
+ assert subaccount_number is not None
+
+
+@pytest.mark.asyncio
+@retry_on_forbidden(max_retries=3, delay=1)
+async def test_asset_positions(indexer_rest_client, test_address):
+ response = await indexer_rest_client.account.get_subaccount_asset_positions(
+ test_address, 0
+ )
+ assert response is not None
+ positions = response["positions"]
+ assert positions is not None
+ if len(positions) > 0:
+ position = positions[0]
+ assert position is not None
+
+
+@pytest.mark.asyncio
+@retry_on_forbidden(max_retries=3, delay=1)
+async def test_perpetual_positions(indexer_rest_client, test_address):
+ response = await indexer_rest_client.account.get_subaccount_perpetual_positions(
+ test_address, 0
+ )
+ assert response is not None
+ positions = response["positions"]
+ assert positions is not None
+ if len(positions) > 0:
+ position = positions[0]
+ assert position is not None
+
+
+@pytest.mark.asyncio
+@retry_on_forbidden(max_retries=3, delay=1)
+async def test_transfers(indexer_rest_client, test_address):
+ response = await indexer_rest_client.account.get_subaccount_transfers(
+ test_address, 0
+ )
+ assert response is not None
+ transfers = response["transfers"]
+ assert transfers is not None
+ if len(transfers) > 0:
+ transfer = transfers[0]
+ assert transfer is not None
+
+
+@pytest.mark.asyncio
+@retry_on_forbidden(max_retries=3, delay=1)
+async def test_orders(indexer_rest_client, test_address):
+ response = await indexer_rest_client.account.get_subaccount_orders(test_address, 0)
+ assert response is not None
+ orders = response
+ assert orders is not None
+ if len(orders) > 0:
+ order = orders[0]
+ assert order is not None
+
+
+@pytest.mark.asyncio
+@retry_on_forbidden(max_retries=3, delay=1)
+async def test_fills(indexer_rest_client, test_address):
+ response = await indexer_rest_client.account.get_subaccount_fills(test_address, 0)
+ assert response is not None
+ fills = response["fills"]
+ assert fills is not None
+ if len(fills) > 0:
+ fill = fills[0]
+ assert fill is not None
+
+
+@pytest.mark.asyncio
+@retry_on_forbidden(max_retries=3, delay=1)
+async def test_historical_pnl(indexer_rest_client, test_address):
+ response = await indexer_rest_client.account.get_subaccount_historical_pnls(
+ test_address, 0
+ )
+ assert response is not None
+ historical_pnl = response["historicalPnl"]
+ assert historical_pnl is not None
+ if len(historical_pnl) > 0:
+ historical_pnl0 = historical_pnl[0]
+ assert historical_pnl0 is not None
+
+
+@pytest.mark.asyncio
+@retry_on_forbidden(max_retries=3, delay=1)
+async def test_historical_block_trading_rewards(indexer_rest_client, test_address):
+ limit = 10
+ response = await indexer_rest_client.account.get_historical_block_trading_rewards(
+ test_address, limit
+ )
+ historical_rewards = response["rewards"]
+ assert historical_rewards is not None
+ assert isinstance(historical_rewards, list)
+ assert len(historical_rewards) <= limit
+
+ assert "createdAt" in historical_rewards[0]
+ assert "createdAtHeight" in historical_rewards[0]
+ assert "tradingReward" in historical_rewards[0]
+
+
+@pytest.mark.asyncio
+@retry_on_forbidden(max_retries=3, delay=1)
+async def test_historical_trading_rewards_aggregated(indexer_rest_client, test_address):
+ period = TradingRewardAggregationPeriod.DAILY
+ limit = 10
+ response = (
+ await indexer_rest_client.account.get_historical_trading_rewards_aggregated(
+ test_address, period, limit
+ )
+ )
+ aggregations = response["rewards"]
+ assert aggregations is not None
+ assert isinstance(aggregations, list)
+ assert len(aggregations) <= limit
+
+ for aggregation in aggregations:
+ assert "period" in aggregation
+ assert aggregation["period"] == period
+ assert "tradingReward" in aggregation
+ assert "startedAt" in aggregation
+ assert "endedAt" in aggregation
+ assert "startedAtHeight" in aggregation
+ assert "endedAtHeight" in aggregation
diff --git a/v4-client-py-v2/tests/indexer/rest/modules/test_markets_endpoints.py b/v4-client-py-v2/tests/indexer/rest/modules/test_markets_endpoints.py
new file mode 100644
index 00000000..b91cc198
--- /dev/null
+++ b/v4-client-py-v2/tests/indexer/rest/modules/test_markets_endpoints.py
@@ -0,0 +1,79 @@
+import pytest
+
+from tests.conftest import retry_on_forbidden
+
+MARKET_BTC_USD: str = "BTC-USD"
+
+
+@pytest.mark.asyncio
+@retry_on_forbidden(max_retries=3, delay=1)
+async def test_markets(indexer_rest_client):
+ response = await indexer_rest_client.markets.get_perpetual_markets()
+ btc = response["markets"][MARKET_BTC_USD]
+ status = btc["status"]
+ assert status == "ACTIVE"
+
+
+@pytest.mark.asyncio
+@retry_on_forbidden(max_retries=3, delay=1)
+async def test_btc_market(indexer_rest_client):
+ response = await indexer_rest_client.markets.get_perpetual_markets(MARKET_BTC_USD)
+ btc = response["markets"][MARKET_BTC_USD]
+ status = btc["status"]
+ assert status == "ACTIVE"
+
+
+@pytest.mark.asyncio
+@retry_on_forbidden(max_retries=3, delay=1)
+async def test_btc_trades(indexer_rest_client):
+ response = await indexer_rest_client.markets.get_perpetual_market_trades(
+ MARKET_BTC_USD
+ )
+ trades = response["trades"]
+ assert trades is not None
+
+
+@pytest.mark.asyncio
+@retry_on_forbidden(max_retries=3, delay=1)
+async def test_btc_orderbook(indexer_rest_client):
+ response = await indexer_rest_client.markets.get_perpetual_market_orderbook(
+ MARKET_BTC_USD
+ )
+ asks = response["asks"]
+ bids = response["bids"]
+ assert asks is not None
+ assert bids is not None
+
+
+@pytest.mark.asyncio
+@retry_on_forbidden(max_retries=3, delay=1)
+async def test_btc_candles(indexer_rest_client):
+ response = await indexer_rest_client.markets.get_perpetual_market_candles(
+ MARKET_BTC_USD, "1MIN"
+ )
+ candles = response["candles"]
+ assert candles is not None
+
+
+@pytest.mark.asyncio
+@retry_on_forbidden(max_retries=3, delay=1)
+async def test_btc_historical_funding(indexer_rest_client):
+ response = (
+ await indexer_rest_client.markets.get_perpetual_market_historical_funding(
+ MARKET_BTC_USD
+ )
+ )
+ assert response is not None
+ historical_funding = response["historicalFunding"]
+ assert historical_funding is not None
+ if len(historical_funding) > 0:
+ historical_funding0 = historical_funding[0]
+ assert historical_funding0 is not None
+
+
+@pytest.mark.asyncio
+@retry_on_forbidden(max_retries=3, delay=1)
+async def test_sparklines(indexer_rest_client):
+ response = await indexer_rest_client.markets.get_perpetual_market_sparklines()
+ btc_sparklines = response[MARKET_BTC_USD]
+ assert btc_sparklines is not None
diff --git a/v4-client-py-v2/tests/indexer/rest/modules/test_status_endpoints.py b/v4-client-py-v2/tests/indexer/rest/modules/test_status_endpoints.py
new file mode 100644
index 00000000..8314226b
--- /dev/null
+++ b/v4-client-py-v2/tests/indexer/rest/modules/test_status_endpoints.py
@@ -0,0 +1,29 @@
+import pytest
+
+from tests.conftest import retry_on_forbidden
+
+
+@pytest.mark.asyncio
+@retry_on_forbidden(max_retries=3, delay=1)
+async def test_get_time(indexer_rest_client):
+ response = await indexer_rest_client.utility.get_time()
+ iso = response["iso"]
+ assert iso is not None
+
+
+@pytest.mark.asyncio
+@retry_on_forbidden(max_retries=3, delay=1)
+async def test_get_height(indexer_rest_client):
+ response = await indexer_rest_client.utility.get_height()
+ height = response["height"]
+ time = response["time"]
+ assert height is not None
+ assert time is not None
+
+
+@pytest.mark.asyncio
+@pytest.mark.skip(reason="Endpoint may have changed")
+async def test_screen_address(indexer_rest_client, test_address):
+ response = await indexer_rest_client.utility.screen(test_address)
+ restricted = response.get("restricted")
+ assert restricted is not None
diff --git a/v4-client-py-v2/tests/indexer/rest/test_noble_client.py b/v4-client-py-v2/tests/indexer/rest/test_noble_client.py
new file mode 100644
index 00000000..d4006da4
--- /dev/null
+++ b/v4-client-py-v2/tests/indexer/rest/test_noble_client.py
@@ -0,0 +1,50 @@
+import pytest
+from v4_proto.cosmos.base.abci.v1beta1.abci_pb2 import TxResponse
+
+
+@pytest.mark.asyncio
+async def test_is_connected(noble_client):
+ assert noble_client.is_connected
+
+
+@pytest.mark.asyncio
+@pytest.mark.skip(reason="This test is not implemented")
+async def test_ibc_transfer(node_client, noble_client):
+ message = {
+ "source_port": "transfer",
+ "source_channel": "channel-0",
+ "token": {"denom": "usdc", "amount": "1000"},
+ "sender": noble_client.wallet.get_verifying_key().to_string(),
+ "receiver": "cosmos1...",
+ "timeout_height": 0,
+ "timeout_timestamp": 0,
+ }
+ tx_response = await node_client.ibc_transfer([message])
+ assert isinstance(tx_response, TxResponse)
+ assert tx_response.code == 0
+
+
+@pytest.mark.asyncio
+@pytest.mark.skip(reason="This test is not implemented")
+async def test_send(noble_client):
+ message = {
+ "depositor": noble_client.wallet.get_verifying_key().to_string(),
+ "amount": {"denom": "usdc", "amount": "1000"},
+ }
+ tx_response = await noble_client.send([message])
+ assert isinstance(tx_response, TxResponse)
+ assert tx_response.code == 0
+
+
+@pytest.mark.asyncio
+@pytest.mark.skip(reason="This test is not implemented")
+async def test_simulate_transaction(noble_client, node_client):
+ message = {
+ "depositor": noble_client.wallet.get_verifying_key().to_string(),
+ "amount": {"denom": "usdc", "amount": "1000"},
+ }
+ fee = await noble_client.simulate_transaction([message])
+ assert isinstance(fee, dict)
+ assert fee["gas_limit"] > 0
+ assert len(fee["amount"]) == 1
+ assert "usdc" in fee["amount"][0]["denom"]
diff --git a/v4-client-py-v2/tests/indexer/socket/__init__.py b/v4-client-py-v2/tests/indexer/socket/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/v4-client-py-v2/tests/indexer/socket/test_websocket.py b/v4-client-py-v2/tests/indexer/socket/test_websocket.py
new file mode 100644
index 00000000..30ade217
--- /dev/null
+++ b/v4-client-py-v2/tests/indexer/socket/test_websocket.py
@@ -0,0 +1,141 @@
+import json
+import os
+
+import pytest
+from dotenv import load_dotenv
+
+from dydx_v4_client.indexer.socket.websocket import CandlesResolution
+
+load_dotenv()
+
+
+@pytest.mark.asyncio
+async def test_order_book(indexer_socket_client):
+
+ order_book_channel_name = indexer_socket_client.order_book.channel
+
+ def on_message(ws, message):
+ message_dict = json.loads(message)
+ if message_dict["type"] == "connected":
+ ws.order_book.subscribe(id="BTC-USD")
+ elif message_dict["type"] == "subscribed":
+ assert message_dict["channel"] == order_book_channel_name
+ if os.getenv("CI") == "true":
+ ws.order_book.unsubscribe(id="BTC-USD")
+ ws.close()
+ elif message_dict["type"] in ["channel_data", "channel_batch_data"]:
+ assert message_dict["channel"] == order_book_channel_name
+ assert "bids" or "asks" in message_dict["contents"][0]
+ ws.order_book.unsubscribe(id="BTC-USD")
+ ws.close()
+ else:
+ ws.close()
+ assert False, f"Unexpected message: {message_dict}"
+
+ indexer_socket_client.on_message = on_message
+ await indexer_socket_client.connect()
+
+
+@pytest.mark.asyncio
+async def test_trades(indexer_socket_client):
+ trades_channel_name = indexer_socket_client.trades.channel
+
+ def on_message(ws, message):
+ message_dict = json.loads(message)
+ if message_dict["type"] == "connected":
+ ws.trades.subscribe(id="BTC-USD")
+ elif message_dict["type"] == "subscribed":
+ assert message_dict["channel"] == trades_channel_name
+ if os.getenv("CI") == "true":
+ ws.trades.unsubscribe(id="BTC-USD")
+ ws.close()
+ elif message_dict["type"] in ["channel_data", "channel_batch_data"]:
+ assert message_dict["channel"] == trades_channel_name
+ assert "trades" in message_dict["contents"][0]
+ ws.trades.unsubscribe(id="BTC-USD")
+ ws.close()
+ else:
+ ws.close()
+ assert False, f"Unexpected message: {message_dict}"
+
+ indexer_socket_client.on_message = on_message
+ await indexer_socket_client.connect()
+
+
+@pytest.mark.asyncio
+async def test_markets(indexer_socket_client):
+ markets_channel_name = indexer_socket_client.markets.channel
+
+ def on_message(ws, message):
+ message_dict = json.loads(message)
+ if message_dict["type"] == "connected":
+ ws.markets.subscribe()
+ elif message_dict["type"] == "subscribed":
+ assert message_dict["channel"] == markets_channel_name
+ if os.getenv("CI") == "true":
+ ws.markets.unsubscribe()
+ ws.close()
+ elif message_dict["type"] in ["channel_data", "channel_batch_data"]:
+ assert message_dict["channel"] == markets_channel_name
+ assert "trading" in message_dict["contents"][0]
+ ws.markets.unsubscribe()
+ ws.close()
+ else:
+ ws.close()
+ assert False, f"Unexpected message: {message_dict}"
+
+ indexer_socket_client.on_message = on_message
+ await indexer_socket_client.connect()
+
+
+@pytest.mark.asyncio
+async def test_candles(indexer_socket_client):
+ candles_channel_name = indexer_socket_client.candles.channel
+
+ def on_message(ws, message):
+ message_dict = json.loads(message)
+ if message_dict["type"] == "connected":
+ ws.candles.subscribe(id="BTC-USD", resolution=CandlesResolution.ONE_MINUTE)
+ elif message_dict["type"] == "subscribed":
+ assert message_dict["channel"] == candles_channel_name
+ if os.getenv("CI") == "true":
+ ws.candles.unsubscribe(
+ id="BTC-USD", resolution=CandlesResolution.ONE_MINUTE
+ )
+ ws.close()
+ elif message_dict["type"] in ["channel_data", "channel_batch_data"]:
+ assert message_dict["channel"] == candles_channel_name
+ assert "startedAt" in message_dict["contents"][0]
+ assert "ticker" in message_dict["contents"][0]
+ assert "resolution" in message_dict["contents"][0]
+ ws.candles.unsubscribe(
+ id="BTC-USD", resolution=CandlesResolution.ONE_MINUTE
+ )
+ ws.close()
+ else:
+ ws.close()
+ assert False, f"Unexpected message: {message_dict}"
+
+ indexer_socket_client.on_message = on_message
+ await indexer_socket_client.connect()
+
+
+@pytest.mark.asyncio
+async def test_subaccounts(indexer_socket_client, test_address):
+ subaccounts_channel_name = indexer_socket_client.subaccounts.channel
+
+ def on_message(ws, message):
+ message_dict = json.loads(message)
+ if message_dict["type"] == "connected":
+ ws.subaccounts.subscribe(address=test_address, subaccount_number=0)
+ elif message_dict["type"] == "subscribed":
+ assert message_dict["channel"] == subaccounts_channel_name
+ assert "subaccount" in message_dict["contents"]
+ ws.subaccounts.unsubscribe(address=test_address, subaccount_number=0)
+ ws.close()
+ else:
+ ws.close()
+ assert False, f"Unexpected message: {message_dict}"
+
+ indexer_socket_client.on_message = on_message
+ await indexer_socket_client.connect()
diff --git a/v4-client-py-v2/tests/test_faucet_client.py b/v4-client-py-v2/tests/test_faucet_client.py
new file mode 100644
index 00000000..d9a401a8
--- /dev/null
+++ b/v4-client-py-v2/tests/test_faucet_client.py
@@ -0,0 +1,20 @@
+import httpx
+import pytest
+
+
+@pytest.mark.asyncio
+async def test_fill(test_address, faucet_client):
+ try:
+ response = await faucet_client.fill(test_address, 0, 2000)
+ assert response.status_code == 202
+ except httpx.HTTPStatusError as e:
+ assert e.response.status_code == 429
+
+
+@pytest.mark.asyncio
+async def test_fill_native(test_address, faucet_client):
+ try:
+ response = await faucet_client.fill_native(test_address)
+ assert response.status_code == 202
+ except httpx.HTTPStatusError as e:
+ assert e.response.status_code == 429
diff --git a/v4-client-py-v2/tests/test_mutating_node_client.py b/v4-client-py-v2/tests/test_mutating_node_client.py
new file mode 100644
index 00000000..74f1aef1
--- /dev/null
+++ b/v4-client-py-v2/tests/test_mutating_node_client.py
@@ -0,0 +1,122 @@
+import time
+
+import grpc
+import pytest
+import v4_proto
+
+from dydx_v4_client.node.message import subaccount
+from tests.conftest import get_wallet
+
+REQUEST_PROCESSING_TIME = 5
+
+
+@pytest.fixture(autouse=True)
+def sleep_after_test(request):
+ """
+ Applies 5 seconds sleep to all tests in this file.
+ It gives the testnet the time to process the request.
+ Otherwise tests would throw incorrect sequence errors.
+ """
+ yield
+ time.sleep(REQUEST_PROCESSING_TIME)
+
+
+def is_successful(response):
+ return response.tx_response.code == 0
+
+
+def assert_successful_broadcast(response):
+ assert type(response) == v4_proto.cosmos.tx.v1beta1.service_pb2.BroadcastTxResponse
+ assert is_successful(response)
+
+
+@pytest.mark.asyncio
+async def test_deposit(node_client, test_address, wallet):
+ response = await node_client.deposit(
+ wallet,
+ test_address,
+ subaccount(test_address, 0),
+ asset_id=0,
+ quantums=10000000,
+ )
+ assert_successful_broadcast(response)
+
+
+@pytest.mark.asyncio
+async def test_withdraw(node_client, wallet, test_address):
+ try:
+ response = await node_client.withdraw(
+ wallet,
+ subaccount(test_address, 0),
+ test_address,
+ asset_id=0,
+ quantums=10000000,
+ )
+ assert_successful_broadcast(response)
+ except grpc.RpcError as e:
+ if "StillUndercollateralized" in str(e.details()):
+ pytest.xfail("Subaccount is undercollateralized. Skipping the test.")
+ else:
+ raise e
+
+
+@pytest.mark.asyncio
+async def test_send_token(node_client, wallet, test_address, recipient):
+ response = await node_client.send_token(
+ wallet,
+ test_address,
+ recipient,
+ 10000000,
+ "adv4tnt",
+ )
+ assert_successful_broadcast(response)
+
+
+@pytest.mark.asyncio
+async def test_order(
+ node_client, test_order, test_order_id, test_address, private_key, wallet
+):
+ try:
+ placed = await node_client.place_order(
+ wallet,
+ test_order,
+ )
+ assert_successful_broadcast(placed)
+
+ # If the time is too short the result of cancel order is sequence error:
+ # codespace: "sdk"\n code: 32\n raw_log: "account sequence mismatch, expected 1460, got 1459: incorrect account sequence"
+ # If the time is too long the result is:
+ # codespace: "clob"\n code:...hj67cghhf9jypslcf9sh2n5k6art Number:0} ClientId:13850897 OrderFlags:64 ClobPairId:0}: Stateful order does not exist"
+ time.sleep(2)
+
+ wallet = await get_wallet(node_client, private_key, test_address)
+
+ canceled = await node_client.cancel_order(
+ wallet,
+ test_order_id,
+ good_til_block_time=test_order.good_til_block_time,
+ )
+ assert_successful_broadcast(canceled)
+ except Exception as e:
+ if "StillUndercollateralized" in str(e):
+ pytest.skip("Account is undercollateralized. Skipping the test.")
+ else:
+ raise e
+
+
+@pytest.mark.asyncio
+async def test_transfer(node_client, wallet, test_address, recipient):
+ try:
+ response = await node_client.transfer(
+ wallet,
+ subaccount(test_address, 0),
+ subaccount(recipient, 1),
+ asset_id=0,
+ amount=1,
+ )
+ assert_successful_broadcast(response)
+ except grpc.RpcError as e:
+ if "StillUndercollateralized" in str(e):
+ pytest.skip("Subaccount is undercollateralized. Skipping the test.")
+ else:
+ raise e
diff --git a/v4-client-py-v2/tests/test_query_node_client.py b/v4-client-py-v2/tests/test_query_node_client.py
new file mode 100644
index 00000000..85cf332a
--- /dev/null
+++ b/v4-client-py-v2/tests/test_query_node_client.py
@@ -0,0 +1,176 @@
+import pytest
+from v4_proto.cosmos.auth.v1beta1.auth_pb2 import BaseAccount
+from v4_proto.cosmos.bank.v1beta1 import query_pb2 as bank_query
+from v4_proto.cosmos.base.tendermint.v1beta1.query_pb2 import GetLatestBlockResponse
+from v4_proto.cosmos.base.v1beta1.coin_pb2 import Coin
+from v4_proto.cosmos.staking.v1beta1.query_pb2 import (
+ QueryDelegatorDelegationsResponse,
+ QueryDelegatorUnbondingDelegationsResponse,
+ QueryValidatorsResponse,
+)
+from v4_proto.dydxprotocol.bridge.query_pb2 import (
+ QueryDelayedCompleteBridgeMessagesResponse,
+)
+from v4_proto.dydxprotocol.clob.clob_pair_pb2 import ClobPair
+from v4_proto.dydxprotocol.clob.equity_tier_limit_config_pb2 import (
+ EquityTierLimitConfiguration,
+)
+from v4_proto.dydxprotocol.clob.query_pb2 import QueryClobPairAllResponse
+from v4_proto.dydxprotocol.feetiers.query_pb2 import (
+ QueryPerpetualFeeParamsResponse,
+ QueryUserFeeTierResponse,
+)
+from v4_proto.dydxprotocol.perpetuals.query_pb2 import (
+ QueryAllPerpetualsResponse,
+ QueryPerpetualResponse,
+)
+from v4_proto.dydxprotocol.prices.market_price_pb2 import MarketPrice
+from v4_proto.dydxprotocol.prices.query_pb2 import QueryAllMarketPricesResponse
+from v4_proto.dydxprotocol.rewards.query_pb2 import QueryParamsResponse
+from v4_proto.dydxprotocol.stats.query_pb2 import QueryUserStatsResponse
+from v4_proto.dydxprotocol.subaccounts.query_pb2 import QuerySubaccountAllResponse
+from v4_proto.dydxprotocol.subaccounts.subaccount_pb2 import Subaccount
+
+
+@pytest.mark.asyncio
+async def test_get_account_balances(node_client, test_address):
+ result = await node_client.get_account_balances(test_address)
+ assert type(result) == bank_query.QueryAllBalancesResponse
+
+
+@pytest.mark.asyncio
+async def test_get_account(node_client, test_address):
+ account = await node_client.get_account(test_address)
+ assert isinstance(account, BaseAccount)
+
+
+@pytest.mark.asyncio
+async def test_get_account_balances(node_client, test_address):
+ response = await node_client.get_account_balances(test_address)
+ assert isinstance(response, bank_query.QueryAllBalancesResponse)
+ assert all(isinstance(balance, Coin) for balance in response.balances)
+
+
+@pytest.mark.asyncio
+async def test_get_account_balance(node_client, test_address):
+ response = await node_client.get_account_balance(test_address, "usdc")
+ assert isinstance(response, bank_query.QueryBalanceResponse)
+ assert response.balance.denom == "usdc"
+ assert isinstance(response.balance.amount, str)
+
+
+@pytest.mark.asyncio
+async def test_latest_block(node_client):
+ block = await node_client.latest_block()
+ assert isinstance(block, GetLatestBlockResponse)
+
+
+@pytest.mark.asyncio
+async def test_latest_block_height(node_client):
+ height = await node_client.latest_block_height()
+ assert isinstance(height, int)
+ assert height > 0
+
+
+@pytest.mark.asyncio
+async def test_get_user_stats(node_client, test_address):
+ stats = await node_client.get_user_stats(test_address)
+ assert isinstance(stats, QueryUserStatsResponse)
+
+
+@pytest.mark.asyncio
+async def test_get_all_validators(node_client):
+ validators = await node_client.get_all_validators()
+ assert isinstance(validators, QueryValidatorsResponse)
+
+
+@pytest.mark.asyncio
+async def test_get_subaccount(node_client, test_address):
+ subaccount = await node_client.get_subaccount(test_address, 0)
+ assert isinstance(subaccount, Subaccount)
+
+
+@pytest.mark.asyncio
+async def test_get_subaccounts(node_client):
+ subaccounts = await node_client.get_subaccounts()
+ assert isinstance(subaccounts, QuerySubaccountAllResponse)
+
+
+@pytest.mark.asyncio
+async def test_get_clob_pair(node_client):
+ clob_pair = await node_client.get_clob_pair(1)
+ assert isinstance(clob_pair, ClobPair)
+
+
+@pytest.mark.asyncio
+async def test_get_clob_pairs(node_client):
+ clob_pairs = await node_client.get_clob_pairs()
+ assert isinstance(clob_pairs, QueryClobPairAllResponse)
+
+
+@pytest.mark.asyncio
+async def test_get_price(node_client):
+ price = await node_client.get_price(1)
+ assert isinstance(price, MarketPrice)
+
+
+@pytest.mark.asyncio
+async def test_get_prices(node_client):
+ prices = await node_client.get_prices()
+ assert isinstance(prices, QueryAllMarketPricesResponse)
+
+
+@pytest.mark.asyncio
+async def test_get_perpetual(node_client):
+ perpetual = await node_client.get_perpetual(1)
+ assert isinstance(perpetual, QueryPerpetualResponse)
+
+
+@pytest.mark.asyncio
+async def test_get_perpetuals(node_client):
+ perpetuals = await node_client.get_perpetuals()
+ assert isinstance(perpetuals, QueryAllPerpetualsResponse)
+
+
+@pytest.mark.asyncio
+async def test_get_equity_tier_limit_config(node_client):
+ config = await node_client.get_equity_tier_limit_config()
+ assert isinstance(config, EquityTierLimitConfiguration)
+
+
+@pytest.mark.asyncio
+async def test_get_delegator_delegations(node_client, test_address):
+ delegations = await node_client.get_delegator_delegations(test_address)
+ assert isinstance(delegations, QueryDelegatorDelegationsResponse)
+
+
+@pytest.mark.asyncio
+async def test_get_delegator_unbonding_delegations(node_client, test_address):
+ unbonding_delegations = await node_client.get_delegator_unbonding_delegations(
+ test_address
+ )
+ assert isinstance(unbonding_delegations, QueryDelegatorUnbondingDelegationsResponse)
+
+
+@pytest.mark.asyncio
+async def test_get_delayed_complete_bridge_messages(node_client):
+ bridge_messages = await node_client.get_delayed_complete_bridge_messages()
+ assert isinstance(bridge_messages, QueryDelayedCompleteBridgeMessagesResponse)
+
+
+@pytest.mark.asyncio
+async def test_get_fee_tiers(node_client):
+ fee_tiers = await node_client.get_fee_tiers()
+ assert isinstance(fee_tiers, QueryPerpetualFeeParamsResponse)
+
+
+@pytest.mark.asyncio
+async def test_get_user_fee_tier(node_client, test_address):
+ user_fee_tier = await node_client.get_user_fee_tier(test_address)
+ assert isinstance(user_fee_tier, QueryUserFeeTierResponse)
+
+
+@pytest.mark.asyncio
+async def test_get_rewards_params(node_client):
+ rewards_params = await node_client.get_rewards_params()
+ assert isinstance(rewards_params, QueryParamsResponse)
diff --git a/v4-client-py-v2/tests/test_v4_proto.py b/v4-client-py-v2/tests/test_v4_proto.py
new file mode 100644
index 00000000..362378a0
--- /dev/null
+++ b/v4-client-py-v2/tests/test_v4_proto.py
@@ -0,0 +1,91 @@
+from google.protobuf.message import Message
+
+from dydx_v4_client.node.message import (
+ cancel_order,
+ deposit,
+ order,
+ order_id,
+ place_order,
+ send_token,
+ subaccount,
+ transfer,
+ withdraw,
+)
+from tests.conftest import TEST_ADDRESS
+
+SERIALIZED_PLACE_ORDER = b"\nF\n1\n-\n+dydx14zzueazeh0hj67cghhf9jypslcf9sh2n5k6art\x18@\x10\x01\x18\x80\xad\xe2\x04 \x80\xa0\xbe\x81\x95\x015\t\x9cYf"
+SERIALIZED_CANCEL_ORDER = (
+ b"\n1\n-\n+dydx14zzueazeh0hj67cghhf9jypslcf9sh2n5k6art\x18@\x1d\t\x9cYf"
+)
+SERIALIZED_DEPOSIT = b"\n+dydx14zzueazeh0hj67cghhf9jypslcf9sh2n5k6art\x12-\n+dydx14zzueazeh0hj67cghhf9jypslcf9sh2n5k6art \x80\xad\xe2\x04"
+SERIALIZED_WITHDRAW = b"\n+dydx14zzueazeh0hj67cghhf9jypslcf9sh2n5k6art\x12-\n+dydx14zzueazeh0hj67cghhf9jypslcf9sh2n5k6art \x80\xad\xe2\x04"
+SERIALIZED_SEND_TOKEN = b"\n+dydx14zzueazeh0hj67cghhf9jypslcf9sh2n5k6art\x12+dydx1slanxj8x9ntk9knwa6cvfv2tzlsq5gk3dshml0\x1a\x13\n\x07adv4tnt\x12\x0810000000"
+SERIALIZED_TRANSFER = (
+ b"\nb\n-\n+dydx14zzueazeh0hj67cghhf9jypslcf9sh2n5k6art\x12/\n+dydx1slanxj8x9n"
+ b"tk9knwa6cvfv2tzlsq5gk3dshml0\x10\x01 \x01"
+)
+
+
+def assert_serializes_properly(message: Message, expected: str):
+ assert message.SerializeToString() == expected
+
+
+ORDER_ID = order_id(
+ TEST_ADDRESS,
+ subaccount_number=0,
+ client_id=0,
+ clob_pair_id=0,
+ order_flags=64,
+)
+GOOD_TIL_BLOCK_TIME = 1717148681
+
+
+def test_place_order_serialization(test_address):
+ test_order = order(
+ ORDER_ID,
+ time_in_force=0,
+ reduce_only=False,
+ side=1,
+ quantums=10000000,
+ subticks=40000000000,
+ good_til_block_time=GOOD_TIL_BLOCK_TIME,
+ )
+ assert_serializes_properly(place_order(test_order), SERIALIZED_PLACE_ORDER)
+
+
+def test_cancel_order_serialization():
+ assert_serializes_properly(
+ cancel_order(ORDER_ID, good_til_block_time=GOOD_TIL_BLOCK_TIME),
+ SERIALIZED_CANCEL_ORDER,
+ )
+
+
+def test_deposit_serialization(test_address):
+ assert_serializes_properly(
+ deposit(test_address, subaccount(test_address, 0), 0, 10_000_000),
+ SERIALIZED_DEPOSIT,
+ )
+
+
+def test_withdraw_serialization(test_address):
+ assert_serializes_properly(
+ withdraw(
+ subaccount(test_address, 0), test_address, asset_id=0, quantums=10000000
+ ),
+ SERIALIZED_WITHDRAW,
+ )
+
+
+def test_send_token_serialization(test_address, recipient):
+ assert_serializes_properly(
+ send_token(test_address, recipient, 10000000, "adv4tnt"), SERIALIZED_SEND_TOKEN
+ )
+
+
+def test_transfer_serialization(test_address, recipient):
+ assert_serializes_properly(
+ transfer(
+ subaccount(test_address, 0), subaccount(recipient, 1), asset_id=0, amount=1
+ ),
+ SERIALIZED_TRANSFER,
+ )
From 11e270f532982211dd2aa8239562c2a754d9890f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Piotr=20Piwo=C5=84ski?=
Date: Wed, 19 Jun 2024 17:37:01 +0200
Subject: [PATCH 3/7] Make network configs configurable
---
v4-client-py-v2/README.md | 44 +++++++++---
v4-client-py-v2/dydx_v4_client/network.py | 69 +++++++++++--------
v4-client-py-v2/dydx_v4_client/node/client.py | 14 +---
3 files changed, 77 insertions(+), 50 deletions(-)
diff --git a/v4-client-py-v2/README.md b/v4-client-py-v2/README.md
index 17907035..607d39a5 100644
--- a/v4-client-py-v2/README.md
+++ b/v4-client-py-v2/README.md
@@ -40,9 +40,10 @@ https://github.com/NethermindEth/dydx-v4-client/blob/f8be7bf9165fb052e831fcafb80
**Note:** It's possible to create a read only node client which doesn't allow to send transactions:
```python
from dydx_v4_client import QueryNodeClient
+from dydx_v4_client.network import secure_channel
-node = await QueryNodeClient.connect("https://dydx-ops-rpc.kingnodes.com:443")
+node = await QueryNodeClient(secure_channel("https://dydx-ops-rpc.kingnodes.com:443"))
```
### REST Indexer
@@ -67,25 +68,48 @@ Websocket indexer allows to subscribe to channels to obtain live updates:
https://github.com/NethermindEth/dydx-v4-client/blob/18eb769dde2a8691fc13445a34f46f0ecb266ec8/examples/websocket_example.py#L1-L24
### Networks
-A set of predefined networks may be imported:
+To connect to the mainnet you can use `make_mainnet` function:
+```python
+from dydx_v4_client.network import make_mainnet
+
+
+NETWORK = make_mainnet(
+ node_url=NODE_URL,
+ rest_indexer=REST_URL,
+ websocket_indexer=WEBSOCKET_URL
+)
+```
+
+For local and testnet networks there is a set of predefined networks:
```python
-from dydx_v4_client.network import TESTNET, MAINNET, LOCAL
+from dydx_v4_client.network import TESTNET, LOCAL
```
-If you want to use a custom API you can create a network directly:
+If you want to use a custom API each network has its respective _make_ function:
```python
-from dydx_v4_client.network import Network
+from dydx_v4_client.network import make_testnet, make_local
+```
+
+You can overwrite the default URL when calling the function:
+```python
+NETWORK = make_local(node_url="http://localhost:26657")
+```
+
+To create a custom network you can do it directly:
+```python
+from dydx_v4_client.network import Network, NodeConfig, secure_channel
+
CUSTOM_MAINNET = Network(
+ "https://dydx-testnet.imperator.co",
+ "wss://indexer.v4testnet.dydx.exchange/v4/ws",
NodeConfig(
- "dydx-mainnet-1",
- "https://dydx-ops-rpc.kingnodes.com:443",
- "adydx",
+ "dydx-testnet-4",
+ secure_channel("test-dydx-grpc.kingnodes.com"),
+ "adv4tnt",
"ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
),
- "https://indexer.dydx.trade",
- "wss://indexer.dydx.trade/v4/ws",
)
```
Or provide the URL directly to the client, e.g.:
diff --git a/v4-client-py-v2/dydx_v4_client/network.py b/v4-client-py-v2/dydx_v4_client/network.py
index 2e169bfc..398803ee 100644
--- a/v4-client-py-v2/dydx_v4_client/network.py
+++ b/v4-client-py-v2/dydx_v4_client/network.py
@@ -1,52 +1,63 @@
from dataclasses import dataclass
+from functools import partial
+
+import grpc
+from grpc import insecure_channel
+
+
+secure_channel = partial(
+ grpc.secure_channel, credentials=grpc.ssl_channel_credentials()
+)
@dataclass
class NodeConfig:
chain_id: str
- url: str
chaintoken_denom: str
usdc_denom: str
+ channel: grpc.Channel
@dataclass
class Network:
- node: NodeConfig
rest_indexer: str
websocket_indexer: str
+ node: NodeConfig
-MAINNET = Network(
- NodeConfig(
- "dydx-mainnet-1",
- "https://dydx-ops-rpc.kingnodes.com:443",
- "adydx",
- "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
- ),
- "https://indexer.dydx.trade",
- "wss://indexer.dydx.trade/v4/ws",
-)
+def make_config(make_channel, make_node, rest_indexer: str, websocket_indexer: str, node_url: str):
+ return Network(
+ rest_indexer,
+ websocket_indexer,
+ make_node(channel=make_channel(node_url)),
+ )
+make_secure = partial(make_config, secure_channel)
+make_insecure = partial(make_config, insecure_channel)
+
+
+mainnet_node = partial(NodeConfig, "dydx-mainnet-1", chaintoken_denom="adydx", usdc_denom="ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5")
+make_mainnet = partial(make_secure, mainnet_node)
-TESTNET = Network(
- NodeConfig(
- "dydx-testnet-4",
- "test-dydx-grpc.kingnodes.com",
- "adv4tnt",
- "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
- ),
- "https://dydx-testnet.imperator.co",
- "wss://indexer.v4testnet.dydx.exchange/v4/ws",
+
+testnet_node = partial(NodeConfig,
+ "dydx-testnet-4",
+ chaintoken_denom="adv4tnt",
+ usdc_denom="ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
)
+make_testnet = partial(make_secure, testnet_node, rest_indexer="https://dydx-testnet.imperator.co", websocket_indexer="wss://indexer.v4testnet.dydx.exchange/v4/ws", node_url="test-dydx-grpc.kingnodes.com")
+TESTNET = make_testnet()
TESTNET_FAUCET = "https://faucet.v4testnet.dydx.exchange"
TESTNET_NOBLE = "https://rpc.testnet.noble.strange.love"
-LOCAL = Network(
- NodeConfig(
+
+local_node = partial(
+ NodeConfig,
"localdydxprotocol",
- "http://localhost:26657",
- "adv4tnt",
- "ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
- ),
- "http://localhost:3002",
- "ws://localhost:3003",
+ chaintoken_denom="adv4tnt",
+ usdc_denom="ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
)
+make_local = partial(make_insecure, local_node, rest_indexer=
+ "http://localhost:3002", websocket_indexer=
+ "ws://localhost:3003", node_url=
+ "http://localhost:9090")
+LOCAL = make_local()
diff --git a/v4-client-py-v2/dydx_v4_client/node/client.py b/v4-client-py-v2/dydx_v4_client/node/client.py
index 5625e6a7..84b253fd 100644
--- a/v4-client-py-v2/dydx_v4_client/node/client.py
+++ b/v4-client-py-v2/dydx_v4_client/node/client.py
@@ -1,5 +1,4 @@
-from dataclasses import dataclass, field
-from functools import partial
+from dataclasses import dataclass
from typing import List, Optional, Self
import grpc
@@ -77,17 +76,10 @@
)
from dydx_v4_client.wallet import Wallet
-secure_channel = partial(
- grpc.secure_channel, credentials=grpc.ssl_channel_credentials()
-)
-
@dataclass
class QueryNodeClient:
- channel: grpc.Channel = field()
-
- async def connect(url: str) -> Self:
- return QueryNodeClient(secure_channel(url))
+ channel: grpc.Channel
async def get_account_balances(
self, address: str
@@ -532,7 +524,7 @@ class NodeClient(MutatingNodeClient):
@staticmethod
async def connect(config: NodeConfig) -> Self:
return NodeClient(
- secure_channel(config.url), Builder(config.chain_id, config.usdc_denom)
+ config.channel, Builder(config.chain_id, config.usdc_denom)
)
async def deposit(
From bb8999d47868f2495117a236effdccbba5253319 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Piotr=20Piwo=C5=84ski?=
Date: Wed, 19 Jun 2024 17:48:07 +0200
Subject: [PATCH 4/7] Reformat
---
v4-client-py-v2/dydx_v4_client/network.py | 42 +++++++++++++------
v4-client-py-v2/dydx_v4_client/node/client.py | 4 +-
2 files changed, 31 insertions(+), 15 deletions(-)
diff --git a/v4-client-py-v2/dydx_v4_client/network.py b/v4-client-py-v2/dydx_v4_client/network.py
index 398803ee..e0ae9266 100644
--- a/v4-client-py-v2/dydx_v4_client/network.py
+++ b/v4-client-py-v2/dydx_v4_client/network.py
@@ -4,7 +4,6 @@
import grpc
from grpc import insecure_channel
-
secure_channel = partial(
grpc.secure_channel, credentials=grpc.ssl_channel_credentials()
)
@@ -25,26 +24,42 @@ class Network:
node: NodeConfig
-def make_config(make_channel, make_node, rest_indexer: str, websocket_indexer: str, node_url: str):
+def make_config(
+ make_channel, make_node, rest_indexer: str, websocket_indexer: str, node_url: str
+):
return Network(
rest_indexer,
websocket_indexer,
make_node(channel=make_channel(node_url)),
)
+
+
make_secure = partial(make_config, secure_channel)
make_insecure = partial(make_config, insecure_channel)
-mainnet_node = partial(NodeConfig, "dydx-mainnet-1", chaintoken_denom="adydx", usdc_denom="ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5")
+mainnet_node = partial(
+ NodeConfig,
+ "dydx-mainnet-1",
+ chaintoken_denom="adydx",
+ usdc_denom="ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
+)
make_mainnet = partial(make_secure, mainnet_node)
-testnet_node = partial(NodeConfig,
+testnet_node = partial(
+ NodeConfig,
"dydx-testnet-4",
chaintoken_denom="adv4tnt",
usdc_denom="ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
)
-make_testnet = partial(make_secure, testnet_node, rest_indexer="https://dydx-testnet.imperator.co", websocket_indexer="wss://indexer.v4testnet.dydx.exchange/v4/ws", node_url="test-dydx-grpc.kingnodes.com")
+make_testnet = partial(
+ make_secure,
+ testnet_node,
+ rest_indexer="https://dydx-testnet.imperator.co",
+ websocket_indexer="wss://indexer.v4testnet.dydx.exchange/v4/ws",
+ node_url="test-dydx-grpc.kingnodes.com",
+)
TESTNET = make_testnet()
TESTNET_FAUCET = "https://faucet.v4testnet.dydx.exchange"
TESTNET_NOBLE = "https://rpc.testnet.noble.strange.love"
@@ -52,12 +67,15 @@ def make_config(make_channel, make_node, rest_indexer: str, websocket_indexer:
local_node = partial(
NodeConfig,
- "localdydxprotocol",
- chaintoken_denom="adv4tnt",
- usdc_denom="ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
+ "localdydxprotocol",
+ chaintoken_denom="adv4tnt",
+ usdc_denom="ibc/8E27BA2D5493AF5636760E354E46004562C46AB7EC0CC4C1CA14E9E20E2545B5",
+)
+make_local = partial(
+ make_insecure,
+ local_node,
+ rest_indexer="http://localhost:3002",
+ websocket_indexer="ws://localhost:3003",
+ node_url="http://localhost:9090",
)
-make_local = partial(make_insecure, local_node, rest_indexer=
- "http://localhost:3002", websocket_indexer=
- "ws://localhost:3003", node_url=
- "http://localhost:9090")
LOCAL = make_local()
diff --git a/v4-client-py-v2/dydx_v4_client/node/client.py b/v4-client-py-v2/dydx_v4_client/node/client.py
index 84b253fd..791596a4 100644
--- a/v4-client-py-v2/dydx_v4_client/node/client.py
+++ b/v4-client-py-v2/dydx_v4_client/node/client.py
@@ -523,9 +523,7 @@ def calculate_fee(self, gas_used) -> Fee:
class NodeClient(MutatingNodeClient):
@staticmethod
async def connect(config: NodeConfig) -> Self:
- return NodeClient(
- config.channel, Builder(config.chain_id, config.usdc_denom)
- )
+ return NodeClient(config.channel, Builder(config.chain_id, config.usdc_denom))
async def deposit(
self,
From f039f94c03a38041b344a9f9855db45184cf58ee Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Piotr=20Piwo=C5=84ski?=
Date: Wed, 19 Jun 2024 17:51:04 +0200
Subject: [PATCH 5/7] Add markets endpoints example
---
v4-client-py-v2/examples/markets_endpoints.py | 130 ++++++++++++++++++
1 file changed, 130 insertions(+)
create mode 100644 v4-client-py-v2/examples/markets_endpoints.py
diff --git a/v4-client-py-v2/examples/markets_endpoints.py b/v4-client-py-v2/examples/markets_endpoints.py
new file mode 100644
index 00000000..2e4ca8b8
--- /dev/null
+++ b/v4-client-py-v2/examples/markets_endpoints.py
@@ -0,0 +1,130 @@
+import asyncio
+
+from dydx_v4_client.indexer.rest.indexer_client import IndexerClient
+from dydx_v4_client.network import TESTNET
+
+# ------------ Markets ------------
+MARKET_BTC_USD = "BTC-USD"
+
+
+async def test():
+ client = IndexerClient(TESTNET.rest_indexer)
+
+ # Get perp markets
+ try:
+ response = await client.markets.get_perpetual_markets()
+ print(response)
+ print("markets")
+ btc_market = response["markets"][MARKET_BTC_USD]
+ btc_market_status = btc_market["status"]
+ print(btc_market_status)
+ except Exception as e:
+ print(e)
+
+ try:
+ response = await client.markets.get_perpetual_markets(market=MARKET_BTC_USD)
+ print(response)
+ print("markets")
+ btc_market = response["markets"][MARKET_BTC_USD]
+ btc_market_status = btc_market["status"]
+ print(btc_market_status)
+ except Exception as e:
+ print(e)
+
+ # Get sparklines
+ try:
+ response = await client.markets.get_perpetual_market_sparklines()
+ print(response)
+ print("sparklines")
+ btc_sparklines = response[MARKET_BTC_USD]
+ print(btc_sparklines)
+ except Exception as e:
+ print(e)
+
+ # Get perp market trades
+ try:
+ response = await client.markets.get_perpetual_market_trades(
+ market=MARKET_BTC_USD
+ )
+ print(response)
+ print("trades")
+ trades = response["trades"]
+ print(trades)
+ except Exception as e:
+ print(e)
+
+ # Get perp market orderbook
+ try:
+ response = await client.markets.get_perpetual_market_orderbook(
+ market=MARKET_BTC_USD
+ )
+ print(response)
+ print("orderbook")
+ asks = response["asks"]
+ bids = response["bids"]
+ if asks:
+ asks0 = asks[0]
+ asks0_price = asks0["price"]
+ asks0_size = asks0["size"]
+ print(asks0_price)
+ print(asks0_size)
+ if bids:
+ bids0 = bids[0]
+ bids0_price = bids0["price"]
+ bids0_size = bids0["size"]
+ print(bids0_price)
+ print(bids0_size)
+ trades = response["trades"]
+ print(trades)
+ except Exception as e:
+ print(e)
+
+ # Get perp market candles
+ try:
+ response = await client.markets.get_perpetual_market_candles(
+ market=MARKET_BTC_USD, resolution="1MIN"
+ )
+ print(response)
+ print("candles")
+ candles = response["candles"]
+ if candles:
+ candles0 = candles[0]
+ started_at = candles0["startedAt"]
+ low = candles0["low"]
+ high = candles0["high"]
+ open_ = candles0["open"]
+ close = candles0["close"]
+ base_token_volume = candles0["baseTokenVolume"]
+ usd_volume = candles0["usdVolume"]
+ trades = candles0["trades"]
+ print(started_at)
+ print(low)
+ print(high)
+ print(open_)
+ print(close)
+ print(base_token_volume)
+ print(usd_volume)
+ print(trades)
+ except Exception as e:
+ print(e)
+
+ # Get perp market historical funding rates
+ try:
+ response = await client.markets.get_perpetual_market_historical_funding(
+ market=MARKET_BTC_USD
+ )
+ print(response)
+ print("historical funding")
+ historical_funding = response["historicalFunding"]
+ if historical_funding:
+ historical_funding0 = historical_funding[0]
+ print(historical_funding0)
+ except Exception as e:
+ print(e)
+
+
+async def main():
+ await test()
+
+
+asyncio.run(main())
From acd63021fe123b53180b4259927dd9ee807cd2d2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Piotr=20Piwo=C5=84ski?=
Date: Wed, 19 Jun 2024 18:33:33 +0200
Subject: [PATCH 6/7] Replace mainnet urls with testnet
---
v4-client-py-v2/README.md | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/v4-client-py-v2/README.md b/v4-client-py-v2/README.md
index 607d39a5..a393d370 100644
--- a/v4-client-py-v2/README.md
+++ b/v4-client-py-v2/README.md
@@ -43,7 +43,7 @@ from dydx_v4_client import QueryNodeClient
from dydx_v4_client.network import secure_channel
-node = await QueryNodeClient(secure_channel("https://dydx-ops-rpc.kingnodes.com:443"))
+node = await QueryNodeClient(secure_channel("test-dydx-grpc.kingnodes.com"))
```
### REST Indexer
@@ -114,7 +114,7 @@ CUSTOM_MAINNET = Network(
```
Or provide the URL directly to the client, e.g.:
```python
-indexer = IndexerClient("https://indexer.dydx.trade")
+indexer = IndexerClient("https://dydx-testnet.imperator.co")
```
### Faucet
Faucet allows to obtain usdc on testnet. To use it create `FaucetClient`:
@@ -198,8 +198,7 @@ Methods are available directly, no methods `get` or `post` needed, since the cli
For parameters raw types used.
-For construcint order the `Market` builder is provided, that
-helps to calculate quantums and subticks values.
+For construcint order the `Market` builder is provided, that helps to calculate quantums and subticks values.
#### IndexerClient
From f5f087c2084acae810e6b911acd29e58ae7dabc1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Piotr=20Piwo=C5=84ski?=
Date: Wed, 19 Jun 2024 18:52:26 +0200
Subject: [PATCH 7/7] Add the link to dydx network resources
---
v4-client-py-v2/README.md | 3 +++
1 file changed, 3 insertions(+)
diff --git a/v4-client-py-v2/README.md b/v4-client-py-v2/README.md
index a393d370..f0371c29 100644
--- a/v4-client-py-v2/README.md
+++ b/v4-client-py-v2/README.md
@@ -68,6 +68,9 @@ Websocket indexer allows to subscribe to channels to obtain live updates:
https://github.com/NethermindEth/dydx-v4-client/blob/18eb769dde2a8691fc13445a34f46f0ecb266ec8/examples/websocket_example.py#L1-L24
### Networks
+
+> **See [network resources](https://docs.dydx.exchange/infrastructure_providers-network/resources#networks-repositories) to find publicly available endpoints**
+
To connect to the mainnet you can use `make_mainnet` function:
```python
from dydx_v4_client.network import make_mainnet