From c5f6797adce50a317f09f960d1fe268205df3e48 Mon Sep 17 00:00:00 2001 From: zarathustra Date: Thu, 18 Sep 2025 15:07:46 +0200 Subject: [PATCH 01/21] feat: create and update docs dependency group in pyproject.toml --- poetry.lock | 306 +++++++++++++++++++++++++++++++++++++------------ pyproject.toml | 10 +- 2 files changed, 240 insertions(+), 76 deletions(-) diff --git a/poetry.lock b/poetry.lock index 66182f77..e6b2ba29 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.1 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -167,7 +167,7 @@ description = "Timeout context manager for asyncio programs" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version == \"3.10\"" +markers = "python_version < \"3.11\"" files = [ {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, @@ -193,6 +193,64 @@ docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphi tests = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] tests-mypy = ["mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\""] +[[package]] +name = "babel" +version = "2.17.0" +description = "Internationalization utilities" +optional = false +python-versions = ">=3.8" +groups = ["docs"] +files = [ + {file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"}, + {file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"}, +] + +[package.extras] +dev = ["backports.zoneinfo ; python_version < \"3.9\"", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata ; sys_platform == \"win32\""] + +[[package]] +name = "backrefs" +version = "5.9" +description = "A wrapper around re and regex that adds additional back references." +optional = false +python-versions = ">=3.9" +groups = ["docs"] +files = [ + {file = "backrefs-5.9-py310-none-any.whl", hash = "sha256:db8e8ba0e9de81fcd635f440deab5ae5f2591b54ac1ebe0550a2ca063488cd9f"}, + {file = "backrefs-5.9-py311-none-any.whl", hash = "sha256:6907635edebbe9b2dc3de3a2befff44d74f30a4562adbb8b36f21252ea19c5cf"}, + {file = "backrefs-5.9-py312-none-any.whl", hash = "sha256:7fdf9771f63e6028d7fee7e0c497c81abda597ea45d6b8f89e8ad76994f5befa"}, + {file = "backrefs-5.9-py313-none-any.whl", hash = "sha256:cc37b19fa219e93ff825ed1fed8879e47b4d89aa7a1884860e2db64ccd7c676b"}, + {file = "backrefs-5.9-py314-none-any.whl", hash = "sha256:df5e169836cc8acb5e440ebae9aad4bf9d15e226d3bad049cf3f6a5c20cc8dc9"}, + {file = "backrefs-5.9-py39-none-any.whl", hash = "sha256:f48ee18f6252b8f5777a22a00a09a85de0ca931658f1dd96d4406a34f3748c60"}, + {file = "backrefs-5.9.tar.gz", hash = "sha256:808548cb708d66b82ee231f962cb36faaf4f2baab032f2fbb783e9c2fdddaa59"}, +] + +[package.extras] +extras = ["regex"] + +[[package]] +name = "beautifulsoup4" +version = "4.13.5" +description = "Screen-scraping library" +optional = false +python-versions = ">=3.7.0" +groups = ["docs"] +files = [ + {file = "beautifulsoup4-4.13.5-py3-none-any.whl", hash = "sha256:642085eaa22233aceadff9c69651bc51e8bf3f874fb6d7104ece2beb24b47c4a"}, + {file = "beautifulsoup4-4.13.5.tar.gz", hash = "sha256:5e70131382930e7c3de33450a2f54a63d5e4b19386eab43a5b34d594268f3695"}, +] + +[package.dependencies] +soupsieve = ">1.2" +typing-extensions = ">=4.0.0" + +[package.extras] +cchardet = ["cchardet"] +chardet = ["chardet"] +charset-normalizer = ["charset-normalizer"] +html5lib = ["html5lib"] +lxml = ["lxml"] + [[package]] name = "bitarray" version = "3.4.3" @@ -384,13 +442,25 @@ d = ["aiohttp (>=3.10)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] +[[package]] +name = "bracex" +version = "2.6" +description = "Bash style brace expander." +optional = false +python-versions = ">=3.9" +groups = ["docs"] +files = [ + {file = "bracex-2.6-py3-none-any.whl", hash = "sha256:0b0049264e7340b3ec782b5cb99beb325f36c3782a32e36e876452fd49a09952"}, + {file = "bracex-2.6.tar.gz", hash = "sha256:98f1347cd77e22ee8d967a30ad4e310b233f7754dbf31ff3fceb76145ba47dc7"}, +] + [[package]] name = "certifi" version = "2025.6.15" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.7" -groups = ["main", "dev"] +groups = ["main", "docs"] files = [ {file = "certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057"}, {file = "certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b"}, @@ -402,7 +472,7 @@ version = "3.4.2" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7" -groups = ["main", "dev"] +groups = ["main", "docs"] files = [ {file = "charset_normalizer-3.4.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7c48ed483eb946e6c04ccbe02c6b4d1d48e51944b6db70f697e089c193404941"}, {file = "charset_normalizer-3.4.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2d318c11350e10662026ad0eb71bb51c7812fc8590825304ae0bdd4ac283acd"}, @@ -631,7 +701,7 @@ version = "8.1.8" description = "Composable command line interface toolkit" optional = false python-versions = ">=3.7" -groups = ["main", "dev"] +groups = ["main", "dev", "docs"] files = [ {file = "click-8.1.8-py3-none-any.whl", hash = "sha256:63c132bbbed01578a06712a2d1f497bb62d9c1c0d329b7903a866228027263b2"}, {file = "click-8.1.8.tar.gz", hash = "sha256:ed53c9d8990d83c2a27deae68e4ee337473f6330c040a31d4225c9574d16096a"}, @@ -646,7 +716,7 @@ 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" -groups = ["main", "dev"] +groups = ["main", "dev", "docs"] files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, @@ -801,6 +871,21 @@ files = [ {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, ] +[[package]] +name = "editorconfig" +version = "0.17.1" +description = "EditorConfig File Locator and Interpreter for Python" +optional = false +python-versions = ">=3.9" +groups = ["docs"] +files = [ + {file = "editorconfig-0.17.1-py3-none-any.whl", hash = "sha256:1eda9c2c0db8c16dbd50111b710572a5e6de934e39772de1959d41f64fc17c82"}, + {file = "editorconfig-0.17.1.tar.gz", hash = "sha256:23c08b00e8e08cc3adcddb825251c497478df1dada6aefeb01e626ad37303745"}, +] + +[package.extras] +dev = ["mypy (>=1.15)"] + [[package]] name = "eth-abi" version = "5.2.0" @@ -989,7 +1074,7 @@ description = "Backport of PEP 654 (exception groups)" optional = false python-versions = ">=3.7" groups = ["dev"] -markers = "python_version == \"3.10\"" +markers = "python_version < \"3.11\"" files = [ {file = "exceptiongroup-1.3.0-py3-none-any.whl", hash = "sha256:4d111e6e0c13d0644cad6ddaa7ed0261a0b36971f6d23e7ec9b4b9097da78a10"}, {file = "exceptiongroup-1.3.0.tar.gz", hash = "sha256:b241f5885f560bc56a59ee63ca4c6a8bfa46ae4ad651af316d4e81817bb9fd88"}, @@ -1138,7 +1223,7 @@ version = "2.1.0" description = "Copy your docs directly to the gh-pages branch." optional = false python-versions = "*" -groups = ["dev"] +groups = ["docs"] files = [ {file = "ghp-import-2.1.0.tar.gz", hash = "sha256:9c535c4c61193c2df8871222567d7fd7e5014d835f97dc7b7439069e2413d343"}, {file = "ghp_import-2.1.0-py3-none-any.whl", hash = "sha256:8337dd7b50877f163d4c0289bc1f1c7f127550241988d568c1db512c4324a619"}, @@ -1173,7 +1258,7 @@ version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false python-versions = ">=3.6" -groups = ["main", "dev"] +groups = ["main", "docs"] files = [ {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, @@ -1215,7 +1300,7 @@ version = "3.1.6" description = "A very fast and expressive template engine." optional = false python-versions = ">=3.7" -groups = ["dev"] +groups = ["docs"] files = [ {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, @@ -1227,6 +1312,22 @@ MarkupSafe = ">=2.0" [package.extras] i18n = ["Babel (>=2.7)"] +[[package]] +name = "jsbeautifier" +version = "1.15.4" +description = "JavaScript unobfuscator and beautifier." +optional = false +python-versions = "*" +groups = ["docs"] +files = [ + {file = "jsbeautifier-1.15.4-py3-none-any.whl", hash = "sha256:72f65de312a3f10900d7685557f84cb61a9733c50dcc27271a39f5b0051bf528"}, + {file = "jsbeautifier-1.15.4.tar.gz", hash = "sha256:5bb18d9efb9331d825735fbc5360ee8f1aac5e52780042803943aa7f854f7592"}, +] + +[package.dependencies] +editorconfig = ">=0.12.2" +six = ">=1.13.0" + [[package]] name = "jsonschema" version = "4.24.0" @@ -1364,7 +1465,7 @@ version = "3.8.2" description = "Python implementation of John Gruber's Markdown." optional = false python-versions = ">=3.9" -groups = ["dev"] +groups = ["docs"] files = [ {file = "markdown-3.8.2-py3-none-any.whl", hash = "sha256:5c83764dbd4e00bdd94d85a19b8d55ccca20fe35b2e678a1422b380324dd5f24"}, {file = "markdown-3.8.2.tar.gz", hash = "sha256:247b9a70dd12e27f67431ce62523e675b866d254f900c4fe75ce3dda62237c45"}, @@ -1405,7 +1506,7 @@ version = "3.0.2" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.9" -groups = ["dev"] +groups = ["docs"] files = [ {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:7e94c425039cde14257288fd61dcfb01963e658efbc0ff54f5306b06054700f8"}, {file = "MarkupSafe-3.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9e2d922824181480953426608b81967de705c3cef4d1af983af849d7bd619158"}, @@ -1500,7 +1601,7 @@ version = "1.3.4" description = "A deep merge function for 🐍." optional = false python-versions = ">=3.6" -groups = ["dev"] +groups = ["docs"] files = [ {file = "mergedeep-1.3.4-py3-none-any.whl", hash = "sha256:70775750742b25c0d8f36c55aed03d24c3384d17c951b3175d898bd778ef0307"}, {file = "mergedeep-1.3.4.tar.gz", hash = "sha256:0096d52e9dad9939c3d975a774666af186eda617e6ca84df4c94dec30004f2a8"}, @@ -1512,7 +1613,7 @@ version = "1.6.1" description = "Project documentation with Markdown." optional = false python-versions = ">=3.8" -groups = ["dev"] +groups = ["docs"] files = [ {file = "mkdocs-1.6.1-py3-none-any.whl", hash = "sha256:db91759624d1647f3f34aa0c3f327dd2601beae39a366d6e064c03468d35c20e"}, {file = "mkdocs-1.6.1.tar.gz", hash = "sha256:7b432f01d928c084353ab39c57282f29f92136665bdd6abf7c1ec8d822ef86f2"}, @@ -1537,29 +1638,13 @@ watchdog = ">=2.0" i18n = ["babel (>=2.9.0)"] min-versions = ["babel (==2.9.0)", "click (==7.0)", "colorama (==0.4) ; platform_system == \"Windows\"", "ghp-import (==1.0)", "importlib-metadata (==4.4) ; python_version < \"3.10\"", "jinja2 (==2.11.1)", "markdown (==3.3.6)", "markupsafe (==2.0.1)", "mergedeep (==1.3.4)", "mkdocs-get-deps (==0.2.0)", "packaging (==20.5)", "pathspec (==0.11.1)", "pyyaml (==5.1)", "pyyaml-env-tag (==0.1)", "watchdog (==2.0)"] -[[package]] -name = "mkdocs-autorefs" -version = "0.4.1" -description = "Automatically link across pages in MkDocs." -optional = false -python-versions = ">=3.7" -groups = ["dev"] -files = [ - {file = "mkdocs-autorefs-0.4.1.tar.gz", hash = "sha256:70748a7bd025f9ecd6d6feeba8ba63f8e891a1af55f48e366d6d6e78493aba84"}, - {file = "mkdocs_autorefs-0.4.1-py3-none-any.whl", hash = "sha256:a2248a9501b29dc0cc8ba4c09f4f47ff121945f6ce33d760f145d6f89d313f5b"}, -] - -[package.dependencies] -Markdown = ">=3.3" -mkdocs = ">=1.1" - [[package]] name = "mkdocs-get-deps" version = "0.2.0" description = "MkDocs extension that lists all dependencies according to a mkdocs.yml file" optional = false python-versions = ">=3.8" -groups = ["dev"] +groups = ["docs"] files = [ {file = "mkdocs_get_deps-0.2.0-py3-none-any.whl", hash = "sha256:2bf11d0b133e77a0dd036abeeb06dec8775e46efa526dc70667d8863eefc6134"}, {file = "mkdocs_get_deps-0.2.0.tar.gz", hash = "sha256:162b3d129c7fad9b19abfdcb9c1458a651628e4b1dea628ac68790fb3061c60c"}, @@ -1572,40 +1657,53 @@ pyyaml = ">=5.1" [[package]] name = "mkdocs-include-markdown-plugin" -version = "3.9.1" +version = "7.1.7" description = "Mkdocs Markdown includer plugin." optional = false -python-versions = ">=3.6" -groups = ["dev"] +python-versions = ">=3.9" +groups = ["docs"] files = [ - {file = "mkdocs_include_markdown_plugin-3.9.1-py3-none-any.whl", hash = "sha256:f33687e29ac66d045ba181ea50f054646b0090b42b0a4318f08e7f1d1235e6f6"}, - {file = "mkdocs_include_markdown_plugin-3.9.1.tar.gz", hash = "sha256:5e5698e78d7fea111be9873a456089daa333497988405acaac8eba2924a19152"}, + {file = "mkdocs_include_markdown_plugin-7.1.7-py3-none-any.whl", hash = "sha256:a0c13efe4f6b05a419c022e201055bf43145eed90de65f2353c33fb4005b6aa5"}, + {file = "mkdocs_include_markdown_plugin-7.1.7.tar.gz", hash = "sha256:677637e04c2d3497c50340be522e2a7f614124f592c7982d88b859f88d527a4c"}, ] +[package.dependencies] +mkdocs = ">=1.4" +wcmatch = "*" + [package.extras] -dev = ["bump2version (==1.0.1)", "mkdocs (==1.4.0)", "pre-commit", "pytest (==7.1.3)", "pytest-cov (==3.0.0)", "tox"] -test = ["mkdocs (==1.4.0)", "pytest (==7.1.3)", "pytest-cov (==3.0.0)"] +cache = ["platformdirs"] [[package]] name = "mkdocs-material" -version = "8.5.11" +version = "9.6.20" description = "Documentation that simply works" optional = false -python-versions = ">=3.7" -groups = ["dev"] +python-versions = ">=3.8" +groups = ["docs"] files = [ - {file = "mkdocs_material-8.5.11-py3-none-any.whl", hash = "sha256:c907b4b052240a5778074a30a78f31a1f8ff82d7012356dc26898b97559f082e"}, - {file = "mkdocs_material-8.5.11.tar.gz", hash = "sha256:b0ea0513fd8cab323e8a825d6692ea07fa83e917bb5db042e523afecc7064ab7"}, + {file = "mkdocs_material-9.6.20-py3-none-any.whl", hash = "sha256:b8d8c8b0444c7c06dd984b55ba456ce731f0035c5a1533cc86793618eb1e6c82"}, + {file = "mkdocs_material-9.6.20.tar.gz", hash = "sha256:e1f84d21ec5fb730673c4259b2e0d39f8d32a3fef613e3a8e7094b012d43e790"}, ] [package.dependencies] -jinja2 = ">=3.0.2" -markdown = ">=3.2" -mkdocs = ">=1.4.0" -mkdocs-material-extensions = ">=1.1" -pygments = ">=2.12" -pymdown-extensions = ">=9.4" -requests = ">=2.26" +babel = ">=2.10,<3.0" +backrefs = ">=5.7.post1,<6.0" +click = "<8.2.2" +colorama = ">=0.4,<1.0" +jinja2 = ">=3.1,<4.0" +markdown = ">=3.2,<4.0" +mkdocs = ">=1.6,<2.0" +mkdocs-material-extensions = ">=1.3,<2.0" +paginate = ">=0.5,<1.0" +pygments = ">=2.16,<3.0" +pymdown-extensions = ">=10.2,<11.0" +requests = ">=2.26,<3.0" + +[package.extras] +git = ["mkdocs-git-committers-plugin-2 (>=1.1,<3)", "mkdocs-git-revision-date-localized-plugin (>=1.2.4,<2.0)"] +imaging = ["cairosvg (>=2.6,<3.0)", "pillow (>=10.2,<12.0)"] +recommended = ["mkdocs-minify-plugin (>=0.7,<1.0)", "mkdocs-redirects (>=1.2,<2.0)", "mkdocs-rss-plugin (>=1.6,<2.0)"] [[package]] name = "mkdocs-material-extensions" @@ -1613,12 +1711,35 @@ version = "1.3.1" description = "Extension pack for Python Markdown and MkDocs Material." optional = false python-versions = ">=3.8" -groups = ["dev"] +groups = ["docs"] files = [ {file = "mkdocs_material_extensions-1.3.1-py3-none-any.whl", hash = "sha256:adff8b62700b25cb77b53358dad940f3ef973dd6db797907c49e3c2ef3ab4e31"}, {file = "mkdocs_material_extensions-1.3.1.tar.gz", hash = "sha256:10c9511cea88f568257f960358a467d12b970e1f7b2c0e5fb2bb48cab1928443"}, ] +[[package]] +name = "mkdocs-mermaid2-plugin" +version = "1.2.2" +description = "A MkDocs plugin for including mermaid graphs in markdown sources" +optional = false +python-versions = ">=3.8" +groups = ["docs"] +files = [ + {file = "mkdocs_mermaid2_plugin-1.2.2-py3-none-any.whl", hash = "sha256:a003dddd6346ecc0ad530f48f577fe6f8b21ea23fbee09eabf0172bbc1f23df8"}, + {file = "mkdocs_mermaid2_plugin-1.2.2.tar.gz", hash = "sha256:20a44440d32cf5fd1811b3e261662adb3e1b98be272e6f6fb9a476f3e28fd507"}, +] + +[package.dependencies] +beautifulsoup4 = ">=4.6.3" +jsbeautifier = "*" +mkdocs = ">=1.0.4" +pymdown-extensions = ">=8.0" +requests = "*" +setuptools = ">=18.5" + +[package.extras] +test = ["mkdocs-macros-test", "mkdocs-material", "packaging", "requests-html"] + [[package]] name = "multidict" version = "6.5.1" @@ -1794,12 +1915,28 @@ version = "25.0" description = "Core utilities for Python packages" optional = false python-versions = ">=3.8" -groups = ["dev"] +groups = ["dev", "docs"] files = [ {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, ] +[[package]] +name = "paginate" +version = "0.5.7" +description = "Divides large result sets into pages for easier browsing" +optional = false +python-versions = "*" +groups = ["docs"] +files = [ + {file = "paginate-0.5.7-py2.py3-none-any.whl", hash = "sha256:b885e2af73abcf01d9559fd5216b57ef722f8c42affbb63942377668e35c7591"}, + {file = "paginate-0.5.7.tar.gz", hash = "sha256:22bd083ab41e1a8b4f3690544afb2c60c25e5c9a63a30fa2f483f6c60c8e5945"}, +] + +[package.extras] +dev = ["pytest", "tox"] +lint = ["black"] + [[package]] name = "pandas" version = "2.3.0" @@ -1908,7 +2045,7 @@ version = "0.12.1" description = "Utility library for gitignore style pattern matching of file paths." optional = false python-versions = ">=3.8" -groups = ["dev"] +groups = ["dev", "docs"] files = [ {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, @@ -1920,7 +2057,7 @@ version = "4.3.8" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.9" -groups = ["dev"] +groups = ["dev", "docs"] files = [ {file = "platformdirs-4.3.8-py3-none-any.whl", hash = "sha256:ff7059bb7eb1179e2685604f4aaf157cfd9535242bd23742eadc3c13542139b4"}, {file = "platformdirs-4.3.8.tar.gz", hash = "sha256:3d512d96e16bcb959a814c9f348431070822a6496326a4be0911c40b5a74c2bc"}, @@ -2289,7 +2426,7 @@ version = "2.19.2" description = "Pygments is a syntax highlighting package written in Python." optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["main", "docs"] files = [ {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, @@ -2300,14 +2437,14 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pymdown-extensions" -version = "10.16" +version = "10.16.1" description = "Extension pack for Python Markdown." optional = false python-versions = ">=3.9" -groups = ["dev"] +groups = ["docs"] files = [ - {file = "pymdown_extensions-10.16-py3-none-any.whl", hash = "sha256:f5dd064a4db588cb2d95229fc4ee63a1b16cc8b4d0e6145c0899ed8723da1df2"}, - {file = "pymdown_extensions-10.16.tar.gz", hash = "sha256:71dac4fca63fabeffd3eb9038b756161a33ec6e8d230853d3cecf562155ab3de"}, + {file = "pymdown_extensions-10.16.1-py3-none-any.whl", hash = "sha256:d6ba157a6c03146a7fb122b2b9a121300056384eafeec9c9f9e584adfdb2a32d"}, + {file = "pymdown_extensions-10.16.1.tar.gz", hash = "sha256:aace82bcccba3efc03e25d584e6a22d27a8e17caa3f4dd9f207e49b787aa9a91"}, ] [package.dependencies] @@ -2362,7 +2499,7 @@ version = "2.9.0.post0" description = "Extensions to the standard Python datetime module" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main", "dev"] +groups = ["main", "docs"] files = [ {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, @@ -2443,7 +2580,7 @@ version = "6.0.2" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.8" -groups = ["dev"] +groups = ["docs"] files = [ {file = "PyYAML-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0a9a2848a5b7feac301353437eb7d5957887edbf81d56e903999a75a3d743086"}, {file = "PyYAML-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:29717114e51c84ddfba879543fb232a6ed60086602313ca38cce623c1d62cfbf"}, @@ -2506,7 +2643,7 @@ version = "1.1" description = "A custom YAML tag for referencing environment variables in YAML files." optional = false python-versions = ">=3.9" -groups = ["dev"] +groups = ["docs"] files = [ {file = "pyyaml_env_tag-1.1-py3-none-any.whl", hash = "sha256:17109e1a528561e32f026364712fee1264bc2ea6715120891174ed1b980d2e04"}, {file = "pyyaml_env_tag-1.1.tar.gz", hash = "sha256:2eb38b75a2d21ee0475d6d97ec19c63287a7e140231e4214969d0eac923cd7ff"}, @@ -2642,7 +2779,7 @@ version = "2.32.4" description = "Python HTTP for Humans." optional = false python-versions = ">=3.8" -groups = ["main", "dev"] +groups = ["main", "docs"] files = [ {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"}, {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"}, @@ -2896,7 +3033,7 @@ version = "75.9.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.9" -groups = ["main"] +groups = ["main", "docs"] files = [ {file = "setuptools-75.9.1-py3-none-any.whl", hash = "sha256:0a6f876d62f4d978ca1a11ab4daf728d1357731f978543ff18ecdbf9fd071f73"}, {file = "setuptools-75.9.1.tar.gz", hash = "sha256:b6eca2c3070cdc82f71b4cb4bb2946bc0760a210d11362278cf1ff394e6ea32c"}, @@ -2917,12 +3054,24 @@ version = "1.17.0" description = "Python 2 and 3 compatibility utilities" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" -groups = ["main", "dev"] +groups = ["main", "docs"] files = [ {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, ] +[[package]] +name = "soupsieve" +version = "2.8" +description = "A modern CSS selector implementation for Beautiful Soup." +optional = false +python-versions = ">=3.9" +groups = ["docs"] +files = [ + {file = "soupsieve-2.8-py3-none-any.whl", hash = "sha256:0cc76456a30e20f5d7f2e14a98a4ae2ee4e5abdc7c5ea0aafe795f344bc7984c"}, + {file = "soupsieve-2.8.tar.gz", hash = "sha256:e2dd4a40a628cb5f28f6d4b0db8800b8f581b65bb380b97de22ba5ca8d72572f"}, +] + [[package]] name = "tabulate" version = "0.9.0" @@ -2963,7 +3112,7 @@ description = "A lil' TOML parser" optional = false python-versions = ">=3.8" groups = ["dev"] -markers = "python_version == \"3.10\"" +markers = "python_version < \"3.11\"" files = [ {file = "tomli-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:678e4fa69e4575eb77d103de3df8a895e1591b48e740211bd1067378c69e8249"}, {file = "tomli-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:023aa114dd824ade0100497eb2318602af309e5a55595f76b626d6d9f3b7b0a6"}, @@ -3030,12 +3179,12 @@ version = "4.14.0" description = "Backported and Experimental Type Hints for Python 3.9+" optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "dev", "docs"] files = [ {file = "typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af"}, {file = "typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4"}, ] -markers = {dev = "python_version == \"3.10\""} +markers = {dev = "python_version < \"3.11\""} [[package]] name = "typing-inspection" @@ -3082,7 +3231,7 @@ version = "2.5.0" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.9" -groups = ["main", "dev"] +groups = ["main", "docs"] files = [ {file = "urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc"}, {file = "urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760"}, @@ -3100,7 +3249,7 @@ version = "6.0.0" description = "Filesystem events monitoring" optional = false python-versions = ">=3.9" -groups = ["dev"] +groups = ["docs"] files = [ {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d1cdb490583ebd691c012b3d6dae011000fe42edb7a82ece80965b42abd61f26"}, {file = "watchdog-6.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bc64ab3bdb6a04d69d4023b29422170b74681784ffb9463ed4870cf2f3e66112"}, @@ -3137,6 +3286,21 @@ files = [ [package.extras] watchmedo = ["PyYAML (>=3.10)"] +[[package]] +name = "wcmatch" +version = "10.1" +description = "Wildcard/glob file name matcher." +optional = false +python-versions = ">=3.9" +groups = ["docs"] +files = [ + {file = "wcmatch-10.1-py3-none-any.whl", hash = "sha256:5848ace7dbb0476e5e55ab63c6bbd529745089343427caa5537f230cc01beb8a"}, + {file = "wcmatch-10.1.tar.gz", hash = "sha256:f11f94208c8c8484a16f4f48638a85d771d9513f4ab3f37595978801cb9465af"}, +] + +[package.dependencies] +bracex = ">=2.1.1" + [[package]] name = "web3" version = "6.11.0" @@ -3389,4 +3553,4 @@ propcache = ">=0.2.1" [metadata] lock-version = "2.1" python-versions = ">=3.10,<=3.12" -content-hash = "46ddf29603fffbbb4d51c3017b93edbbf0c979ee1f444e99956843b028933a4d" +content-hash = "7f500cce13fde71c9a0fe7d2713d16d375eac11b96917682470f54ff4138dd91" diff --git a/pyproject.toml b/pyproject.toml index b71292ce..5d6267b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,11 +35,11 @@ pytest-rerunfailures = "^13.0" semver = ">=2.9.1,<3.0.0" numpy = "<2" -mkdocs = "^1.3.1" -mkdocs-include-markdown-plugin = "^3.6.1" -mkdocs-material = "^8.4.0" -mkdocs-material-extensions = "^1.0.3" -mkdocs-autorefs = "^0.4.1" +[tool.poetry.group.docs.dependencies] +mkdocs = "^1.6.1" +mkdocs-material = "^9.6.20" +mkdocs-include-markdown-plugin = "^7.1.7" +mkdocs-mermaid2-plugin = "^1.2.2" [build-system] requires = ["poetry-core"] From 65b737e8a0cae08fdc04a822e45de3ca459ec1fb Mon Sep 17 00:00:00 2001 From: zarathustra Date: Thu, 18 Sep 2025 15:09:17 +0200 Subject: [PATCH 02/21] chore: remove empty files in docs --- docs/api/constants.md | 4 ---- docs/api/enums.md | 4 ---- docs/usage.md | 1 - docs/utils.md | 3 --- 4 files changed, 12 deletions(-) delete mode 100644 docs/api/constants.md delete mode 100644 docs/api/enums.md delete mode 100644 docs/usage.md delete mode 100644 docs/utils.md diff --git a/docs/api/constants.md b/docs/api/constants.md deleted file mode 100644 index f071ffbd..00000000 --- a/docs/api/constants.md +++ /dev/null @@ -1,4 +0,0 @@ -# API - Base Client - - -::: derive.base_client \ No newline at end of file diff --git a/docs/api/enums.md b/docs/api/enums.md deleted file mode 100644 index 55d04d2b..00000000 --- a/docs/api/enums.md +++ /dev/null @@ -1,4 +0,0 @@ -# API - Base Client - - -::: derive.constants \ No newline at end of file diff --git a/docs/usage.md b/docs/usage.md deleted file mode 100644 index 5499364d..00000000 --- a/docs/usage.md +++ /dev/null @@ -1 +0,0 @@ -# Usage of the file diff --git a/docs/utils.md b/docs/utils.md deleted file mode 100644 index 6bc174af..00000000 --- a/docs/utils.md +++ /dev/null @@ -1,3 +0,0 @@ -# API - Utils - -::: derive.utils \ No newline at end of file From 67806d95104b63781c75afa256e492929137a609 Mon Sep 17 00:00:00 2001 From: zarathustra Date: Thu, 18 Sep 2025 15:09:57 +0200 Subject: [PATCH 03/21] feat: update mkdocs.yml --- mkdocs.yml | 102 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 63 insertions(+), 39 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index c10f7ca2..31c8b963 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,18 +1,31 @@ -site_name: auto_dev -site_url: https://8ball030.github.io/auto_dev -repo_url: https://github.com/8ball030/auto_dev -repo_name: 8ball030/auto_dev -#strict: true +site_name: Derive Client +site_description: +docs_dir: docs +# site_url: ... +repo_url: https://github.com/8ball030/derive_client + nav: - Home: index.md - # - Usage: usage.md - - Reference: - - "constants": api/constants.md - # - "utils": utils.md + - Getting Started: + - Installation: getting-started/installation.md + - Account Model: getting-started/account-model.md + - Authentication: getting-started/authentication.md + - Clients: getting-started/clients.md + - Bridging & Funding: getting-started/bridging.md + - Quick Start: getting-started/quickstart.md + # - Examples: + # - Funding: examples/funding.md + # - Trading: examples/trading.md + # - Websockets: examples/websockets.md + # - API Reference: + # - HTTP Client: reference/http_client.md + # - Async Client: reference/async_client.md + # - Websocket Client: reference/ws_client.md + theme: name: material language: en - #logo: assets/logo.png + # logo: assets/logo.png palette: scheme: slate primary: indigo @@ -21,52 +34,63 @@ theme: - navigation.indexes - navigation.instant - navigation.tabs.sticky + - navigation.top + - navigation.tracking + - navigation.footer + - search.highlight + - search.share + - search.suggest + - content.code.copy + - content.code.annotate + - content.tabs.link + markdown_extensions: - - pymdownx.emoji: - emoji_index: !!python/name:materialx.emoji.twemoji - emoji_generator: !!python/name:materialx.emoji.to_svg - - pymdownx.critic - - pymdownx.caret + - admonition + - attr_list + - meta + - pymdownx.details + - pymdownx.highlight + - pymdownx.keys + - pymdownx.inlinehilite + - pymdownx.snippets + - pymdownx.tasklist + - pymdownx.magiclink - pymdownx.mark - pymdownx.tilde - - pymdownx.tabbed - - attr_list + - pymdownx.caret + - pymdownx.critic + - pymdownx.betterem + - pymdownx.superfences + - pymdownx.inlinehilite + - pymdownx.details + - pymdownx.tabbed: + alternate_style: true - pymdownx.arithmatex: generic: true - pymdownx.highlight: linenums: false - - pymdownx.superfences - - pymdownx.inlinehilite - - pymdownx.details - - admonition - toc: baselevel: 2 + toc_depth: 3 permalink: true - slugify: !!python/name:pymdownx.slugs.uslugify - - meta + slugify: !!python/object/apply:pymdownx.slugs.slugify + kwds: + case: lower + percent_encode: true + - pymdownx.emoji: + emoji_index: !!python/name:material.extensions.emoji.twemoji + emoji_generator: !!python/name:material.extensions.emoji.to_svg + plugins: - include-markdown + - mermaid2 - search: lang: en - - mkdocstrings + extra: social: - - icon: fontawesome/brands/twitter - # replace with your own tweet link below - link: https://github.com/waynerv/cookiecutter-pypackage - name: Tweet - - icon: fontawesome/brands/facebook - # replace with your own facebook link below - link: https://github.com/waynerv/cookiecutter-pypackage - name: Facebook - icon: fontawesome/brands/github - link: https://github.com/8ball030/auto_dev + link: https://github.com/8ball030/derive_client name: Github - icon: material/email link: "mailto:8ball030@gmail.com" - # to enable disqus, uncomment the following and put your disqus id below - # disqus: disqus_id -# uncomment the following and put your google tracking id below to enable GA -#google_analytics: - #- UA-xxx - #- auto From 9a7605d7d3a75fdc441448c664f88b52e66df7ed Mon Sep 17 00:00:00 2001 From: zarathustra Date: Thu, 18 Sep 2025 15:10:23 +0200 Subject: [PATCH 04/21] docs: index.md --- docs/index.md | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 16c8a6fe..b834436d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1 +1,68 @@ -# Lyra Client \ No newline at end of file +# Derive Client + +The **Derive Client** is a comprehensive library for interacting with [Derive Protocol](https://derive.xyz) - a decentralized, self-custodial derivatives trading platform built on Ethereum. + +## What is Derive? + +Derive is a high-performance crypto trading platform offering options, perpetuals, and spot trading. The platform consists of: + +- **Derive Chain**: An Optimistic rollup settlement layer built on the OP Stack, secured by Ethereum +- **Trading Engine**: High-performance matching engine for derivatives trading +- **Self-Custodial**: Users maintain full control of their funds through smart contract wallets + +## Key Features + +### **Multiple Client Types** + +- **Sync Client**: HTTP-based REST API client for simple, blocking operations +- **Async Client**: Asynchronous HTTP REST API client for high-performance applications +- **WebSocket Client**: Real-time client for streaming data and low-latency trading + +### **Built-in Bridging** + +- **Standard Bridge**: ETH bridging from Ethereum mainnet to Derive for gas fees +- **Derive Bridge**: Asset bridging using Socket Superbridge (WBTC, WETH, USDC, etc.) and LayerZero (DRV token) +- **Session Keys**: Secure delegation of trading permissions without compromising wallet security + +### **Unified API** + +All clients support the same core methods through Derive's JSON-RPC protocol, which works over both HTTP and WebSocket transports. + +## Architecture Overview + +```mermaid +graph TB + A[Your Application] --> B[Derive Client] + B --> C[Sync Client
HTTP REST] + B --> D[Async Client
HTTP REST] + B --> E[WebSocket Client
Real-time] + B --> F[Bridge Module] + + C --> G[Derive API
JSON-RPC over HTTP] + D --> G + E --> H[Derive API
JSON-RPC over WebSocket] + + F --> I[Standard Bridge
ETH Mainnet → Derive] + F --> J[Socket Superbridge
ERC20 Tokens] + F --> K[LayerZero
DRV Token] +``` + +## Next Steps + +### Getting Started + +1. **[Installation](getting-started/installation.md)** - Install the client and dependencies +2. **[Account Model](getting-started/account-model.md)** - Understand EOA, LightAccount, subaccounts, and session keys +3. **[Authentication](getting-started/authentication.md)** - Register accounts and session keys via the web app +4. **[Bridging & Funding](getting-started/bridging.md)** - Deposit and withdraw assets to/from Derive +5. **[Clients](getting-started/clients.md)** - Choose the right client for your use case +6. **[Quick Start](getting-started/quickstart.md)** - Connect, fund, and start using the client programmatically + +### Need Help? + +- **[Examples](examples/)** - Working code examples +- **[API Reference](reference/)** - Complete method documentation + +--- + +_Ready to start trading derivatives on Derive? Let's get you set up!_ From ccef9e60d3bb55cbe2d2bed7537ac84ef87b2cb3 Mon Sep 17 00:00:00 2001 From: zarathustra Date: Thu, 18 Sep 2025 15:10:52 +0200 Subject: [PATCH 05/21] docs: installation.md --- docs/getting-started/installation.md | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 docs/getting-started/installation.md diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md new file mode 100644 index 00000000..524763a2 --- /dev/null +++ b/docs/getting-started/installation.md @@ -0,0 +1,28 @@ +# Installation + +Quick, minimal steps to get the Derive Client installed. + +## From PyPI + +```shell +pip install derive-client +``` + +## From source (recommended for contributors / development) + +```shell +# clone the repo +git clone git@github.com:8ball030/derive_client.git +cd derive_client + +# (option A) install editable with pip +pip install -e . + +# (option B) use Poetry (recommended for development) +poetry install +poetry shell +``` + +--- + +Next: **[Account Model](account-model.md)** - Sunderstand LightAccounts, subaccounts, and session keys. From 3630d0fdc3b87e39be7ec94dbcd3293983659407 Mon Sep 17 00:00:00 2001 From: zarathustra Date: Thu, 18 Sep 2025 15:11:25 +0200 Subject: [PATCH 06/21] docs: account-model.md --- docs/getting-started/account-model.md | 103 ++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 docs/getting-started/account-model.md diff --git a/docs/getting-started/account-model.md b/docs/getting-started/account-model.md new file mode 100644 index 00000000..8957b664 --- /dev/null +++ b/docs/getting-started/account-model.md @@ -0,0 +1,103 @@ +# Understanding Derive's Account Model + +Before diving into authentication and trading, it's crucial to understand how accounts work on Derive. This will explain why you need multiple addresses and keys, and why the bridging works the way it does. + +## The Problem with Traditional Exchanges + +Centralized exchanges hold custody of your funds: + +- ❌ You do not control your keys or coins +- ❌ Accounts can be frozen +- ❌ Exchange hacks can lead to loss of funds + +Derive avoids these risks by being **self-custodial**: you always control your assets. + +## Derive's Smart Contract Wallet Model + +A Derive account is a **LightAccount** ([ERC-4337](https://docs.erc4337.io/)), a smart contract wallet you own through your Ethereum address (EOA). + +### Your EOA (Externally Owned Account) + +This is your regular Ethereum address controlled by your private key: + +- Your normal Ethereum wallet address +- Controlled by your private key +- Used to sign transactions, prove ownership, and pay gas fees + +### Your LightAccount (Smart Contract Wallet) + +This is a smart contract deployed on Derive chain that you own: + +- A programmable wallet deployed on Derive +- Owned by your EOA +- Holds funds, manages subaccounts, enables trading +- Can delegate permissions to other addresses +- Transactions are gas-sponsored by Derive + +### Subaccounts + +- Isolated trading accounts within a LightAccount +- Useful for separating strategies, risks, or teams +- Identified by numeric IDs (e.g., `123456`) + +## The Three-Layer Structure + +```mermaid +graph TD + A[EOA
Owner Key] --> B[LightAccount
Smart Contract Wallet] + B --> C[Subaccount 1
Trading Account] + B --> D[Subaccount 2
Trading Account] + B --> E[Subaccount N
Trading Account] + + F[EOA
Session Key] -.-> C + F -.-> D + F -.-> E +``` + +### Layer 1: Your EOA (Owner) + +- **Purpose**: Ultimate ownership and control +- **Controlled by**: Your main private key +- **Powers**: Can do everything - bridge funds, withdraw funds, create session keys + +### Layer 2: LightAccount (Funding Wallet) + +- **Purpose**: Hold your trading capital on Derive +- **Controlled by**: Your EOA (you're the owner) +- **Powers**: Holds funds, manages subaccounts, enables trading +- **Gas**: Transactions are sponsored by Derive's paymaster + +### Layer 3: Subaccounts (Trading Accounts) + +- **Purpose**: Isolated trading environments +- **Why multiple?**: Different strategies, risk management, organization +- **Example**: Subaccount 1 for spot trading, Subaccount 2 for options + +## Session Keys: Secure Delegation + +For active trading you should not use your owner key. Instead, delegate to **session keys**: + +- Normal EOAs granted limited permissions +- Registered with expiration +- Revocable at any time + +### Permission Levels + +| Permission | What it allows | +| ------------- | -------------------------------------- | +| **Admin** | Everything except bridging | +| **Account** | Trading, transfers between subaccounts | +| **Read Only** | Query and view data only | + +## Summary + +| Component | Address | Purpose | Pays Gas | Can Bridge | +| ---------------- | ------------ | -------------------------- | ------------ | ---------- | +| **Your EOA** | `0x742d...` | Owner, ultimate control | ✅ Yes | ✅ Yes | +| **LightAccount** | `0x8f5B...` | Holds trading capital | ❌ Sponsored | ❌ No | +| **Subaccount** | ID: `123456` | Individual trading account | ❌ Sponsored | ❌ No | +| **Session Key** | `0x9A8B...` | Delegated trading access | ❌ Sponsored | ❌ No | + +--- + +Next: **[Authentication](authentication.md)** - Set up your EOA, LightAccount, and Session Keys. From 232df90142ef3eb2f426df60e10d8d73b8ed0910 Mon Sep 17 00:00:00 2001 From: zarathustra Date: Thu, 18 Sep 2025 15:11:40 +0200 Subject: [PATCH 07/21] docs: authentication.md --- docs/getting-started/authentication.md | 78 ++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 docs/getting-started/authentication.md diff --git a/docs/getting-started/authentication.md b/docs/getting-started/authentication.md new file mode 100644 index 00000000..f5783a46 --- /dev/null +++ b/docs/getting-started/authentication.md @@ -0,0 +1,78 @@ +# Authentication + +To interact with Derive using the client, you first need to register and configure your accounts. This involves creating a **LightAccount**, **Subaccount**, and **Session Key**. + +At the moment, registration is done via the [Derive web app](https://derive.xyz). Programmatic account creation is not yet supported by this client (TODO). + +--- + +## What You Need + +1. **Session Key Private Key:** + The EOA private key for the session key you registered (this can be the owner EOA or a secondary signer). +2. **LightAccount Address:** + The smart contract wallet that holds your funds on Derive. +3. **Subaccount ID:** + The trading account within your LightAccount. + - Currently optional in the client: if omitted, the first subaccount will be used. + - **Best practice:** always provide explicitly to avoid ambiguity. + +--- + +## Step 1. Generate a Private Key + +You can generate a private key using the helper script: + +```shell +./scripts/generate-key.py +``` + +Or use an existing key + +## Step 2: Create Your LightAccount + +1. Visit [derive.xyz](https://derive.xyz) +2. Connect your wallet and create an account +3. This creates your **LightAccount** (funding wallet) +4. Copy the LightAccount address from the Developer section + +--- + +## Step 3: Create a Subaccount + +1. Go to the [Developers](https://www.derive.xyz/developers) section +2. Click **Create Subaccount** +3. Select your margin type: _Standard_ or _Portfolio_ +4. Name the subaccount +5. Click **Create Subaccount** + +--- + +## Step 4: Register a Session Key + +1. In the [Developers](https://www.derive.xyz/developers) section +2. Click **Register Session Key** +3. Provide the EOA you want to use as a session key (can be the owner or a secondary signer) +4. Select permissions (Admin / Account / Read-only) and expiry +5. Note your **Subaccount ID** + +> ⚠️ Important: +> +> - Even the owner EOA must be registered as a session key in order to trade on Derive. +> - This client currently only supports bridging when using the owner EOA. This is a deliberate precaution to prevent confusion and mistakes. See [Bridging & Funding](bridging.md) for details. + +--- + +## Step 5: Store Your Credentials + +You will need the following information to use the client: + +```bash +export DERIVE_SESSION_KEY=0x742d... # Required: session key private key +export DERIVE_LIGHTACCOUNT=0x8f5B... # Required: LightAccount address +export DERIVE_SUBACCOUNT=123456 # Recommended: subaccount ID (explicit) +``` + +--- + +Next: [Bridging & Funding](bridging.md) - How to fund your accounts and get ready to trade. From 38fec8471f2ca376105f2f884e4a1d1917a29bde Mon Sep 17 00:00:00 2001 From: zarathustra Date: Thu, 18 Sep 2025 15:12:00 +0200 Subject: [PATCH 08/21] docs: bridging.md --- docs/getting-started/bridging.md | 93 ++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 docs/getting-started/bridging.md diff --git a/docs/getting-started/bridging.md b/docs/getting-started/bridging.md new file mode 100644 index 00000000..fc903931 --- /dev/null +++ b/docs/getting-started/bridging.md @@ -0,0 +1,93 @@ +# Bridging & Funding + +Now that you understand Derive's account model, let's get your funds where they need to be. + +Remember: you have **two different addresses** that need funding for different purposes: + +- **Your EOA** needs ETH for paying gas on bridge transactions +- **Your LightAccount** needs trading assets (USDC, WETH, etc.) for actual trading + +The Derive client handles three different bridge protocols automatically, so you don't need to worry about the technical details. + +--- + +## Supported Bridges + +### 1. Standard Bridge + +- Used **only** for transferring native ETH from Ethereum mainnet to the Derive owner EOA. +- Purpose: fund the Derive EOA with native ETH to cover gas for bridging operations. +- Currently restricted to ETH-only. Other assets and chains may be supported in the future. + +### 2. Socket Superbridge + +- Used for transferring **ERC20 tokens** (e.g. USDC, WETH, etc.) between + - Ethereum mainnet, Base, Arbitrum, Optimism + - and Derive +- Supports **bidirectional transfers**. +- Bridge and gas costs are always paid by the **Derive EOA in native ETH**. + +### 3. LayerZero + +- Used exclusively for the **DRV token**: + - Deposits into Derive: gas is paid in the source chain's native token. + - Withdrawals from Derive: gas is paid in Derive's native **DRV token**. + +--- + +## Restrictions and Client Behavior + +- **Owner-only bridging:** + While Derive contracts allow admin-level secondary signers to bridge, this client restricts bridging to the **owner EOA only**. This prevents accidental transfers between the wrong addresses. + +- **No ETH deposit to LightAccount:** + Although the deployed contracts support bridging ETH directly to the Derive LightAccount, this automatically wraps it into WETH. To avoid confusion, the client currently **does not support this**. If you want WETH on your LightAccount, you should therefore wrap ETH into WETH on the source chain first, and then bridge WETH to Derive. + +--- + +## After Bridging: Moving Funds Into Subaccounts + +Assets bridged into Derive first arrive in your LightAccount (funding wallet). However, you cannot trade directly from the LightAccount. To trade, funds must be transferred into a subaccount. + +- Transfers are supported both ways: + - LightAccount -> Subaccount (to fund trading) + - Subaccount -> LightAccount (to withdraw trading balance) + +When creating a subaccount, you choose a margin model: + +- **Standard Margin:** margin calculated per position, conservative but simple. +- **Portfolio Margin:** margin calculated on the overall portfolio with stress tests, usually more capital efficient but limited to a single base asset type. + +For detailed descriptions of margin models and liquidation rules, see the Derive documenation: + +- [Standard Margin](https://docs.derive.xyz/docs/standard-margin-1) +- [Portfolio Margin](https://docs.derive.xyz/docs/portfolio-margin-1) + +--- + +## How Bridging Works (Conceptual Flow) + +All bridge operations follow the same general lifecycle: + +1. Prepare a transaction on the source chain +2. Sign and submit the transaction +3. Wait for finality on the source chain +4. Event is picked up by the bridge +5. Wait for confirmation and finality on the target chain + +This flow is abstracted in the client. +You only need to call the high-level methods exposed on the `DeriveClient`. + +--- + +## Usage + +The client exposes bridging via high-level methods on `DeriveClient`. +Underlying bridge implementations live in the private `_bridge` module. + +- **We do not document the private modules here.** +- Refer to [Quickstart](quickstart.md) or [Examples](examples/) for usage patterns and sample code. + +--- + +Next: [Clients](clients.md) - choose the right client type (sync, async, websocket) for your use case. From defde634a0151fc435201a25c3697bc6cf260c71 Mon Sep 17 00:00:00 2001 From: zarathustra Date: Thu, 18 Sep 2025 15:12:14 +0200 Subject: [PATCH 09/21] docs: clients.md --- docs/getting-started/clients.md | 48 +++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 docs/getting-started/clients.md diff --git a/docs/getting-started/clients.md b/docs/getting-started/clients.md new file mode 100644 index 00000000..00eafdc8 --- /dev/null +++ b/docs/getting-started/clients.md @@ -0,0 +1,48 @@ +# Clients + +Derive offers multiple client interfaces so you can choose the one that best fits your application needs. All clients use the same underlying **JSON-RPC API**; the difference is in the transport protocol (HTTP vs WebSocket). + +--- + +## Available Clients + +| Client Type | Transport Protocol | Best For | Real-Time Channels? | +| -------------------- | -------------------- | --------------------------------------------------- | ------------------- | +| **Sync HTTP** | HTTP POST / JSON-RPC | Simple scripts, CLI tools, one-off queries | ❌ No | +| **Async HTTP** | HTTP POST / JSON-RPC | High-performance, multi-concurrent workflows | ❌ No | +| **WebSocket Client** | WebSocket JSON-RPC | Low latency, live updates, market data, event feeds | ✅ Yes | + +**All clients support**: Trading methods, account management, and bridging operations + +--- + +## Transport Differences + +**HTTP (Sync & Async):** + +- Request-response pattern, new connection per call +- Simple, reliable, fewer moving parts + +**WebSocket:** + +- Persistent connection, lower latency for multiple requests +- Real-time subscriptions to live data streams + +**Real-Time Channels (WebSocket Only):** + +- **Public**: Orderbook, trade feeds, ticker data +- **Private**: Account updates, order status, personal notifications + +--- + +## Choosing the Right Client + +- **Occasional queries** → **Sync HTTP** (simplest) +- **High-frequency operations** → **Async HTTP** (concurrent requests) +- **Real-time applications** → **WebSocket** (live data streams) + +All clients have identical trading and bridging APIs - choose based on your performance and real-time data needs. + +--- + +Next: **[Quick Start](quickstart.md)** — Connect, fund, and start trading. From e7be616e84eb0cee155d45bfe12dd80e1065db8c Mon Sep 17 00:00:00 2001 From: zarathustra Date: Thu, 18 Sep 2025 15:12:34 +0200 Subject: [PATCH 10/21] docs: quickstart.md --- docs/getting-started/quickstart.md | 145 +++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 docs/getting-started/quickstart.md diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md new file mode 100644 index 00000000..3aaf3208 --- /dev/null +++ b/docs/getting-started/quickstart.md @@ -0,0 +1,145 @@ +# Quick Start + +This guide will help you get started with the **Derive Python Client** using a minimal, synchronous HTTP setup. You will initialize your client, fund your subaccount, and prepare it for trading. + +> Assumes you have completed: +> +> - Installation ([installation.md](installation.md)) +> - Account registration ([authentication.md](authentication.md)) +> You should already have: +> - `DERIVE_SESSION_KEY` +> - `DERIVE_LIGHTACCOUNT` +> - `DERIVE_SUBACCOUNT` + +## 1. Set Up Environment Variables + +You can manage your credentials via a `.env` file to keep them out of your code: + +```shell +SESSION_KEY_PRIVATE_KEY= +DERIVE_LIGHTACCOUNT= +DERIVE_SUBACCOUNT= +``` + +Then load them in Python using `python-dotenv` (or any preferred method): + +```python +from dotenv import load_dotenv +import os + +load_dotenv() + +session_key = os.environ["SESSION_KEY_PRIVATE_KEY"] +light_account = os.environ["DERIVE_LIGHTACCOUNT"] +subaccount_id = os.environ.get("DERIVE_SUBACCOUNT") +``` + +## 2. Initialize the Sync HTTP Client + +```python +from derive_client import DeriveClient + +client = DeriveClient( + private_key=session_key, + wallet=light_account, + subaccount_id=subaccount_id +) +``` + +- Authenticates via your session key +- Supports core JSON-RPC methods and bridging operations +- Ideal for scripts and minimal setups + +Async and WebSocket clients are available for high-concurrency or real-time workflows (see [clients.md](clients.md)). + +## 3. Fund Your Account + +Trading requires funds in the right places. Here's the complete funding flow: + +### Understanding Bridge Operations + +Each bridge operation follows a safe 3-step pattern: + +1. **Prepare** - Calculate fees, validate, return transaction details for review. +2. **Submit** - Execute transaction and return tracking information +3. **Poll** - Monitor progress across both chains until completion: + - Finality on source chain + - Event on target chain + - Finality on target chain + +### Funding Steps + +#### Step 1: Fund EOA with gas money + +Bridge ETH to your EOA for gas fees (needed for withdrawals later): + +```python +# Bridge ETH for gas - ensures you can always withdraw funds +prepared_tx = client.bridge.prepare_deposit( + human_amount=0.01, + currency="ETH", + source_chain="ETH", + target_chain="DERIVE", # Goes to your EOA +) +tx_result = client.bridge.submit_deposit(prepared_tx=prepared_tx) +tx_result = client.bridge.poll_progress(tx_result=tx_result) +``` + +#### Step 2: Bridge Trading Assets + +Bridge tokens to your LightAccount for trading: + +```python +# Bridge trading capital to LightAccount +prepared_tx = client.bridge.prepare_deposit( + human_amount=100.0, + currency="USDC", + source_chain="BASE", + target_chain="DERIVE", # Goes to LightAccount +) +tx_result = client.bridge.submit_deposit(prepared_tx=prepared_tx) +tx_result = client.bridge.poll_progress(tx_result=tx_result) +``` + +#### Step 3: Transfer to Subaccount + +Move funds from LightAccount to your trading subaccount: + +```python +# Transfer to subaccount for trading +tx_result = client.transfer_to_subaccount( + amount=100.0, + token="USDC", + subaccount_id=subaccount_id, +) +``` + +## 4. Withdrawing Funds + +When you want to move funds back to external chains: + +```python +# Withdraw from subaccount to LightAccount first +tx_result = client.transfer_from_subaccount( + amount=100.0, + token="USDC", + subaccount_id=subaccount_id, +) + +# Then bridge back to external chain +prepared_tx = client.bridge.prepare_withdrawal( + human_amount=100.0, + currency="USDC", + source_chain="DERIVE", + target_chain="BASE", +) +tx_result = client.bridge.submit_withdrawal(prepared_tx=prepared_tx) +tx_result = client.bridge.poll_progress(tx_result=tx_result) +``` + +--- + +Next: + +- Start exploring example scripts for trading: [examples](examples/) +- Refer to API documentation: [API Reference](reference/) From 132dd84a45cf54d3f3770a0b44243b0cdd6ac4f3 Mon Sep 17 00:00:00 2001 From: zarathustra Date: Wed, 1 Oct 2025 19:26:47 +0200 Subject: [PATCH 11/21] docs: update env variable names --- docs/getting-started/authentication.md | 6 +++--- docs/getting-started/quickstart.md | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/getting-started/authentication.md b/docs/getting-started/authentication.md index f5783a46..3f18a741 100644 --- a/docs/getting-started/authentication.md +++ b/docs/getting-started/authentication.md @@ -68,9 +68,9 @@ Or use an existing key You will need the following information to use the client: ```bash -export DERIVE_SESSION_KEY=0x742d... # Required: session key private key -export DERIVE_LIGHTACCOUNT=0x8f5B... # Required: LightAccount address -export DERIVE_SUBACCOUNT=123456 # Recommended: subaccount ID (explicit) +export DERIVE_SESSION_PRIVATE_KEY=0x742d... # Required: EOA private key registered as a session key +export DERIVE_WALLET=0x8f5B... # Required: LightAccount (smart contract wallet) address +export DERIVE_SUBACCOUNT=123456 # Recommended: Subaccount ID (explicit) ``` --- diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md index 3aaf3208..de7e4d63 100644 --- a/docs/getting-started/quickstart.md +++ b/docs/getting-started/quickstart.md @@ -7,8 +7,8 @@ This guide will help you get started with the **Derive Python Client** using a m > - Installation ([installation.md](installation.md)) > - Account registration ([authentication.md](authentication.md)) > You should already have: -> - `DERIVE_SESSION_KEY` -> - `DERIVE_LIGHTACCOUNT` +> - `DERIVE_SESSION_PRIVATE_KEY` +> - `DERIVE_WALLET` > - `DERIVE_SUBACCOUNT` ## 1. Set Up Environment Variables @@ -16,8 +16,8 @@ This guide will help you get started with the **Derive Python Client** using a m You can manage your credentials via a `.env` file to keep them out of your code: ```shell -SESSION_KEY_PRIVATE_KEY= -DERIVE_LIGHTACCOUNT= +DERIVE_SESSION_PRIVATE_KEY= +DERIVE_WALLET= DERIVE_SUBACCOUNT= ``` @@ -29,8 +29,8 @@ import os load_dotenv() -session_key = os.environ["SESSION_KEY_PRIVATE_KEY"] -light_account = os.environ["DERIVE_LIGHTACCOUNT"] +private_key = os.environ["DERIVE_SESSION_PRIVATE_KEY"] +wallet = os.environ["DERIVE_WALLET"] subaccount_id = os.environ.get("DERIVE_SUBACCOUNT") ``` @@ -40,8 +40,8 @@ subaccount_id = os.environ.get("DERIVE_SUBACCOUNT") from derive_client import DeriveClient client = DeriveClient( - private_key=session_key, - wallet=light_account, + private_key=private_key, + wallet=wallet, subaccount_id=subaccount_id ) ``` From 1553bc9a327f769fc7e2fa293563ecbb4b3675b2 Mon Sep 17 00:00:00 2001 From: zarathustra Date: Wed, 1 Oct 2025 19:27:51 +0200 Subject: [PATCH 12/21] docs: update bridging from idealised to current interface --- docs/getting-started/quickstart.md | 49 ++++++++++++++++++------------ 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md index de7e4d63..e4e7e899 100644 --- a/docs/getting-started/quickstart.md +++ b/docs/getting-started/quickstart.md @@ -74,15 +74,18 @@ Each bridge operation follows a safe 3-step pattern: Bridge ETH to your EOA for gas fees (needed for withdrawals later): ```python +from derive_client.data_types import ChainID, Currency + # Bridge ETH for gas - ensures you can always withdraw funds -prepared_tx = client.bridge.prepare_deposit( +prepared_tx = client.prepare_standard_tx( human_amount=0.01, - currency="ETH", - source_chain="ETH", - target_chain="DERIVE", # Goes to your EOA + currency=Currency.ETH, + to: ..., # The LightAccount owner + source_chain=ChainID.BASE, + target_chain=ChainID.DERIVE, # Goes to LightAccount owner (EOA) ) -tx_result = client.bridge.submit_deposit(prepared_tx=prepared_tx) -tx_result = client.bridge.poll_progress(tx_result=tx_result) +tx_result = client.submit_bridge_tx(prepared_tx=prepared_tx) +tx_result = client.poll_bridge_progress(tx_result=tx_result) ``` #### Step 2: Bridge Trading Assets @@ -91,14 +94,16 @@ Bridge tokens to your LightAccount for trading: ```python # Bridge trading capital to LightAccount -prepared_tx = client.bridge.prepare_deposit( +from derive_client.data_types import ChainID, Currency + +prepared_tx = client.prepare_deposit_to_derive( human_amount=100.0, - currency="USDC", - source_chain="BASE", - target_chain="DERIVE", # Goes to LightAccount + currency=Currency.USDC, + source_chain=ChainID.BASE, + target_chain=ChainID.DERIVE, # Goes to LightAccount ) -tx_result = client.bridge.submit_deposit(prepared_tx=prepared_tx) -tx_result = client.bridge.poll_progress(tx_result=tx_result) +tx_result = client.submit_bridge_tx(prepared_tx=prepared_tx) +tx_result = client.poll_bridge_progress(tx_result=tx_result) ``` #### Step 3: Transfer to Subaccount @@ -106,10 +111,12 @@ tx_result = client.bridge.poll_progress(tx_result=tx_result) Move funds from LightAccount to your trading subaccount: ```python +from derive_client.data_types import Currency + # Transfer to subaccount for trading tx_result = client.transfer_to_subaccount( amount=100.0, - token="USDC", + token=Currency.USDC, subaccount_id=subaccount_id, ) ``` @@ -119,22 +126,24 @@ tx_result = client.transfer_to_subaccount( When you want to move funds back to external chains: ```python +from derive_client.data_types import ChainID, Currency + # Withdraw from subaccount to LightAccount first tx_result = client.transfer_from_subaccount( amount=100.0, - token="USDC", + token=Currency.USDC, subaccount_id=subaccount_id, ) # Then bridge back to external chain -prepared_tx = client.bridge.prepare_withdrawal( +prepared_tx = client.prepare_withdrawal_from_derive( human_amount=100.0, - currency="USDC", - source_chain="DERIVE", - target_chain="BASE", + currency=Currency.USDC, + source_chain=ChainID.DERIVE, + target_chain=ChainID.BASE, ) -tx_result = client.bridge.submit_withdrawal(prepared_tx=prepared_tx) -tx_result = client.bridge.poll_progress(tx_result=tx_result) +tx_result = client.submit_bridge_tx(prepared_tx=prepared_tx) +tx_result = client.poll_bridge_progress(tx_result=tx_result) ``` --- From 798250315aca95f80ff07a39dea2bd263276c644 Mon Sep 17 00:00:00 2001 From: zarathustra Date: Wed, 1 Oct 2025 19:32:21 +0200 Subject: [PATCH 13/21] docs: reference create_new_pk.py --- docs/getting-started/authentication.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started/authentication.md b/docs/getting-started/authentication.md index 3f18a741..381c3726 100644 --- a/docs/getting-started/authentication.md +++ b/docs/getting-started/authentication.md @@ -24,7 +24,7 @@ At the moment, registration is done via the [Derive web app](https://derive.xyz) You can generate a private key using the helper script: ```shell -./scripts/generate-key.py +python ./scripts/create_new_pk.py ``` Or use an existing key From 49d52626dd6501f14160c6054737cd6f7d105769 Mon Sep 17 00:00:00 2001 From: zarathustra Date: Thu, 30 Oct 2025 19:18:48 +0100 Subject: [PATCH 14/21] feat: templates/transaction_struct.py --- .../data/templates/transaction_structs.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 derive_client/data/templates/transaction_structs.py diff --git a/derive_client/data/templates/transaction_structs.py b/derive_client/data/templates/transaction_structs.py new file mode 100644 index 00000000..c98dd0b0 --- /dev/null +++ b/derive_client/data/templates/transaction_structs.py @@ -0,0 +1,27 @@ +"""Struct definitions for PublicGetTransactionResultSchema nested types.""" + +from msgspec import Struct + + +class TransactionDataInner(Struct): + asset: str + amount: str + decimals: int + + +class TransactionData(Struct): + data: TransactionDataInner + nonce: int + owner: str + expiry: int + module: str + signer: str + asset_id: str + signature: str + asset_name: str + subaccount_id: int + is_atomic_signing: bool + + +class TransactionErrorLog(Struct): + error: str From 73cc049dfe26896010a9b7a512feed18daf3c1ca Mon Sep 17 00:00:00 2001 From: zarathustra Date: Thu, 30 Oct 2025 19:19:35 +0100 Subject: [PATCH 15/21] feat: inject_transaction_structs in generate-models --- scripts/generate-models.py | 46 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/scripts/generate-models.py b/scripts/generate-models.py index a0cd6d5a..e8c0e148 100644 --- a/scripts/generate-models.py +++ b/scripts/generate-models.py @@ -176,12 +176,58 @@ def is_struct_class(node: ast.ClassDef) -> bool: return False +def inject_transaction_structs(path: Path, template_path: Path) -> None: + """Inject transaction struct definitions and fix PublicGetTransactionResultSchema.""" + + print("Injecting transaction structs via AST") + + template_code = template_path.read_text() + template_tree = ast.parse(template_code) + + struct_classes = [node for node in template_tree.body if isinstance(node, ast.ClassDef)] + + code = path.read_text() + tree = ast.parse(code) + + target_class_idx = None + target_class = None + for idx, node in enumerate(tree.body): + if isinstance(node, ast.ClassDef) and node.name == "PublicGetTransactionResultSchema": + target_class_idx = idx + target_class = node + break + + if target_class_idx is None: + print("Warning: PublicGetTransactionResultSchema not found, skipping injection") + return + + for struct_class in reversed(struct_classes): + tree.body.insert(target_class_idx, struct_class) + + for stmt in target_class.body: + if isinstance(stmt, ast.AnnAssign) and isinstance(stmt.target, ast.Name): + field_name = stmt.target.id + + if field_name == "data": + stmt.annotation = ast.Name(id="TransactionData", ctx=ast.Load()) + + elif field_name == "error_log" and isinstance(stmt.annotation, ast.Subscript): + stmt.annotation.slice = ast.Name(id="TransactionErrorLog", ctx=ast.Load()) + + ast.fix_missing_locations(tree) + new_code = ast.unparse(tree) + path.write_text(CUSTOM_HEADER + "\n" + new_code) + + if __name__ == "__main__": base_url = "https://docs.derive.xyz" repo_root = Path(__file__).parent.parent input_path = repo_root / "openapi-spec.json" output_path = repo_root / "derive_client" / "data" / "generated" / "models.py" + transaction_structs_template = repo_root / "derive_client" / "data" / "templates" / "transaction_structs.py" + generate_models(input_path=input_path, output_path=output_path) patch_pagination_to_optional(output_path) + inject_transaction_structs(output_path, transaction_structs_template) patch_file(output_path) print("Done.") From ffb51f7642cfbc3b48247ee5a5a3879b305894e9 Mon Sep 17 00:00:00 2001 From: zarathustra Date: Thu, 30 Oct 2025 19:20:02 +0100 Subject: [PATCH 16/21] chore: generate-models --- derive_client/data/generated/models.py | 30 ++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/derive_client/data/generated/models.py b/derive_client/data/generated/models.py index 507a13f0..31f4ae8f 100644 --- a/derive_client/data/generated/models.py +++ b/derive_client/data/generated/models.py @@ -1,10 +1,8 @@ # ruff: noqa: E741 from __future__ import annotations - from decimal import Decimal from enum import Enum from typing import Any, Dict, List, Optional, Union - from msgspec import Struct @@ -1318,10 +1316,34 @@ class PublicGetTransactionParamsSchema(Struct): transaction_id: str +class TransactionDataInner(Struct): + asset: str + amount: str + decimals: int + + +class TransactionData(Struct): + data: TransactionDataInner + nonce: int + owner: str + expiry: int + module: str + signer: str + asset_id: str + signature: str + asset_name: str + subaccount_id: int + is_atomic_signing: bool + + +class TransactionErrorLog(Struct): + error: str + + class PublicGetTransactionResultSchema(Struct): - data: dict + data: TransactionData status: TxStatus - error_log: Optional[dict] = None + error_log: Optional[TransactionErrorLog] = None transaction_hash: Optional[str] = None From a4076c647d30b559dbd080f928b3cfd118f79eff Mon Sep 17 00:00:00 2001 From: zarathustra Date: Thu, 30 Oct 2025 19:20:56 +0100 Subject: [PATCH 17/21] feat: cli command group: transaction --- derive_client/cli/__init__.py | 4 +- derive_client/cli/_transactions.py | 93 ++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 derive_client/cli/_transactions.py diff --git a/derive_client/cli/__init__.py b/derive_client/cli/__init__.py index 414cce9f..6810e7c5 100644 --- a/derive_client/cli/__init__.py +++ b/derive_client/cli/__init__.py @@ -13,6 +13,7 @@ from ._mmp import mmp from ._orders import order from ._positions import position +from ._transactions import transaction click.rich_click.USE_RICH_MARKUP = True @@ -34,7 +35,7 @@ ) @click.pass_context def cli(ctx, session_key_path: Path | None, env_file: Path | None): - """Derive v2 client command line interface.""" + """Derive client command line interface.""" ctx.ensure_object(dict) client = create_client(ctx=ctx, session_key_path=session_key_path, env_file=env_file) @@ -47,3 +48,4 @@ def cli(ctx, session_key_path: Path | None, env_file: Path | None): cli.add_command(mmp) cli.add_command(order) cli.add_command(position) +cli.add_command(transaction) diff --git a/derive_client/cli/_transactions.py b/derive_client/cli/_transactions.py new file mode 100644 index 00000000..ef47e2fd --- /dev/null +++ b/derive_client/cli/_transactions.py @@ -0,0 +1,93 @@ +"""CLI commands for transactions.""" + +from __future__ import annotations + +from decimal import Decimal + +import pandas as pd +import rich_click as click + +from ._utils import struct_to_series + + +@click.group("transaction") +@click.pass_context +def transaction(ctx): + """Move funds between wallet and subaccount.""" + + +@transaction.command("get") +@click.argument( + "transaction_id", + required=True, +) +@click.pass_context +def get(ctx, transaction_id: str): + """Used for getting a transaction by its transaction id.""" + + client = ctx.obj["client"] + subaccount = client.active_subaccount + transaction = subaccount.transactions.get(transaction_id=transaction_id) + + tx_data = dict( + subaccount_id=transaction.data.subaccount_id, + status=transaction.status.name, + asset_name=transaction.data.asset_name, + amount=transaction.data.data.amount, + ) + series = pd.Series(tx_data) + + print("\n=== Transaction ===") + print(series.to_string(index=True)) + if transaction.error_log: + print(f"\nError: {transaction.error_log.error}") + + +@transaction.command("deposit-to-subaccount") +@click.argument( + "amount", + type=Decimal, + required=True, +) +@click.argument( + "asset_name", + required=True, +) +@click.pass_context +def deposit_to_subaccount(ctx, amount: Decimal, asset_name: str): + """Deposit an asset to a subaccount.""" + + client = ctx.obj["client"] + subaccount = client.active_subaccount + deposit = subaccount.transactions.deposit_to_subaccount( + amount=amount, + asset_name=asset_name, + ) + + print(f"\n=== Deposit to subaccount {subaccount.id} ===") + print(struct_to_series(deposit).to_string(index=True)) + + +@transaction.command("withdraw-from-subaccount") +@click.argument( + "amount", + type=Decimal, + required=True, +) +@click.argument( + "asset_name", + required=True, +) +@click.pass_context +def withdraw_from_subaccount(ctx, amount: Decimal, asset_name: str): + """Withdraw an asset to wallet.""" + + client = ctx.obj["client"] + subaccount = client.active_subaccount + withdrawal = subaccount.transactions.withdraw_from_subaccount( + amount=amount, + asset_name=asset_name, + ) + + print(f"\n=== Withdrawal from subaccount {subaccount.id} ===") + print(struct_to_series(withdrawal).to_string(index=True)) From 11d83001f3dc4b0974cfa9af9a75da5244f3031f Mon Sep 17 00:00:00 2001 From: zarathustra Date: Thu, 30 Oct 2025 19:22:00 +0100 Subject: [PATCH 18/21] refactor: DERIVE_SUBACCOUNT -> DERIVE_SUBACCOUNT_ID --- .env.template | 2 +- derive_client/cli/_context.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.env.template b/.env.template index 037f0407..696dd40e 100644 --- a/.env.template +++ b/.env.template @@ -1,4 +1,4 @@ DERIVE_SESSION_KEY= DERIVE_WALLET= -DERIVE_SUBACCOUNT= +DERIVE_SUBACCOUNT_ID= DERIVE_ENV=PROD diff --git a/derive_client/cli/_context.py b/derive_client/cli/_context.py index 41f214f5..b94b0db3 100644 --- a/derive_client/cli/_context.py +++ b/derive_client/cli/_context.py @@ -24,7 +24,7 @@ def create_client( session_key = session_key_path.read_text().strip() if session_key_path else os.environ.get("DERIVE_SESSION_KEY") wallet = os.environ.get("DERIVE_WALLET") - subaccount_id = os.environ.get("DERIVE_SUBACCOUNT") + subaccount_id = os.environ.get("DERIVE_SUBACCOUNT_ID") env = Environment[os.environ.get("DERIVE_ENV", "PROD").upper()] missing = [] @@ -33,7 +33,7 @@ def create_client( if not wallet: missing.append("DERIVE_WALLET: Not found in environment variables.") if not subaccount_id: - missing.append("DERIVE_SUBACCOUNT: Not found in environment variables.") + missing.append("DERIVE_SUBACCOUNT_ID: Not found in environment variables.") if missing: error_msg = "Missing required configuration:\n\n" + "\n".join(f" • {m}" for m in missing) @@ -45,7 +45,7 @@ def create_client( error_msg += "\n\nProvide via environment variables or create a .env file with:" error_msg += "\n DERIVE_SESSION_KEY=" error_msg += "\n DERIVE_WALLET=" - error_msg += "\n DERIVE_SUBACCOUNT=" + error_msg += "\n DERIVE_SUBACCOUNT_ID=" error_msg += "\n DERIVE_ENV=PROD # optional, defaults to PROD" raise click.ClickException(error_msg) From 015e2d575693b38e5bd615e628363761d3d453f9 Mon Sep 17 00:00:00 2001 From: zarathustra Date: Thu, 30 Oct 2025 19:23:46 +0100 Subject: [PATCH 19/21] docs: update authentication, bridging and quickstart --- docs/getting-started/authentication.md | 8 +- docs/getting-started/bridging.md | 6 +- docs/getting-started/quickstart.md | 178 +++++++++++++++++-------- 3 files changed, 130 insertions(+), 62 deletions(-) diff --git a/docs/getting-started/authentication.md b/docs/getting-started/authentication.md index 381c3726..b307cbd7 100644 --- a/docs/getting-started/authentication.md +++ b/docs/getting-started/authentication.md @@ -24,7 +24,7 @@ At the moment, registration is done via the [Derive web app](https://derive.xyz) You can generate a private key using the helper script: ```shell -python ./scripts/create_new_pk.py +python ./scripts/create-new-private-key.py ``` Or use an existing key @@ -68,9 +68,9 @@ Or use an existing key You will need the following information to use the client: ```bash -export DERIVE_SESSION_PRIVATE_KEY=0x742d... # Required: EOA private key registered as a session key -export DERIVE_WALLET=0x8f5B... # Required: LightAccount (smart contract wallet) address -export DERIVE_SUBACCOUNT=123456 # Recommended: Subaccount ID (explicit) +export DERIVE_SESSION_KEY=0x742d... # Required: EOA private key registered as a session key +export DERIVE_WALLET=0x8f5B... # Required: LightAccount (smart contract wallet) address +export DERIVE_SUBACCOUNT_ID=123456 # Required: Subaccount ID ``` --- diff --git a/docs/getting-started/bridging.md b/docs/getting-started/bridging.md index fc903931..88c2028c 100644 --- a/docs/getting-started/bridging.md +++ b/docs/getting-started/bridging.md @@ -76,16 +76,14 @@ All bridge operations follow the same general lifecycle: 5. Wait for confirmation and finality on the target chain This flow is abstracted in the client. -You only need to call the high-level methods exposed on the `DeriveClient`. +You only need to call the high-level methods exposed on the `HTTPClient`. --- ## Usage -The client exposes bridging via high-level methods on `DeriveClient`. -Underlying bridge implementations live in the private `_bridge` module. +The client exposes bridging via high-level methods on `HTTPClient.bridge`. -- **We do not document the private modules here.** - Refer to [Quickstart](quickstart.md) or [Examples](examples/) for usage patterns and sample code. --- diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md index e4e7e899..9d9cde3f 100644 --- a/docs/getting-started/quickstart.md +++ b/docs/getting-started/quickstart.md @@ -1,24 +1,24 @@ # Quick Start -This guide will help you get started with the **Derive Python Client** using a minimal, synchronous HTTP setup. You will initialize your client, fund your subaccount, and prepare it for trading. +This guide walks you through funding your account and placing your first trade. > Assumes you have completed: > > - Installation ([installation.md](installation.md)) -> - Account registration ([authentication.md](authentication.md)) -> You should already have: -> - `DERIVE_SESSION_PRIVATE_KEY` -> - `DERIVE_WALLET` -> - `DERIVE_SUBACCOUNT` +> - Account registration ([authentication.md](authentication.md)) +> - You should already have: +> - `DERIVE_SESSION_KEY` +> - `DERIVE_WALLET` +> - `DERIVE_SUBACCOUNT_ID` ## 1. Set Up Environment Variables You can manage your credentials via a `.env` file to keep them out of your code: ```shell -DERIVE_SESSION_PRIVATE_KEY= +DERIVE_SESSION_KEY= DERIVE_WALLET= -DERIVE_SUBACCOUNT= +DERIVE_SUBACCOUNT_ID= ``` Then load them in Python using `python-dotenv` (or any preferred method): @@ -29,20 +29,21 @@ import os load_dotenv() -private_key = os.environ["DERIVE_SESSION_PRIVATE_KEY"] +session_key = os.environ["DERIVE_SESSION_KEY"] wallet = os.environ["DERIVE_WALLET"] -subaccount_id = os.environ.get("DERIVE_SUBACCOUNT") +subaccount_id = os.environ.get("DERIVE_SUBACCOUNT_ID") ``` -## 2. Initialize the Sync HTTP Client +## 2. Initialize the HTTPClient ```python -from derive_client import DeriveClient +from derive_client import HTTPClient, Environment -client = DeriveClient( - private_key=private_key, +client = HTTPClient( + session_key=session_key, wallet=wallet, - subaccount_id=subaccount_id + subaccount_id=subaccount_id, + env=Environment.PROD, ) ``` @@ -54,6 +55,12 @@ Async and WebSocket clients are available for high-concurrency or real-time work ## 3. Fund Your Account +**Quick Reference:** + +- **EOA** → Your externally owned account (needs ETH for gas to withdraw) +- **LightAccount** → Your smart contract wallet on Derive (holds bridged assets) +- **Subaccount** → Your trading account (where you place orders) + Trading requires funds in the right places. Here's the complete funding flow: ### Understanding Bridge Operations @@ -71,36 +78,38 @@ Each bridge operation follows a safe 3-step pattern: #### Step 1: Fund EOA with gas money -Bridge ETH to your EOA for gas fees (needed for withdrawals later): +Bridge ETH to your EOA on Derive for gas fees (needed for withdrawals later): ```python -from derive_client.data_types import ChainID, Currency - -# Bridge ETH for gas - ensures you can always withdraw funds -prepared_tx = client.prepare_standard_tx( - human_amount=0.01, - currency=Currency.ETH, - to: ..., # The LightAccount owner - source_chain=ChainID.BASE, - target_chain=ChainID.DERIVE, # Goes to LightAccount owner (EOA) -) -tx_result = client.submit_bridge_tx(prepared_tx=prepared_tx) -tx_result = client.poll_bridge_progress(tx_result=tx_result) +from derive_client.data_types import ChainID, Currency, D + +# D - convenience wrapper that returns Decimal from int, float or str +amount_eth = D(0.001) + +# Bridge ETH from ETH Mainnet to Derive - needed for gas fees when withdrawing later +prepared_tx = client.bridge.prepare_gas_deposit_tx(amount=amount_eth) + +# One may inspect the prepared transaction, including estimated gas costs, before submission +tx_result = client.bridge.submit_tx(prepared_tx=prepared_tx) + +# A BridgeTxResult, including the transaction hash on source chain, is immediately returned. +# This can be passed along for finality checks. +tx_result = client.bridge.poll_tx_progress(tx_result=tx_result) ``` +> **Note:** All trading operations on Derive (orders, transfers between subaccounts, rfq operations) are gasless - the Derive paymaster covers transaction costs. You only need ETH in your EOA for withdrawing funds back to external chains. + #### Step 2: Bridge Trading Assets -Bridge tokens to your LightAccount for trading: +Bridge assets to your LightAccount wallet for trading: ```python -# Bridge trading capital to LightAccount -from derive_client.data_types import ChainID, Currency +amount_usdc = D(100.0) -prepared_tx = client.prepare_deposit_to_derive( - human_amount=100.0, +prepared_tx = client.bridge.prepare_deposit_tx( + amount=amount_usdc, currency=Currency.USDC, - source_chain=ChainID.BASE, - target_chain=ChainID.DERIVE, # Goes to LightAccount + chain_id=ChainID.BASE, ) tx_result = client.submit_bridge_tx(prepared_tx=prepared_tx) tx_result = client.poll_bridge_progress(tx_result=tx_result) @@ -108,47 +117,108 @@ tx_result = client.poll_bridge_progress(tx_result=tx_result) #### Step 3: Transfer to Subaccount -Move funds from LightAccount to your trading subaccount: +Move funds from your LightAccount wallet to a subaccount to trade: ```python -from derive_client.data_types import Currency - -# Transfer to subaccount for trading -tx_result = client.transfer_to_subaccount( - amount=100.0, +tx_result = client.transactions.deposit_to_subaccount( + amount=amount_usdc, token=Currency.USDC, - subaccount_id=subaccount_id, ) ``` ## 4. Withdrawing Funds -When you want to move funds back to external chains: +Withdrawal is a two-step process in the reverse direction: ```python -from derive_client.data_types import ChainID, Currency - -# Withdraw from subaccount to LightAccount first -tx_result = client.transfer_from_subaccount( - amount=100.0, +# Step 1: Withdraw from subaccount to your LightAccount wallet +tx_result = client.transactions.withdraw_from_subaccount( + amount=amount_usdc, token=Currency.USDC, - subaccount_id=subaccount_id, ) -# Then bridge back to external chain -prepared_tx = client.prepare_withdrawal_from_derive( - human_amount=100.0, +# Step 2: Bridge to an external chain (funds sent to LightAccount wallet's owner address) +prepared_tx = client.bridge.prepare_withdrawal_tx( + amount=amount_usdc, currency=Currency.USDC, - source_chain=ChainID.DERIVE, - target_chain=ChainID.BASE, + chain_id=ChainID.BASE, ) tx_result = client.submit_bridge_tx(prepared_tx=prepared_tx) tx_result = client.poll_bridge_progress(tx_result=tx_result) ``` +## 5. Place Your First Trade + +Now that your subaccount is funded, let's place a simple order: + +### Check Available Markets + +```python +# Get current ticker for ETH perpetual +ticker = client.get_ticker(instrument_name="ETH-PERP") + +print(f"ETH-PERP bid: {ticker.best_bid_price}, ask: {ticker.best_ask_price}") +``` + +### Create a Limit Order + +```python +from derive_client.data.generated.models import Direction, OrderType + +# Place a limit buy order at $3000 +order = client.orders.create( + instrument_name="ETH-PERP", + amount=D("0.1"), # 0.1 ETH + limit_price=D("3000"), + direction=Direction.buy, + order_type=OrderType.limit, +) + +print(f"Order created: {order.order.order_id}") +print(f"Status: {order.order.order_status}") + +# if (part of) it filled immediately, a non-empty array of trades will be returned: +for trade in order.trades: + print(f"Trade: {trade.amount} @ tx id: {trade.transaction_id}) +``` + +### Check Order Status + +```python +# List all open orders +open_orders = client.orders.list_open() +for order in open_orders.orders: + print(f"{order.instrument_name}: {order.amount} @ {order.limit_price}") + +# Get specific order details +order_detail = client.orders.get(order_id=order.order_id) +print(f"Filled: {order_detail.filled_amount/order_detail.amount:.2f}") +``` + +### View Your Positions + +```python +# Check your positions +positions = client.positions.list() +for position in positions: + print(f"{position.instrument_name}: {position.amount} @ avg {position.average_price:.2f}") +``` + +### Cancel Orders + +```python +# Cancel a specific order +cancelled_order = client.orders.cancel(order_id=order.order_id, instrument_name="ETH-PERP") +print(f"Order status: {cancelled_order.order_status.value}, reason: {cancelled_order.cancel_reason.value}") + +# Or cancel all outstanding orders +client.orders.cancel_all() +``` + --- Next: - Start exploring example scripts for trading: [examples](examples/) - Refer to API documentation: [API Reference](reference/) + 1 From 571810b081628a19146b08042081b5755eaa94fc Mon Sep 17 00:00:00 2001 From: zarathustra Date: Thu, 30 Oct 2025 19:25:51 +0100 Subject: [PATCH 20/21] refactor: move properties up and private methods down on MarketOperations --- derive_client/_clients/rest/http/markets.py | 48 ++++++++++----------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/derive_client/_clients/rest/http/markets.py b/derive_client/_clients/rest/http/markets.py index 0d635d14..ca574c57 100644 --- a/derive_client/_clients/rest/http/markets.py +++ b/derive_client/_clients/rest/http/markets.py @@ -41,6 +41,30 @@ def __init__(self, *, public_api: PublicAPI, logger: Logger): self._perp_instruments_cache: dict[str, InstrumentPublicResponseSchema] = {} self._option_instruments_cache: dict[str, InstrumentPublicResponseSchema] = {} + @property + def erc20_instruments_cache(self) -> dict[str, InstrumentPublicResponseSchema]: + """Get cached ERC20 instruments.""" + + if not self._erc20_instruments_cache: + self.fetch_instruments(instrument_type=InstrumentType.erc20) + return self._erc20_instruments_cache + + @property + def perp_instruments_cache(self) -> dict[str, InstrumentPublicResponseSchema]: + """Get cached perpetual instruments.""" + + if not self._perp_instruments_cache: + self.fetch_instruments(instrument_type=InstrumentType.perp) + return self._perp_instruments_cache + + @property + def option_instruments_cache(self) -> dict[str, InstrumentPublicResponseSchema]: + """Get cached option instruments.""" + + if not self._option_instruments_cache: + self.fetch_instruments(instrument_type=InstrumentType.option) + return self._option_instruments_cache + def fetch_instruments( self, *, @@ -108,30 +132,6 @@ def _get_cache_for_type(self, instrument_type: InstrumentType) -> dict[str, Inst case _: raise TypeError(f"Unsupported instrument_type: {instrument_type!r}") - @property - def erc20_instruments_cache(self) -> dict[str, InstrumentPublicResponseSchema]: - """Get cached ERC20 instruments.""" - - if not self._erc20_instruments_cache: - self.fetch_instruments(instrument_type=InstrumentType.erc20) - return self._erc20_instruments_cache - - @property - def perp_instruments_cache(self) -> dict[str, InstrumentPublicResponseSchema]: - """Get cached perpetual instruments.""" - - if not self._perp_instruments_cache: - self.fetch_instruments(instrument_type=InstrumentType.perp) - return self._perp_instruments_cache - - @property - def option_instruments_cache(self) -> dict[str, InstrumentPublicResponseSchema]: - """Get cached option instruments.""" - - if not self._option_instruments_cache: - self.fetch_instruments(instrument_type=InstrumentType.option) - return self._option_instruments_cache - def _get_cached_instrument(self, *, instrument_name: str) -> InstrumentPublicResponseSchema: """Internal helper to retrieve an instrument from cache.""" From 7638cc9a907ba199d9b5bd8c670be3aad00aec2a Mon Sep 17 00:00:00 2001 From: zarathustra Date: Thu, 30 Oct 2025 19:31:46 +0100 Subject: [PATCH 21/21] fix: Makefile generate commands --- Makefile | 5 +- .../_clients/rest/async_http/markets.py | 48 +++++++++---------- derive_client/data/generated/models.py | 2 + 3 files changed, 29 insertions(+), 26 deletions(-) diff --git a/Makefile b/Makefile index c2510300..7950407c 100644 --- a/Makefile +++ b/Makefile @@ -68,17 +68,18 @@ release: .PHONY: generate-models generate-models: python scripts/generate-models.py - poetry run ruff format derive_client/data/generated/models.py poetry run ruff check --fix derive_client/data/generated/models.py + poetry run ruff format derive_client/data/generated/models.py .PHONY: generate-rest-api generate-rest-api: python scripts/generate-rest-api.py poetry run ruff format derive_client/_clients/rest/ poetry run ruff check --fix derive_client/_clients/rest/ + poetry run ruff format derive_client/_clients/rest/ .PHONY: generate-rest-async-http generate-rest-async-http: python scripts/generate-rest-async-http.py + poetry run ruff check --fix tests/test_clients/test_rest/test_async_http poetry run ruff format tests/test_clients/test_rest/test_async_http - poetry run ruff check --fix tests/test_clients/test_rest/test_async_http \ No newline at end of file diff --git a/derive_client/_clients/rest/async_http/markets.py b/derive_client/_clients/rest/async_http/markets.py index e72c71dc..3bec7f8d 100644 --- a/derive_client/_clients/rest/async_http/markets.py +++ b/derive_client/_clients/rest/async_http/markets.py @@ -41,6 +41,30 @@ def __init__(self, *, public_api: AsyncPublicAPI, logger: Logger): self._perp_instruments_cache: dict[str, InstrumentPublicResponseSchema] = {} self._option_instruments_cache: dict[str, InstrumentPublicResponseSchema] = {} + @property + def erc20_instruments_cache(self) -> dict[str, InstrumentPublicResponseSchema]: + """Get cached ERC20 instruments.""" + + if not self._erc20_instruments_cache: + raise RuntimeError("Call fetch_instruments() or fetch_all_instruments() to create the erc20_instruments_cache.") + return self._erc20_instruments_cache + + @property + def perp_instruments_cache(self) -> dict[str, InstrumentPublicResponseSchema]: + """Get cached perpetual instruments.""" + + if not self._perp_instruments_cache: + raise RuntimeError("Call fetch_instruments() or fetch_all_instruments() to create the perp_instruments_cache.") + return self._perp_instruments_cache + + @property + def option_instruments_cache(self) -> dict[str, InstrumentPublicResponseSchema]: + """Get cached option instruments.""" + + if not self._option_instruments_cache: + raise RuntimeError("Call fetch_instruments() or fetch_all_instruments() to create the option_instruments_cache.") + return self._option_instruments_cache + async def fetch_instruments( self, *, @@ -108,30 +132,6 @@ def _get_cache_for_type(self, instrument_type: InstrumentType) -> dict[str, Inst case _: raise TypeError(f"Unsupported instrument_type: {instrument_type!r}") - @property - def erc20_instruments_cache(self) -> dict[str, InstrumentPublicResponseSchema]: - """Get cached ERC20 instruments.""" - - if not self._erc20_instruments_cache: - raise RuntimeError("Call fetch_instruments() or fetch_all_instruments() to create the erc20_instruments_cache.") - return self._erc20_instruments_cache - - @property - def perp_instruments_cache(self) -> dict[str, InstrumentPublicResponseSchema]: - """Get cached perpetual instruments.""" - - if not self._perp_instruments_cache: - raise RuntimeError("Call fetch_instruments() or fetch_all_instruments() to create the perp_instruments_cache.") - return self._perp_instruments_cache - - @property - def option_instruments_cache(self) -> dict[str, InstrumentPublicResponseSchema]: - """Get cached option instruments.""" - - if not self._option_instruments_cache: - raise RuntimeError("Call fetch_instruments() or fetch_all_instruments() to create the option_instruments_cache.") - return self._option_instruments_cache - def _get_cached_instrument(self, *, instrument_name: str) -> InstrumentPublicResponseSchema: """Internal helper to retrieve an instrument from cache.""" diff --git a/derive_client/data/generated/models.py b/derive_client/data/generated/models.py index 31f4ae8f..e46204fa 100644 --- a/derive_client/data/generated/models.py +++ b/derive_client/data/generated/models.py @@ -1,8 +1,10 @@ # ruff: noqa: E741 from __future__ import annotations + from decimal import Decimal from enum import Enum from typing import Any, Dict, List, Optional, Union + from msgspec import Struct