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