From 5d72c059d206fd597db608a214cdd731fdf73e28 Mon Sep 17 00:00:00 2001 From: Artur Michalek Date: Thu, 3 Jul 2025 17:11:33 +0200 Subject: [PATCH 01/32] Bump pyright --- poetry.lock | 66 ++++++++++++++++++++++++++------------------------ pyproject.toml | 2 +- 2 files changed, 35 insertions(+), 33 deletions(-) diff --git a/poetry.lock b/poetry.lock index 7a764d8ef..23e856595 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 2.0.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 2.1.3 and should not be changed by hand. [[package]] name = "aiohappyeyeballs" @@ -109,7 +109,7 @@ propcache = ">=0.2.0" yarl = ">=1.17.0,<2.0" [package.extras] -speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] +speedups = ["Brotli ; platform_python_implementation == \"CPython\"", "aiodns (>=3.2.0) ; sys_platform == \"linux\" or sys_platform == \"darwin\"", "brotlicffi ; platform_python_implementation != \"CPython\""] [[package]] name = "aiosignal" @@ -238,12 +238,12 @@ files = [ ] [package.extras] -benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +benchmark = ["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-codspeed", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] +cov = ["cloudpickle ; platform_python_implementation == \"CPython\"", "coverage[toml] (>=5.3)", "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]"] +dev = ["cloudpickle ; platform_python_implementation == \"CPython\"", "hypothesis", "mypy (>=1.11.1) ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins ; platform_python_implementation == \"CPython\" and python_version >= \"3.10\"", "pytest-xdist[psutil]"] docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier (<24.7)"] -tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] -tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] +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 = "autodocsumm" @@ -343,7 +343,7 @@ typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +d = ["aiohttp (>=3.7.4) ; sys_platform != \"win32\" or implementation_name != \"pypy\"", "aiohttp (>=3.7.4,!=3.9.0) ; sys_platform == \"win32\" and implementation_name == \"pypy\""] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] @@ -696,7 +696,7 @@ files = [ tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} [package.extras] -toml = ["tomli"] +toml = ["tomli ; python_full_version <= \"3.11.0a6\""] [[package]] name = "crypto-cpp-py" @@ -851,7 +851,7 @@ more-itertools = "*" [package.extras] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] -test = ["cssselect", "importlib-resources", "jaraco.test (>=5.1)", "lxml", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] +test = ["cssselect", "importlib-resources ; python_version < \"3.9\"", "jaraco.test (>=5.1)", "lxml ; python_version < \"3.11\"", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] [[package]] name = "cytoolz" @@ -1096,7 +1096,7 @@ files = [ dev = ["build (>=0.9.0)", "bumpversion (>=0.5.3)", "ipython", "pre-commit (>=3.4.0)", "pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)", "sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)", "tox (>=4.0.0)", "twine", "wheel"] docs = ["sphinx (>=6.0.0)", "sphinx-rtd-theme (>=1.0.0)", "towncrier (>=21,<22)"] pycryptodome = ["pycryptodome (>=3.6.6,<4)"] -pysha3 = ["pysha3 (>=1.0.0,<2.0.0)", "safe-pysha3 (>=1.0.0)"] +pysha3 = ["pysha3 (>=1.0.0,<2.0.0) ; python_version < \"3.9\"", "safe-pysha3 (>=1.0.0) ; python_version >= \"3.9\""] test = ["pytest (>=7.0.0)", "pytest-xdist (>=2.4.0)"] [[package]] @@ -1233,7 +1233,7 @@ files = [ [package.extras] docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2.4.1)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"] -typing = ["typing-extensions (>=4.12.2)"] +typing = ["typing-extensions (>=4.12.2) ; python_version < \"3.11\""] [[package]] name = "frozenlist" @@ -1457,10 +1457,10 @@ six = ">=1.9" webencodings = "*" [package.extras] -all = ["chardet (>=2.2)", "genshi", "lxml"] +all = ["chardet (>=2.2)", "genshi", "lxml ; platform_python_implementation == \"CPython\""] chardet = ["chardet (>=2.2)"] genshi = ["genshi"] -lxml = ["lxml"] +lxml = ["lxml ; platform_python_implementation == \"CPython\""] [[package]] name = "idna" @@ -1494,7 +1494,7 @@ description = "Read metadata from Python packages" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version < \"3.10\"" +markers = "python_version == \"3.9\"" files = [ {file = "importlib_metadata-8.4.0-py3-none-any.whl", hash = "sha256:66f342cc6ac9818fc6ff340576acd24d65ba0b3efabb2b4ac08b598965a4a2f1"}, {file = "importlib_metadata-8.4.0.tar.gz", hash = "sha256:9a547d3bc3608b025f93d403fdd1aae741c24fbb8314df4b155675742ce303c5"}, @@ -1506,7 +1506,7 @@ zipp = ">=0.5" [package.extras] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] perf = ["ipython"] -test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] +test = ["flufl.flake8", "importlib-resources (>=1.3) ; python_version < \"3.9\"", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] [[package]] name = "iniconfig" @@ -1721,10 +1721,10 @@ typing-extensions = {version = ">=4.2.0", markers = "python_version < \"3.11\""} typing-inspect = ">=0.9.0" [package.extras] -dev = ["pre-commit (>=2.17,<3.0)", "pytest (>=5.4)", "pytest-mypy-plugins (>=1.2.0)", "sphinx"] +dev = ["pre-commit (>=2.17,<3.0)", "pytest (>=5.4)", "pytest-mypy-plugins (>=1.2.0) ; implementation_name != \"pypy\"", "sphinx"] docs = ["sphinx"] lint = ["pre-commit (>=2.17,<3.0)"] -tests = ["pytest (>=5.4)", "pytest-mypy-plugins (>=1.2.0)"] +tests = ["pytest (>=5.4)", "pytest-mypy-plugins (>=1.2.0) ; implementation_name != \"pypy\""] [[package]] name = "marshmallow-oneofschema" @@ -1785,7 +1785,7 @@ files = [ [package.extras] develop = ["codecov", "pycodestyle", "pytest (>=4.6)", "pytest-cov", "wheel"] docs = ["sphinx"] -gmpy = ["gmpy2 (>=2.1.0a4)"] +gmpy = ["gmpy2 (>=2.1.0a4) ; platform_python_implementation != \"PyPy\""] tests = ["pytest (>=4.6)"] [[package]] @@ -2141,7 +2141,7 @@ docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline fpx = ["olefile"] mic = ["olefile"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] -typing = ["typing-extensions"] +typing = ["typing-extensions ; python_version < \"3.10\""] xmp = ["defusedxml"] [[package]] @@ -2487,7 +2487,7 @@ colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = [ {version = ">=0.2", markers = "python_version < \"3.11\""}, {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, - {version = ">=0.3.6", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, + {version = ">=0.3.6", markers = "python_version == \"3.11\""}, ] isort = ">=4.2.5,<5.13.0 || >5.13.0,<6" mccabe = ">=0.6,<0.8" @@ -2502,22 +2502,24 @@ testutils = ["gitpython (>3)"] [[package]] name = "pyright" -version = "1.1.371" +version = "1.1.402" description = "Command line wrapper for pyright" optional = false python-versions = ">=3.7" groups = ["dev"] files = [ - {file = "pyright-1.1.371-py3-none-any.whl", hash = "sha256:cce52e42ff73943243e7e5e24f2a59dee81b97d99f4e3cf97370b27e8a1858cd"}, - {file = "pyright-1.1.371.tar.gz", hash = "sha256:777b508b92dda2db476214c400ce043aad8d8f3dd0e10d284c96e79f298308b5"}, + {file = "pyright-1.1.402-py3-none-any.whl", hash = "sha256:2c721f11869baac1884e846232800fe021c33f1b4acb3929cff321f7ea4e2982"}, + {file = "pyright-1.1.402.tar.gz", hash = "sha256:85a33c2d40cd4439c66aa946fd4ce71ab2f3f5b8c22ce36a623f59ac22937683"}, ] [package.dependencies] nodeenv = ">=1.6.0" +typing-extensions = ">=4.1" [package.extras] -all = ["twine (>=3.4.1)"] +all = ["nodejs-wheel-binaries", "twine (>=3.4.1)"] dev = ["twine (>=3.4.1)"] +nodejs = ["nodejs-wheel-binaries"] [[package]] name = "pytest" @@ -2789,7 +2791,7 @@ files = [ [package.extras] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.10.0)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21) ; python_version >= \"3.9\" and sys_platform != \"cygwin\"", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.10.0)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf ; sys_platform != \"cygwin\"", "pytest-ruff (>=0.3.2) ; sys_platform != \"cygwin\"", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "six" @@ -3199,7 +3201,7 @@ typing-extensions = ">=4.10.0" [package.extras] doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.3.0)"] -test = ["coverage[toml] (>=7)", "mypy (>=1.2.0)", "pytest (>=7)"] +test = ["coverage[toml] (>=7)", "mypy (>=1.2.0) ; platform_python_implementation != \"PyPy\"", "pytest (>=7)"] [[package]] name = "typing-extensions" @@ -3243,7 +3245,7 @@ files = [ ] [package.extras] -brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +brotli = ["brotli (>=1.0.9) ; platform_python_implementation == \"CPython\"", "brotlicffi (>=0.8.0) ; platform_python_implementation != \"CPython\""] h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] @@ -3444,18 +3446,18 @@ description = "Backport of pathlib-compatible object wrapper for zip files" optional = false python-versions = ">=3.8" groups = ["main"] -markers = "python_version < \"3.10\"" +markers = "python_version == \"3.9\"" files = [ {file = "zipp-3.20.1-py3-none-any.whl", hash = "sha256:9960cd8967c8f85a56f920d5d507274e74f9ff813a0ab8889a5b5be2daf44064"}, {file = "zipp-3.20.1.tar.gz", hash = "sha256:c22b14cc4763c5a5b04134207736c107db42e9d3ef2d9779d465f5f1bcba572b"}, ] [package.extras] -check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1) ; sys_platform != \"cygwin\""] cover = ["pytest-cov"] doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] enabler = ["pytest-enabler (>=2.2)"] -test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +test = ["big-O", "importlib-resources ; python_version < \"3.9\"", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] type = ["pytest-mypy"] [extras] @@ -3465,4 +3467,4 @@ ledger = ["ledgerwallet"] [metadata] lock-version = "2.1" python-versions = ">=3.9, <3.13" -content-hash = "0ec0be1022052143c49b799e947f074660065273abac162cb398140e82ebc77b" +content-hash = "978b5ee9e3df05c2b64b4047ea2fb78e3f8bfca54e09004b97b17d800c010a5b" diff --git a/pyproject.toml b/pyproject.toml index cd1c122f9..de1d73f7b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,7 @@ pytest-asyncio = "^0.21.1" pylint = "3.2.5" pytest-mock = "^3.6.1" pytest-xdist = "^3.2.1" -pyright = "1.1.371" +pyright = "1.1.402" pytest-cov = "^5.0.0" isort = "^5.11.4" pytest-rerunfailures = "^14.0" From f636cddacb1f30e24dd2b775b7f7534d83d21a7e Mon Sep 17 00:00:00 2001 From: Artur Michalek Date: Thu, 3 Jul 2025 17:13:48 +0200 Subject: [PATCH 02/32] RPC 0.9.0-rc.1 changes --- starknet_py/contract.py | 2 +- starknet_py/devnet_utils/devnet_client.py | 12 ++- starknet_py/net/account/account.py | 4 +- starknet_py/net/account/base_account.py | 12 +-- starknet_py/net/client.py | 68 +++++++----- starknet_py/net/client_models.py | 29 ++--- starknet_py/net/client_utils.py | 15 ++- starknet_py/net/full_node_client.py | 100 ++++++++++-------- starknet_py/net/schemas/rpc/block.py | 36 +++---- starknet_py/net/schemas/rpc/transactions.py | 3 + starknet_py/tests/e2e/block_test.py | 22 ++-- starknet_py/tests/e2e/client/client_test.py | 12 --- .../e2e/tests_on_networks/client_test.py | 32 +++--- .../tests/unit/net/schemas/common_test.py | 8 +- starknet_py/transaction_errors.py | 9 -- 15 files changed, 191 insertions(+), 173 deletions(-) diff --git a/starknet_py/contract.py b/starknet_py/contract.py index 9fd301934..25966d5d5 100644 --- a/starknet_py/contract.py +++ b/starknet_py/contract.py @@ -345,7 +345,7 @@ async def estimate_fee( :param block_hash: Estimate fee at specific block hash. :param block_number: Estimate fee at given block number - (or "latest" / "pending" for the latest / pending block), default is "pending". + (or "latest" / "pre_confirmed" for the latest / pre_confirmed block), default is "pre_confirmed". :param nonce: Nonce of the transaction. :return: Estimated amount of the transaction cost, either in Wei or Fri associated with executing the specified transaction. diff --git a/starknet_py/devnet_utils/devnet_client.py b/starknet_py/devnet_utils/devnet_client.py index ba2c83fa9..1850ec655 100644 --- a/starknet_py/devnet_utils/devnet_client.py +++ b/starknet_py/devnet_utils/devnet_client.py @@ -123,7 +123,7 @@ async def get_account_balance( :param address: Address of the account contract. :param unit: Literals `"FRI"` or `"WEI"` defaults to `"WEI"`. - :param block_tag: Literals `"pending"` or `"latest"`, defaults to `"latest"`. + :param block_tag: Literals `"pre_confirmed"` or `"latest"`, defaults to `"latest"`. """ res = await self._devnet_client.call( @@ -156,9 +156,9 @@ async def abort_block( It is supported in the `--state-archive-capacity full` mode. :param block_number: Number of the block which the state of Devnet will be reverted to - or literals `"pending"` or `"latest"`. + or literals `"pre_confirmed"` or `"latest"`. :param block_hash: Hash of the block which the state of Devnet will be reverted to - or literals `"pending"` or `"latest"` + or literals `"pre_confirmed"` or `"latest"` """ res = await self._devnet_client.call( @@ -341,7 +341,8 @@ async def get_config(self) -> Config: async def increase_time(self, time: int) -> IncreaseTimeResponse: """ - (Only possible if there are no pending transactions) + # TODO update description based on new devnet behavior changes + (Only possible if there are no pre_confirmed transactions) Increases the block timestamp by the provided amount and generates a new block. All subsequent blocks will keep this increment. @@ -358,7 +359,8 @@ async def set_time( self, time: int, generate_block: bool = False ) -> SetTimeResponse: """ - Set the time of the devnet. Only available when there is no pending transaction. + # TODO update description based on new devnet behavior changes + Set the time of the devnet. Only available when there is no pre_confirmed transaction. Warning: block time can be set in the past and lead to unexpected behaviour! :param time: Time to set in seconds. (Unix time) diff --git a/starknet_py/net/account/account.py b/starknet_py/net/account/account.py index 52011dadb..702518f34 100644 --- a/starknet_py/net/account/account.py +++ b/starknet_py/net/account/account.py @@ -226,8 +226,8 @@ async def get_nonce( """ Get the current nonce of the account. - :param block_hash: Block's hash or literals `"pending"` or `"latest"` - :param block_number: Block's number or literals `"pending"` or `"latest"` + :param block_hash: Block's hash or literals `"pre_confirmed"` or `"latest"` + :param block_number: Block's number or literals `"pre_confirmed"` or `"latest"` :return: nonce. """ return await self._client.get_contract_nonce( diff --git a/starknet_py/net/account/base_account.py b/starknet_py/net/account/base_account.py index 23afb3fb5..5c45ebd19 100644 --- a/starknet_py/net/account/base_account.py +++ b/starknet_py/net/account/base_account.py @@ -106,8 +106,8 @@ async def estimate_fee( :param tx: Transaction or list of transactions to estimate :param skip_validate: Flag checking whether the validation part of the transaction should be executed - :param block_hash: Block hash or literals `"pending"` or `"latest"` - :param block_number: Block number or literals `"pending"` or `"latest"` + :param block_hash: Block hash or literals `"pre_confirmed"` or `"latest"` + :param block_number: Block number or literals `"pre_confirmed"` or `"latest"` :return: Estimated fee or list of estimated fees for each transaction """ @@ -121,8 +121,8 @@ async def get_nonce( """ Get the current nonce of the account. - :param block_hash: Block's hash or literals `"pending"` or `"latest"` - :param block_number: Block's number or literals `"pending"` or `"latest"` + :param block_hash: Block's hash or literals `"pre_confirmed"` or `"latest"` + :param block_number: Block's number or literals `"pre_confirmed"` or `"latest"` :return: nonce of the account. """ @@ -139,8 +139,8 @@ async def get_balance( By default, it uses the L2 ETH address for mainnet and sepolia networks. :param token_address: Address of the ERC20 contract. - :param block_hash: Block's hash or literals `"pending"` or `"latest"` - :param block_number: Block's number or literals `"pending"` or `"latest"` + :param block_hash: Block's hash or literals `"pre_confirmed"` or `"latest"` + :param block_number: Block's number or literals `"pre_confirmed"` or `"latest"` :return: Token balance. """ diff --git a/starknet_py/net/client.py b/starknet_py/net/client.py index 1c62906da..346037bed 100644 --- a/starknet_py/net/client.py +++ b/starknet_py/net/client.py @@ -16,8 +16,10 @@ EstimatedFee, Hash, MessageStatus, - PendingBlockStateUpdate, - PendingStarknetBlock, + PreConfirmedBlockStateUpdate, + PreConfirmedStarknetBlock, + PreConfirmedStarknetBlockWithReceipts, + PreConfirmedStarknetBlockWithTxHashes, SentTransactionResponse, SierraContractClass, StarknetBlock, @@ -38,7 +40,6 @@ ) from starknet_py.transaction_errors import ( TransactionNotReceivedError, - TransactionRejectedError, TransactionRevertedError, ) from starknet_py.utils.sync import add_sync_methods @@ -51,12 +52,12 @@ async def get_block( self, block_hash: Optional[Union[Hash, Tag]] = None, block_number: Optional[Union[int, Tag]] = None, - ) -> Union[StarknetBlock, PendingStarknetBlock]: + ) -> Union[StarknetBlock, PreConfirmedStarknetBlock]: """ Retrieve the block's data by its number or hash - :param block_hash: Block's hash or literals `"pending"` or `"latest"` - :param block_number: Block's number or literals `"pending"` or `"latest"` + :param block_hash: Block's hash or literals `"pre_confirmed"` or `"latest"` + :param block_number: Block's number or literals `"pre_confirmed"` or `"latest"` :return: StarknetBlock object representing retrieved block """ @@ -70,7 +71,7 @@ async def trace_block_transactions( Receive the traces of all the transactions within specified block :param block_hash: Block's hash - :param block_number: Block's number or "pending" for pending block + :param block_number: Block's number or "pre_confirmed" for pre_confirmed block :return: BlockTransactionTraces object representing received traces """ @@ -79,12 +80,12 @@ async def get_state_update( self, block_hash: Optional[Union[Hash, Tag]] = None, block_number: Optional[Union[int, Tag]] = None, - ) -> Union[BlockStateUpdate, PendingBlockStateUpdate]: + ) -> Union[BlockStateUpdate, PreConfirmedBlockStateUpdate]: """ Get the information about the result of executing the requested block - :param block_hash: Block's hash or literals `"pending"` or `"latest"` - :param block_number: Block's number or literals `"pending"` or `"latest"` + :param block_hash: Block's hash or literals `"pre_confirmed"` or `"latest"` + :param block_number: Block's number or literals `"pre_confirmed"` or `"latest"` :return: BlockStateUpdate object representing changes in the requested block """ @@ -99,15 +100,16 @@ async def get_storage_at( """ :param contract_address: Contract's address on Starknet :param key: An address of the storage variable inside the contract. - :param block_hash: Block's hash or literals `"pending"` or `"latest"` - :param block_number: Block's number or literals `"pending"` or `"latest"` + :param block_hash: Block's hash or literals `"pre_confirmed"` or `"latest"` + :param block_number: Block's number or literals `"pre_confirmed"` or `"latest"` :return: Storage value of given contract """ @abstractmethod async def get_storage_proof( self, - block_id: Union[int, Hash, Tag, dict], + block_hash: Optional[Union[Hash, Tag]] = None, + block_number: Optional[Union[int, Tag]] = None, class_hashes: Optional[List[int]] = None, contract_addresses: Optional[List[int]] = None, contracts_storage_keys: Optional[List[ContractsStorageKeys]] = None, @@ -115,7 +117,8 @@ async def get_storage_proof( """ Get merkle paths in one of the state tries: global state, classes, individual contract. - :param block_id: Hash of the requested block, or number (height) of the requested block, or a block tag. + :param block_hash: Block's hash or literals `"pre_confirmed"` or `"latest"` + :param block_number: Block's number or literals `"pre_confirmed"` or `"latest"` :param class_hashes: List of the class hashes for which we want to prove membership in the classes trie. :param contract_addresses: List of the contract addresses for which we want to prove membership in the contracts trie. @@ -185,10 +188,23 @@ async def wait_for_tx( if not transaction_received: tx_status = await self.get_transaction_status(tx_hash=tx_hash) - if tx_status.finality_status == TransactionStatus.REJECTED: - raise TransactionRejectedError() - - transaction_received = True + if ( + tx_status.execution_status + == TransactionExecutionStatus.REVERTED + ): + raise TransactionRevertedError( + message=( + tx_status.failure_reason + if tx_status.failure_reason is not None + else "Transaction reverted with unknown reason." + ) + ) + + if tx_status.finality_status in ( + TransactionStatus.ACCEPTED_ON_L2, + TransactionStatus.ACCEPTED_ON_L1, + ): + transaction_received = True else: tx_receipt = await self.get_transaction_receipt(tx_hash=tx_hash) @@ -231,8 +247,8 @@ async def estimate_fee( :param tx: Transaction to estimate :param skip_validate: Flag checking whether the validation part of the transaction should be executed. - :param block_hash: Block's hash or literals `"pending"` or `"latest"`. - :param block_number: Block's number or literals `"pending"` or `"latest"`. + :param block_hash: Block's hash or literals `"pre_confirmed"` or `"latest"`. + :param block_number: Block's number or literals `"pre_confirmed"` or `"latest"`. :return: Estimated amount of Wei executing specified transaction will cost. """ @@ -247,8 +263,8 @@ async def call_contract( Call the contract with given instance of InvokeTransaction :param call: Call - :param block_hash: Block's hash or literals `"pending"` or `"latest"` - :param block_number: Block's number or literals `"pending"` or `"latest"` + :param block_hash: Block's hash or literals `"pre_confirmed"` or `"latest"` + :param block_number: Block's number or literals `"pre_confirmed"` or `"latest"` :return: List of integers representing contract's function output (structured like calldata) """ @@ -295,8 +311,8 @@ async def get_class_hash_at( Get the contract class hash for the contract deployed at the given address :param contract_address: Address of the contract whose class hash is to be returned - :param block_hash: Block's hash or literals `"pending"` or `"latest"` - :param block_number: Block's number or literals `"pending"` or `"latest"` + :param block_hash: Block's hash or literals `"pre_confirmed"` or `"latest"` + :param block_number: Block's number or literals `"pre_confirmed"` or `"latest"` :return: Class hash """ @@ -322,8 +338,8 @@ async def get_contract_nonce( Get the latest nonce associated with the given address :param contract_address: Get the latest nonce associated with the given address - :param block_hash: Block's hash or literals `"pending"` or `"latest"` - :param block_number: Block's number or literals `"pending"` or `"latest"` + :param block_hash: Block's hash or literals `"pre_confirmed"` or `"latest"` + :param block_number: Block's number or literals `"pre_confirmed"` or `"latest"` :return: The last nonce used for the given contract """ diff --git a/starknet_py/net/client_models.py b/starknet_py/net/client_models.py index 56f72e179..0395cb7be 100644 --- a/starknet_py/net/client_models.py +++ b/starknet_py/net/client_models.py @@ -32,7 +32,7 @@ # pylint: disable=too-many-lines Hash = Union[int, str] -Tag = Literal["pending", "latest"] +Tag = Literal["pre_confirmed", "latest"] LatestTag = Literal["latest"] @@ -396,7 +396,8 @@ class TransactionStatus(Enum): """ RECEIVED = "RECEIVED" - REJECTED = "REJECTED" + CANDIDATE = "CANDIDATE" + PRE_CONFIRMED = "PRE_CONFIRMED" ACCEPTED_ON_L2 = "ACCEPTED_ON_L2" ACCEPTED_ON_L1 = "ACCEPTED_ON_L1" @@ -415,6 +416,7 @@ class TransactionFinalityStatus(Enum): Enum representing transaction finality statuses. """ + PRE_CONFIRMED = "PRE_CONFIRMED" ACCEPTED_ON_L2 = "ACCEPTED_ON_L2" ACCEPTED_ON_L1 = "ACCEPTED_ON_L1" @@ -507,16 +509,16 @@ class BlockStatus(Enum): Enum representing block status. """ - PENDING = "PENDING" + PRE_CONFIRMED = "PRE_CONFIRMED" REJECTED = "REJECTED" ACCEPTED_ON_L2 = "ACCEPTED_ON_L2" ACCEPTED_ON_L1 = "ACCEPTED_ON_L1" @dataclass -class PendingBlockHeader: +class PreConfirmedBlockHeader: # pylint: disable=too-many-instance-attributes - parent_hash: int + block_number: int timestamp: int sequencer_address: int l1_gas_price: ResourcePrice @@ -527,27 +529,27 @@ class PendingBlockHeader: @dataclass -class PendingStarknetBlock(PendingBlockHeader): +class PreConfirmedStarknetBlock(PreConfirmedBlockHeader): """ - Dataclass representing a pending block on Starknet. + Dataclass representing a pre_confirmed block on Starknet. """ transactions: List[Transaction] @dataclass -class PendingStarknetBlockWithTxHashes(PendingBlockHeader): +class PreConfirmedStarknetBlockWithTxHashes(PreConfirmedBlockHeader): """ - Dataclass representing a pending block on Starknet containing transaction hashes. + Dataclass representing a pre_confirmed block on Starknet containing transaction hashes. """ transactions: List[int] @dataclass -class PendingStarknetBlockWithReceipts(PendingBlockHeader): +class PreConfirmedStarknetBlockWithReceipts(PreConfirmedBlockHeader): """ - Dataclass representing a pending block on Starknet with txs and receipts result + Dataclass representing a pre_confirmed block on Starknet with txs and receipts result """ transactions: List[TransactionWithReceipt] @@ -763,9 +765,9 @@ class BlockStateUpdate: @dataclass -class PendingBlockStateUpdate: +class PreConfirmedBlockStateUpdate: """ - Dataclass representing a pending change in state of a block. + Dataclass representing a pre_confirmed change in state of a block. """ old_root: int @@ -1206,6 +1208,7 @@ class StorageProofResponse: class MessageStatus: transaction_hash: int finality_status: TransactionStatus + execution_status: Optional[TransactionExecutionStatus] = None failure_reason: Optional[str] = None diff --git a/starknet_py/net/client_utils.py b/starknet_py/net/client_utils.py index 040265bf3..583886bfe 100644 --- a/starknet_py/net/client_utils.py +++ b/starknet_py/net/client_utils.py @@ -91,24 +91,31 @@ def _create_broadcasted_txn(transaction: AccountTransaction) -> dict: def get_block_identifier( block_hash: Optional[Union[Hash, Tag]] = None, block_number: Optional[Union[int, Tag]] = None, - default_tag: Optional[Tag] = "pending", + default_tag: Optional[Tag] = "pre_confirmed", + allow_pre_confirmed: bool = True, ) -> dict: - return { + block_id = { "block_id": _get_raw_block_identifier(block_hash, block_number, default_tag) } + if not allow_pre_confirmed and block_id == {"block_id": "pre_confirmed"}: + raise ValueError("Value pre_confirmed is not valid for this method.") + return block_id def _get_raw_block_identifier( block_hash: Optional[Union[Hash, Tag]] = None, block_number: Optional[Union[int, Tag]] = None, - default_tag: Optional[Tag] = "pending", + default_tag: Optional[Tag] = "pre_confirmed", ) -> Union[dict, Hash, Tag, None]: if block_hash is not None and block_number is not None: raise ValueError( "Arguments block_hash and block_number are mutually exclusive." ) - if block_hash in ("latest", "pending") or block_number in ("latest", "pending"): + if block_hash in ("latest", "pre_confirmed") or block_number in ( + "latest", + "pre_confirmed", + ): return block_hash or block_number if block_hash is not None: diff --git a/starknet_py/net/full_node_client.py b/starknet_py/net/full_node_client.py index 0ea8e55ab..7fa30241d 100644 --- a/starknet_py/net/full_node_client.py +++ b/starknet_py/net/full_node_client.py @@ -20,10 +20,10 @@ Hash, L1HandlerTransaction, MessageStatus, - PendingBlockStateUpdate, - PendingStarknetBlock, - PendingStarknetBlockWithReceipts, - PendingStarknetBlockWithTxHashes, + PreConfirmedBlockStateUpdate, + PreConfirmedStarknetBlock, + PreConfirmedStarknetBlockWithReceipts, + PreConfirmedStarknetBlockWithTxHashes, SentTransactionResponse, SierraContractClass, SimulatedTransaction, @@ -43,6 +43,7 @@ _create_broadcasted_txn, _get_raw_block_identifier, _is_valid_eth_address, + _to_hex_number, _to_rpc_felt, _to_storage_key, encode_l1_message, @@ -60,10 +61,10 @@ from starknet_py.net.schemas.rpc.block import ( BlockHashAndNumberSchema, BlockStateUpdateSchema, - PendingBlockStateUpdateSchema, - PendingStarknetBlockSchema, - PendingStarknetBlockWithReceiptsSchema, - PendingStarknetBlockWithTxHashesSchema, + PreConfirmedBlockStateUpdateSchema, + PreConfirmedStarknetBlockSchema, + PreConfirmedStarknetBlockWithReceiptsSchema, + PreConfirmedStarknetBlockWithTxHashesSchema, StarknetBlockSchema, StarknetBlockWithReceiptsSchema, StarknetBlockWithTxHashesSchema, @@ -117,7 +118,7 @@ async def get_block( self, block_hash: Optional[Union[Hash, Tag]] = None, block_number: Optional[Union[int, Tag]] = None, - ) -> Union[StarknetBlock, PendingStarknetBlock]: + ) -> Union[StarknetBlock, PreConfirmedStarknetBlock]: block_identifier = get_block_identifier( block_hash=block_hash, block_number=block_number ) @@ -127,22 +128,24 @@ async def get_block( params=block_identifier, ) - if block_identifier == {"block_id": "pending"}: - return cast(PendingStarknetBlock, PendingStarknetBlockSchema().load(res)) + if block_identifier == {"block_id": "pre_confirmed"}: + return cast( + PreConfirmedStarknetBlock, PreConfirmedStarknetBlockSchema().load(res) + ) return cast(StarknetBlock, StarknetBlockSchema().load(res)) async def get_block_with_txs( self, block_hash: Optional[Union[Hash, Tag]] = None, block_number: Optional[Union[int, Tag]] = None, - ) -> Union[StarknetBlock, PendingStarknetBlock]: + ) -> Union[StarknetBlock, PreConfirmedStarknetBlock]: return await self.get_block(block_hash=block_hash, block_number=block_number) async def get_block_with_tx_hashes( self, block_hash: Optional[Union[Hash, Tag]] = None, block_number: Optional[Union[int, Tag]] = None, - ) -> Union[StarknetBlockWithTxHashes, PendingStarknetBlockWithTxHashes]: + ) -> Union[StarknetBlockWithTxHashes, PreConfirmedStarknetBlockWithTxHashes]: block_identifier = get_block_identifier( block_hash=block_hash, block_number=block_number ) @@ -152,10 +155,10 @@ async def get_block_with_tx_hashes( params=block_identifier, ) - if block_identifier == {"block_id": "pending"}: + if block_identifier == {"block_id": "pre_confirmed"}: return cast( - PendingStarknetBlockWithTxHashes, - PendingStarknetBlockWithTxHashesSchema().load(res), + PreConfirmedStarknetBlockWithTxHashes, + PreConfirmedStarknetBlockWithTxHashesSchema().load(res), ) return cast( StarknetBlockWithTxHashes, @@ -166,7 +169,7 @@ async def get_block_with_receipts( self, block_hash: Optional[Union[Hash, Tag]] = None, block_number: Optional[Union[int, Tag]] = None, - ): + ) -> Union[StarknetBlockWithReceipts, PreConfirmedStarknetBlockWithReceipts]: block_identifier = get_block_identifier( block_hash=block_hash, block_number=block_number ) @@ -176,10 +179,10 @@ async def get_block_with_receipts( params=block_identifier, ) - if block_identifier == {"block_id": "pending"}: + if block_identifier == {"block_id": "pre_confirmed"}: return cast( - PendingStarknetBlockWithReceipts, - PendingStarknetBlockWithReceiptsSchema().load(res), + PreConfirmedStarknetBlockWithReceipts, + PreConfirmedStarknetBlockWithReceiptsSchema().load(res), ) return cast( StarknetBlockWithReceipts, @@ -207,17 +210,17 @@ async def get_events( e.g. given an event with 3 keys, [[1,2],[],[3]] which should return events that have either 1 or 2 in the first key, any value for their second key and 3 for their third key. :param from_block_number: Number of the block from which events searched for **starts** - or literals `"pending"` or `"latest"`. Mutually exclusive with ``from_block_hash`` parameter. + or literals `"pre_confirmed"` or `"latest"`. Mutually exclusive with ``from_block_hash`` parameter. If not provided, query starts from block 0. :param from_block_hash: Hash of the block from which events searched for **starts** - or literals `"pending"` or `"latest"`. Mutually exclusive with ``from_block_number`` parameter. + or literals `"pre_confirmed"` or `"latest"`. Mutually exclusive with ``from_block_number`` parameter. If not provided, query starts from block 0. :param to_block_number: Number of the block to which events searched for **end** - or literals `"pending"` or `"latest"`. Mutually exclusive with ``to_block_hash`` parameter. - If not provided, query ends at block `"pending"`. + or literals `"pre_confirmed"` or `"latest"`. Mutually exclusive with ``to_block_hash`` parameter. + If not provided, query ends at block `"pre_confirmed"`. :param to_block_hash: Hash of the block to which events searched for **end** - or literals `"pending"` or `"latest"`. Mutually exclusive with ``to_block_number`` parameter. - If not provided, query ends at block `"pending"`. + or literals `"pre_confirmed"` or `"latest"`. Mutually exclusive with ``to_block_number`` parameter. + If not provided, query ends at block `"pre_confirmed"`. :param follow_continuation_token: Flag deciding whether all events should be collected during one function call, defaults to False. :param continuation_token: Continuation token from which the returned events start. @@ -297,7 +300,7 @@ async def get_state_update( self, block_hash: Optional[Union[Hash, Tag]] = None, block_number: Optional[Union[int, Tag]] = None, - ) -> Union[BlockStateUpdate, PendingBlockStateUpdate]: + ) -> Union[BlockStateUpdate, PreConfirmedBlockStateUpdate]: block_identifier = get_block_identifier( block_hash=block_hash, block_number=block_number ) @@ -307,10 +310,10 @@ async def get_state_update( params=block_identifier, ) - if block_identifier == {"block_id": "pending"}: + if block_identifier == {"block_id": "pre_confirmed"}: return cast( - PendingBlockStateUpdate, - PendingBlockStateUpdateSchema().load(res), + PreConfirmedBlockStateUpdate, + PreConfirmedBlockStateUpdateSchema().load(res), ) return cast(BlockStateUpdate, BlockStateUpdateSchema().load(res)) @@ -338,7 +341,8 @@ async def get_storage_at( async def get_storage_proof( self, - block_id: Union[int, Hash, Tag, dict], + block_hash: Optional[Union[Hash, Tag]] = None, + block_number: Optional[Union[int, Tag]] = None, class_hashes: Optional[List[int]] = None, contract_addresses: Optional[List[int]] = None, contracts_storage_keys: Optional[List[ContractsStorageKeys]] = None, @@ -367,11 +371,15 @@ async def get_storage_proof( else [] ) + block_identifier = get_block_identifier( + block_hash=block_hash, block_number=block_number, allow_pre_confirmed=False + ) + params = { - "block_id": block_id, "class_hashes": class_hashes_serialized, "contract_addresses": contract_addresses_serialized, "contracts_storage_keys": contracts_storage_keys_serialized, + **block_identifier, } res = await self._client.call( @@ -464,10 +472,10 @@ async def estimate_message_fee( :param to_address: The target L2 (Starknet) address the message is sent to. :param entry_point_selector: The selector of the l1_handler in invoke in the target contract. :param payload: Payload of the message. - :param block_hash: Hash of the requested block or literals `"pending"` or `"latest"`. - Mutually exclusive with ``block_number`` parameter. If not provided, queries block `"pending"`. - :param block_number: Number (height) of the requested block or literals `"pending"` or `"latest"`. - Mutually exclusive with ``block_hash`` parameter. If not provided, queries block `"pending"`. + :param block_hash: Hash of the requested block or literals `"pre_confirmed"` or `"latest"`. + Mutually exclusive with ``block_number`` parameter. If not provided, queries block `"pre_confirmed"`. + :param block_number: Number (height) of the requested block or literals `"pre_confirmed"` or `"latest"`. + Mutually exclusive with ``block_hash`` parameter. If not provided, queries block `"pre_confirmed"`. """ block_identifier = get_block_identifier( block_hash=block_hash, block_number=block_number @@ -645,7 +653,7 @@ async def get_transaction_by_block_id( :param index: Index of the transaction :param block_hash: Hash of the block - :param block_number: Block's number or literals `"pending"` or `"latest"` + :param block_number: Block's number or literals `"pre_confirmed"` or `"latest"` :return: Transaction object """ block_identifier = get_block_identifier( @@ -669,8 +677,8 @@ async def get_block_transaction_count( """ Get the number of transactions in a block given a block id - :param block_hash: Block's hash or literals `"pending"` or `"latest"` - :param block_number: Block's number or literals `"pending"` or `"latest"` + :param block_hash: Block's hash or literals `"pre_confirmed"` or `"latest"` + :param block_number: Block's number or literals `"pre_confirmed"` or `"latest"` :return: Number of transactions in the designated block """ block_identifier = get_block_identifier( @@ -694,8 +702,8 @@ async def get_class_at( Get the contract class definition in the given block at the given address :param contract_address: The address of the contract whose class definition will be returned - :param block_hash: Block's hash or literals `"pending"` or `"latest"` - :param block_number: Block's number or literals `"pending"` or `"latest"` + :param block_hash: Block's hash or literals `"pre_confirmed"` or `"latest"` + :param block_number: Block's number or literals `"pre_confirmed"` or `"latest"` :return: Contract declared to Starknet """ block_identifier = get_block_identifier( @@ -808,8 +816,8 @@ async def simulate_transactions( :param skip_validate: Flag checking whether the validation part of the transaction should be executed. :param skip_fee_charge: Flag deciding whether fee should be deducted from the balance before the simulation of the next transaction. - :param block_hash: Block's hash or literals `"pending"` or `"latest"` - :param block_number: Block's number or literals `"pending"` or `"latest"` + :param block_hash: Block's hash or literals `"pre_confirmed"` or `"latest"` + :param block_number: Block's number or literals `"pre_confirmed"` or `"latest"` :return: The execution trace and consumed resources for each transaction. """ block_identifier = get_block_identifier( @@ -846,12 +854,12 @@ async def trace_block_transactions( """ Retrieve traces for all transactions in the given block. - :param block_hash: Block's hash or literals `"pending"` or `"latest"` - :param block_number: Block's number or literals `"pending"` or `"latest"` + :param block_hash: Block's hash or literals `"pre_confirmed"` or `"latest"` + :param block_number: Block's number or literals `"pre_confirmed"` or `"latest"` :return: List of execution traces of all transactions included in the given block with transaction hashes. """ block_identifier = get_block_identifier( - block_hash=block_hash, block_number=block_number + block_hash=block_hash, block_number=block_number, allow_pre_confirmed=False ) res = await self._client.call( diff --git a/starknet_py/net/schemas/rpc/block.py b/starknet_py/net/schemas/rpc/block.py index 1037c7f02..e46114fbe 100644 --- a/starknet_py/net/schemas/rpc/block.py +++ b/starknet_py/net/schemas/rpc/block.py @@ -7,10 +7,10 @@ ContractsNonce, DeclaredContractHash, DeployedContract, - PendingBlockStateUpdate, - PendingStarknetBlock, - PendingStarknetBlockWithReceipts, - PendingStarknetBlockWithTxHashes, + PreConfirmedBlockStateUpdate, + PreConfirmedStarknetBlock, + PreConfirmedStarknetBlockWithReceipts, + PreConfirmedStarknetBlockWithTxHashes, ReplacedClass, ResourcePrice, StarknetBlock, @@ -42,8 +42,8 @@ def make_dataclass(self, data, **kwargs) -> ResourcePrice: return ResourcePrice(**data) -class PendingBlockHeaderSchema(Schema): - parent_hash = Felt(data_key="parent_hash", required=True) +class PreConfirmedBlockHeaderSchema(Schema): + block_number = Felt(data_key="block_number", required=True) timestamp = fields.Integer(data_key="timestamp", required=True) sequencer_address = Felt(data_key="sequencer_address", required=True) l1_gas_price = fields.Nested( @@ -187,21 +187,21 @@ def make_dataclass(self, data, **kwargs) -> BlockStateUpdate: return BlockStateUpdate(**data) -class PendingBlockStateUpdateSchema(Schema): +class PreConfirmedBlockStateUpdateSchema(Schema): old_root = Felt(data_key="old_root", required=True) state_diff = fields.Nested(StateDiffSchema(), data_key="state_diff", required=True) @post_load - def make_dataclass(self, data, **kwargs) -> PendingBlockStateUpdate: - return PendingBlockStateUpdate(**data) + def make_dataclass(self, data, **kwargs) -> PreConfirmedBlockStateUpdate: + return PreConfirmedBlockStateUpdate(**data) -class PendingStarknetBlockWithTxHashesSchema(PendingBlockHeaderSchema): +class PreConfirmedStarknetBlockWithTxHashesSchema(PreConfirmedBlockHeaderSchema): transactions = fields.List(Felt(), data_key="transactions", required=True) @post_load - def make_dataclass(self, data, **kwargs) -> PendingStarknetBlockWithTxHashes: - return PendingStarknetBlockWithTxHashes(**data) + def make_dataclass(self, data, **kwargs) -> PreConfirmedStarknetBlockWithTxHashes: + return PreConfirmedStarknetBlockWithTxHashes(**data) class StarknetBlockWithTxHashesSchema(BlockHeaderSchema): @@ -226,7 +226,7 @@ def make_dataclass(self, data, **kwargs) -> StarknetBlockWithReceipts: return StarknetBlockWithReceipts(**data) -class PendingStarknetBlockSchema(PendingBlockHeaderSchema): +class PreConfirmedStarknetBlockSchema(PreConfirmedBlockHeaderSchema): transactions = fields.List( fields.Nested(TypesOfTransactionsSchema()), data_key="transactions", @@ -234,8 +234,8 @@ class PendingStarknetBlockSchema(PendingBlockHeaderSchema): ) @post_load - def make_dataclass(self, data, **kwargs) -> PendingStarknetBlock: - return PendingStarknetBlock(**data) + def make_dataclass(self, data, **kwargs) -> PreConfirmedStarknetBlock: + return PreConfirmedStarknetBlock(**data) class StarknetBlockSchema(BlockHeaderSchema): @@ -251,7 +251,7 @@ def make_dataclass(self, data, **kwargs) -> StarknetBlock: return StarknetBlock(**data) -class PendingStarknetBlockWithReceiptsSchema(PendingBlockHeaderSchema): +class PreConfirmedStarknetBlockWithReceiptsSchema(PreConfirmedBlockHeaderSchema): transactions = fields.List( fields.Nested(TransactionWithReceiptSchema()), data_key="transactions", @@ -259,5 +259,5 @@ class PendingStarknetBlockWithReceiptsSchema(PendingBlockHeaderSchema): ) @post_load - def make_dataclass(self, data, **kwargs) -> PendingStarknetBlockWithReceipts: - return PendingStarknetBlockWithReceipts(**data) + def make_dataclass(self, data, **kwargs) -> PreConfirmedStarknetBlockWithReceipts: + return PreConfirmedStarknetBlockWithReceipts(**data) diff --git a/starknet_py/net/schemas/rpc/transactions.py b/starknet_py/net/schemas/rpc/transactions.py index d76a78daf..0d8072a79 100644 --- a/starknet_py/net/schemas/rpc/transactions.py +++ b/starknet_py/net/schemas/rpc/transactions.py @@ -359,6 +359,9 @@ def make_dataclass(self, data, **kwargs) -> DeployAccountTransactionResponse: class MessageStatusSchema(Schema): transaction_hash = NumberAsHex(data_key="transaction_hash", required=True) finality_status = StatusField(data_key="finality_status", required=True) + execution_status = ExecutionStatusField( + data_key="execution_status", load_default=None + ) failure_reason = fields.String(data_key="failure_reason", load_default=None) @post_load diff --git a/starknet_py/tests/e2e/block_test.py b/starknet_py/tests/e2e/block_test.py index b3a6b3605..bfafe68ae 100644 --- a/starknet_py/tests/e2e/block_test.py +++ b/starknet_py/tests/e2e/block_test.py @@ -3,8 +3,8 @@ from starknet_py.net.client_models import ( BlockStatus, L1DAMode, - PendingStarknetBlock, - PendingStarknetBlockWithTxHashes, + PreConfirmedStarknetBlock, + PreConfirmedStarknetBlockWithTxHashes, StarknetBlock, StarknetBlockWithReceipts, StarknetBlockWithTxHashes, @@ -12,10 +12,10 @@ @pytest.mark.asyncio -async def test_pending_block(account): - blk = await account.client.get_block(block_number="pending") +async def test_pre_confirmed_block(account): + blk = await account.client.get_block(block_number="pre_confirmed") assert blk.transactions is not None - assert isinstance(blk, PendingStarknetBlock) + assert isinstance(blk, PreConfirmedStarknetBlock) @pytest.mark.asyncio @@ -27,10 +27,10 @@ async def test_latest_block(account): @pytest.mark.asyncio -async def test_block_with_tx_hashes_pending(account): - blk = await account.client.get_block_with_tx_hashes(block_number="pending") +async def test_block_with_tx_hashes_pre_confirmed(account): + blk = await account.client.get_block_with_tx_hashes(block_number="pre_confirmed") - assert isinstance(blk, PendingStarknetBlockWithTxHashes) + assert isinstance(blk, PreConfirmedStarknetBlockWithTxHashes) assert isinstance(blk.transactions, list) @@ -55,10 +55,10 @@ async def test_block_with_tx_hashes_latest(account): @pytest.mark.asyncio -async def test_get_block_with_txs_pending(account): - blk = await account.client.get_block_with_txs(block_number="pending") +async def test_get_block_with_txs_pre_confirmed(account): + blk = await account.client.get_block_with_txs(block_number="pre_confirmed") - assert isinstance(blk, PendingStarknetBlock) + assert isinstance(blk, PreConfirmedStarknetBlock) assert isinstance(blk.transactions, list) diff --git a/starknet_py/tests/e2e/client/client_test.py b/starknet_py/tests/e2e/client/client_test.py index 674a66be3..eeba82102 100644 --- a/starknet_py/tests/e2e/client/client_test.py +++ b/starknet_py/tests/e2e/client/client_test.py @@ -48,7 +48,6 @@ from starknet_py.tests.e2e.fixtures.constants import MAX_RESOURCE_BOUNDS from starknet_py.transaction_errors import ( TransactionNotReceivedError, - TransactionRejectedError, TransactionRevertedError, ) @@ -474,17 +473,6 @@ async def test_wait_for_tx_reverted(client, get_tx_receipt_path, get_tx_status_p assert exc_message in err.value.message -@pytest.mark.asyncio -async def test_wait_for_tx_rejected(client, get_tx_status_path): - with patch(get_tx_status_path, AsyncMock()) as mocked_status: - mocked_status.return_value = TransactionStatusResponse( - finality_status=TransactionStatus.REJECTED - ) - - with pytest.raises(TransactionRejectedError): - await client.wait_for_tx(tx_hash=0x1) - - @pytest.mark.asyncio async def test_wait_for_tx_unknown_error( client, get_tx_receipt_path, get_tx_status_path diff --git a/starknet_py/tests/e2e/tests_on_networks/client_test.py b/starknet_py/tests/e2e/tests_on_networks/client_test.py index 942b6be27..3cd62ac25 100644 --- a/starknet_py/tests/e2e/tests_on_networks/client_test.py +++ b/starknet_py/tests/e2e/tests_on_networks/client_test.py @@ -19,8 +19,8 @@ EstimatedFee, EventsChunk, InvokeTransactionV3, - PendingBlockHeader, - PendingStarknetBlockWithReceipts, + PreConfirmedBlockHeader, + PreConfirmedStarknetBlockWithReceipts, ResourceBounds, ResourceBoundsMapping, StarknetBlockWithReceipts, @@ -402,13 +402,13 @@ async def test_get_block_new_header_fields(client_sepolia_testnet): assert block.l1_gas_price is not None assert block.l1_gas_price.price_in_wei > 0 - pending_block = await client_sepolia_testnet.get_block_with_txs( - block_number="pending" + pre_confirmed_block = await client_sepolia_testnet.get_block_with_txs( + block_number="pre_confirmed" ) - assert pending_block.starknet_version is not None - assert pending_block.l1_gas_price is not None - assert pending_block.l1_gas_price.price_in_wei > 0 + assert pre_confirmed_block.starknet_version is not None + assert pre_confirmed_block.l1_gas_price is not None + assert pre_confirmed_block.l1_gas_price.price_in_wei > 0 @pytest.mark.asyncio @@ -420,13 +420,13 @@ async def test_get_block_with_tx_hashes_new_header_fields(client_sepolia_testnet assert block.l1_gas_price is not None assert block.l1_gas_price.price_in_wei > 0 - pending_block = await client_sepolia_testnet.get_block_with_tx_hashes( - block_number="pending" + pre_confirmed_block = await client_sepolia_testnet.get_block_with_tx_hashes( + block_number="pre_confirmed" ) - assert pending_block.starknet_version is not None - assert pending_block.l1_gas_price is not None - assert pending_block.l1_gas_price.price_in_wei > 0 + assert pre_confirmed_block.starknet_version is not None + assert pre_confirmed_block.l1_gas_price is not None + assert pre_confirmed_block.l1_gas_price.price_in_wei > 0 @pytest.mark.parametrize( @@ -500,16 +500,16 @@ async def test_get_block_with_receipts(client_sepolia_testnet): @pytest.mark.asyncio -async def test_get_pending_block_with_receipts(client_sepolia_testnet): +async def test_get_pre_confirmed_block_with_receipts(client_sepolia_testnet): block_with_receipts = await client_sepolia_testnet.get_block_with_receipts( - block_number="pending" + block_number="pre_confirmed" ) - assert isinstance(block_with_receipts, PendingStarknetBlockWithReceipts) + assert isinstance(block_with_receipts, PreConfirmedStarknetBlockWithReceipts) assert len(block_with_receipts.transactions) >= 0 assert all( getattr(block_with_receipts, field.name) is not None - for field in dataclasses.fields(PendingBlockHeader) + for field in dataclasses.fields(PreConfirmedBlockHeader) ) diff --git a/starknet_py/tests/unit/net/schemas/common_test.py b/starknet_py/tests/unit/net/schemas/common_test.py index ef3bd526f..d1eec4e8c 100644 --- a/starknet_py/tests/unit/net/schemas/common_test.py +++ b/starknet_py/tests/unit/net/schemas/common_test.py @@ -224,21 +224,21 @@ def test_serialize_block_status_field(): class SchemaWithBlockStatusField(Schema): value1 = BlockStatusField(data_key="value1") - data = {"value1": BlockStatus.PENDING} + data = {"value1": BlockStatus.PRE_CONFIRMED} serialized = SchemaWithBlockStatusField().dumps(data) - assert '"value1": "PENDING"' in serialized + assert '"value1": "PRE_CONFIRMED"' in serialized def test_deserialize_block_status_field(): class SchemaWithBlockStatusField(Schema): value1 = BlockStatusField(data_key="value1") - data = {"value1": "PENDING"} + data = {"value1": "PRE_CONFIRMED"} deserialized = SchemaWithBlockStatusField().load(data) assert isinstance(deserialized, dict) - assert deserialized["value1"] == BlockStatus.PENDING + assert deserialized["value1"] == BlockStatus.PRE_CONFIRMED def test_serialize_block_status_field_throws_on_invalid_data(): diff --git a/starknet_py/transaction_errors.py b/starknet_py/transaction_errors.py index b9b343277..a40b01ef2 100644 --- a/starknet_py/transaction_errors.py +++ b/starknet_py/transaction_errors.py @@ -31,15 +31,6 @@ def __str__(self): ) -class TransactionRejectedError(TransactionFailedError): - """ - Exception for transactions rejected by Starknet. - """ - - def __str__(self): - return "Transaction was rejected on Starknet." - - class TransactionNotReceivedError(TransactionFailedError): """ Exception for transactions not received on Starknet. From 55277fe2064598cb919539e968e92f57b1583ce7 Mon Sep 17 00:00:00 2001 From: Artur Michalek Date: Thu, 3 Jul 2025 17:14:03 +0200 Subject: [PATCH 03/32] Add missing methods to `Client` ABC --- starknet_py/net/client.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/starknet_py/net/client.py b/starknet_py/net/client.py index 346037bed..2bcce1861 100644 --- a/starknet_py/net/client.py +++ b/starknet_py/net/client.py @@ -23,6 +23,8 @@ SentTransactionResponse, SierraContractClass, StarknetBlock, + StarknetBlockWithReceipts, + StarknetBlockWithTxHashes, StorageProofResponse, Tag, Transaction, @@ -47,6 +49,7 @@ @add_sync_methods class Client(ABC): + # pylint: disable=too-many-public-methods @abstractmethod async def get_block( self, @@ -61,6 +64,36 @@ async def get_block( :return: StarknetBlock object representing retrieved block """ + @abstractmethod + async def get_block_with_txs( + self, + block_hash: Optional[Union[Hash, Tag]] = None, + block_number: Optional[Union[int, Tag]] = None, + ) -> Union[StarknetBlock, PreConfirmedStarknetBlock]: + """ " + TODO docstring + """ + + @abstractmethod + async def get_block_with_tx_hashes( + self, + block_hash: Optional[Union[Hash, Tag]] = None, + block_number: Optional[Union[int, Tag]] = None, + ) -> Union[StarknetBlockWithTxHashes, PreConfirmedStarknetBlockWithTxHashes]: + """ + TODO docstring + """ + + @abstractmethod + async def get_block_with_receipts( + self, + block_hash: Optional[Union[Hash, Tag]] = None, + block_number: Optional[Union[int, Tag]] = None, + ) -> Union[StarknetBlockWithReceipts, PreConfirmedStarknetBlockWithReceipts]: + """ + TODO docstring + """ + @abstractmethod async def trace_block_transactions( self, From 901a3e3976deb19e7b90ec6f914b48b9773e84cd Mon Sep 17 00:00:00 2001 From: Artur Michalek Date: Thu, 3 Jul 2025 17:14:16 +0200 Subject: [PATCH 04/32] Fix `get_messages_status` argument --- starknet_py/net/client.py | 2 +- starknet_py/net/client_utils.py | 9 +++++++++ starknet_py/net/full_node_client.py | 4 ++-- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/starknet_py/net/client.py b/starknet_py/net/client.py index 2bcce1861..367d76188 100644 --- a/starknet_py/net/client.py +++ b/starknet_py/net/client.py @@ -381,7 +381,7 @@ async def get_chain_id(self) -> str: """Return the currently configured Starknet chain id""" @abstractmethod - async def get_messages_status(self, transaction_hash: str) -> List[MessageStatus]: + async def get_messages_status(self, transaction_hash: Hash) -> List[MessageStatus]: """ Get L1 handler transaction data for all L1 to L2 messages sent by the given L1 transaction. diff --git a/starknet_py/net/client_utils.py b/starknet_py/net/client_utils.py index 583886bfe..171d4040f 100644 --- a/starknet_py/net/client_utils.py +++ b/starknet_py/net/client_utils.py @@ -74,6 +74,15 @@ def _to_rpc_felt(value: Hash) -> str: return rpc_felt +def _to_hex_number(value: Hash) -> str: + """ + Convert the value to hex encoded number + """ + if isinstance(value, str): + return value + return hex(value) + + def _is_valid_eth_address(address: str) -> bool: """ A function checking if an address matches Ethereum address regex. Note that it doesn't validate any checksums etc. diff --git a/starknet_py/net/full_node_client.py b/starknet_py/net/full_node_client.py index 7fa30241d..b36166c86 100644 --- a/starknet_py/net/full_node_client.py +++ b/starknet_py/net/full_node_client.py @@ -521,10 +521,10 @@ async def get_block_hash_and_number(self) -> BlockHashAndNumber: async def get_chain_id(self) -> str: return await self._client.call(method_name="chainId", params={}) - async def get_messages_status(self, transaction_hash: str) -> List[MessageStatus]: + async def get_messages_status(self, transaction_hash: Hash) -> List[MessageStatus]: res = await self._client.call( method_name="getMessagesStatus", - params={"transaction_hash": transaction_hash}, + params={"transaction_hash": _to_hex_number(transaction_hash)}, ) return cast( List[MessageStatus], From 0923db5cdbd43dd55cf5dacce062fdaecf51a5cf Mon Sep 17 00:00:00 2001 From: Artur Michalek Date: Thu, 3 Jul 2025 17:14:32 +0200 Subject: [PATCH 05/32] Fix `subscription_id` type in schemas --- starknet_py/net/schemas/rpc/websockets.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/starknet_py/net/schemas/rpc/websockets.py b/starknet_py/net/schemas/rpc/websockets.py index 2bb4cc7e9..72c167a3a 100644 --- a/starknet_py/net/schemas/rpc/websockets.py +++ b/starknet_py/net/schemas/rpc/websockets.py @@ -24,7 +24,7 @@ class NewHeadsNotificationSchema(Schema): - subscription_id = fields.Integer(data_key="subscription_id", required=True) + subscription_id = fields.Str(data_key="subscription_id", required=True) result = fields.Nested(BlockHeaderSchema(), data_key="result", required=True) @post_load @@ -33,7 +33,7 @@ def make_dataclass(self, data, **kwargs) -> NewHeadsNotification: class NewEventsNotificationSchema(Schema): - subscription_id = fields.Integer(data_key="subscription_id", required=True) + subscription_id = fields.Str(data_key="subscription_id", required=True) result = fields.Nested(EmittedEventSchema(), data_key="result", required=True) @post_load @@ -53,7 +53,7 @@ def make_dataclass(self, data, **kwargs) -> NewTransactionStatus: class TransactionStatusNotificationSchema(Schema): - subscription_id = fields.Integer(data_key="subscription_id", required=True) + subscription_id = fields.Str(data_key="subscription_id", required=True) result = fields.Nested( NewTransactionStatusSchema(), data_key="result", required=True ) @@ -90,7 +90,7 @@ def _deserialize( class PendingTransactionsNotificationSchema(Schema): - subscription_id = fields.Integer(data_key="subscription_id", required=True) + subscription_id = fields.Str(data_key="subscription_id", required=True) result = PendingTransactionsNotificationResultField( data_key="result", required=True ) @@ -116,7 +116,7 @@ def make_dataclass(self, data, **kwargs) -> ReorgData: class ReorgNotificationSchema(Schema): - subscription_id = fields.Integer(data_key="subscription_id", required=True) + subscription_id = fields.Str(data_key="subscription_id", required=True) result = fields.Nested(ReorgDataSchema(), data_key="result", required=True) @post_load From 3ec22b7399c5644c118e71e166bf00e7c609eee0 Mon Sep 17 00:00:00 2001 From: Artur Michalek Date: Thu, 3 Jul 2025 17:32:51 +0200 Subject: [PATCH 06/32] Add migration guide --- docs/migration_guide.rst | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docs/migration_guide.rst b/docs/migration_guide.rst index 7ea59e4e4..8d40e1114 100644 --- a/docs/migration_guide.rst +++ b/docs/migration_guide.rst @@ -1,6 +1,34 @@ Migration guide =============== +********************** +0.28.0 Migration guide +********************** + +``starknet_py.net.client_models`` +--------------------------------- + +.. py:currentmodule:: starknet_py.net.client_models + +1. Renamed :class:`PendingBlockHeader` to :class:`PreConfirmedBlockHeader`, changed field ``parent_hash`` to ``block_number``. +2. Renamed :class:`PendingStarknetBlock` to :class:`PreConfirmedStarknetBlock` +3. Renamed :class:`PendingStarknetBlockWithTxHashes` to :class:`PreConfirmedStarknetBlockWithTxHashes` +4. Renamed :class:`PendingStarknetBlockWithReceipts` to :class:`PreConfirmedStarknetBlockWithReceipts` +5. Renamed :class:`PendingBlockStateUpdate` to :class:`PreConfirmedBlockStateUpdate` +6. Enum :class:`BlockStatus` variant ``PENDING`` removed, added ``PRE_CONFIRMED`` +7. Enum :class:`TransactionFinalityStatus`, added variant ``PRE_CONFIRMED`` +8. Enum :class:`TransactionStatus` variant ``REJECTED`` removed, added ``CANDIDATE``, ``PRE_CONFIRMED`` + +``starknet_py.net.client`` +-------------------------- + +.. py:currentmodule:: starknet_py.net.client + + +1. :meth:`Client.get_storage_proof` replaced argument ``get_storage_proof`` with arguments ``block_hash`` and ``block_hash``. +2. :meth:`Client.wait_for_tx` will now wait until transaction ``finality_status`` is either ``ACCEPTED_ON_L2`` or ``ACCEPTED_ON_L1``. +3. :meth:`Client.get_messages_status` changed ``transaction_hash`` type from ``str`` to ``Hash``. + ********************** 0.27.0 Migration guide ********************** From 5298ac72096013d6964e6fe28dc756764dd9ff25 Mon Sep 17 00:00:00 2001 From: Artur Michalek Date: Fri, 4 Jul 2025 12:02:43 +0200 Subject: [PATCH 07/32] Bump devnet to 0.5.0-rc.0 --- .github/workflows/checks.yml | 2 +- starknet_py/tests/install_devnet.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index dc2ffc70f..a03becd29 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -3,7 +3,7 @@ name: Checks env: CAIRO_LANG_VERSION: "0.13.1" # TODO(#1611) - DEVNET_SHA: fc5a2753a2eedcc27eed7a4fae3ecac08c2ca1b4 # v0.3.0 + DEVNET_SHA: dab72df7e14b5117c8daec35ecf7c432411e9561 # v0.5.0-rc.0 LEDGER_APP_SHA: 768a7b47b0da681b28112342edd76e2c9b292c4e # v2.3.1 LEDGER_APP_DEV_TOOLS_SHA: a845b2ab0b5dd824133f73858f6f373edea85ec1bd828245bf50ce9700f33bcb # v4.5.0 diff --git a/starknet_py/tests/install_devnet.sh b/starknet_py/tests/install_devnet.sh index 97a5ee968..db615c391 100755 --- a/starknet_py/tests/install_devnet.sh +++ b/starknet_py/tests/install_devnet.sh @@ -3,7 +3,7 @@ set -e DEVNET_INSTALL_DIR="$(git rev-parse --show-toplevel)/starknet_py/tests/e2e/devnet/bin" DEVNET_REPO="https://github.com/0xSpaceShard/starknet-devnet-rs" -DEVNET_VERSION="v0.3.0" +DEVNET_VERSION="v0.5.0-rc.0" require_cmd() { if ! command -v "$1" >/dev/null 2>&1; then From 115e4be33d32e4479a16f44cf7a82d6047a5ac9f Mon Sep 17 00:00:00 2001 From: Artur Michalek Date: Fri, 4 Jul 2025 18:05:44 +0200 Subject: [PATCH 08/32] Update `MessageStatus` following the spec --- starknet_py/net/client_models.py | 13 ++++++++++-- starknet_py/net/schemas/common.py | 22 +++++++++++++++++++++ starknet_py/net/schemas/rpc/transactions.py | 7 ++++--- 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/starknet_py/net/client_models.py b/starknet_py/net/client_models.py index 0395cb7be..d3a315e55 100644 --- a/starknet_py/net/client_models.py +++ b/starknet_py/net/client_models.py @@ -1204,11 +1204,20 @@ class StorageProofResponse: global_roots: GlobalRoots +class MessageFinalityStatus(Enum): + """ + Enum representing transaction statuses. + """ + + ACCEPTED_ON_L2 = "ACCEPTED_ON_L2" + ACCEPTED_ON_L1 = "ACCEPTED_ON_L1" + + @dataclass class MessageStatus: transaction_hash: int - finality_status: TransactionStatus - execution_status: Optional[TransactionExecutionStatus] = None + finality_status: MessageFinalityStatus + execution_status: TransactionExecutionStatus failure_reason: Optional[str] = None diff --git a/starknet_py/net/schemas/common.py b/starknet_py/net/schemas/common.py index fa20d4103..0a6e8d108 100644 --- a/starknet_py/net/schemas/common.py +++ b/starknet_py/net/schemas/common.py @@ -17,6 +17,7 @@ TransactionFinalityStatus, TransactionStatus, TransactionType, + MessageFinalityStatus, ) # pylint: disable=unused-argument @@ -184,6 +185,27 @@ def _deserialize( return TransactionFinalityStatus(value) +class MessageFinalityStatusField(fields.Field): + def _serialize(self, value: Any, attr: Optional[str], obj: Any, **kwargs): + return value.name if value is not None else "" + + def _deserialize( + self, + value: Any, + attr: Optional[str], + data: Optional[Mapping[str, Any]], + **kwargs, + ) -> MessageFinalityStatus: + values = [v.value for v in MessageFinalityStatus] + + if value not in values: + raise ValidationError( + f"Invalid value provided for MessageFinalityStatus: {value}." + ) + + return MessageFinalityStatus(value) + + class BlockStatusField(fields.Field): def _serialize(self, value: Any, attr: Optional[str], obj: Any, **kwargs): return value.name if value is not None else "" diff --git a/starknet_py/net/schemas/rpc/transactions.py b/starknet_py/net/schemas/rpc/transactions.py index 0d8072a79..da977e080 100644 --- a/starknet_py/net/schemas/rpc/transactions.py +++ b/starknet_py/net/schemas/rpc/transactions.py @@ -37,6 +37,7 @@ TransactionTypeField, Uint64, Uint128, + MessageFinalityStatusField, ) from starknet_py.net.schemas.rpc.event import EventSchema from starknet_py.net.schemas.rpc.general import ExecutionResourcesSchema @@ -358,10 +359,10 @@ def make_dataclass(self, data, **kwargs) -> DeployAccountTransactionResponse: class MessageStatusSchema(Schema): transaction_hash = NumberAsHex(data_key="transaction_hash", required=True) - finality_status = StatusField(data_key="finality_status", required=True) - execution_status = ExecutionStatusField( - data_key="execution_status", load_default=None + finality_status = MessageFinalityStatusField( + data_key="finality_status", required=True ) + execution_status = ExecutionStatusField(data_key="execution_status", required=True) failure_reason = fields.String(data_key="failure_reason", load_default=None) @post_load From 96358375b3c0f1607ee36bf5243d2ec897cd9cc0 Mon Sep 17 00:00:00 2001 From: Artur Michalek Date: Fri, 4 Jul 2025 18:06:13 +0200 Subject: [PATCH 09/32] Improve docs of `wait_for_tx`, check for error code explicitly --- docs/migration_guide.rst | 3 ++- starknet_py/net/client.py | 10 +++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/migration_guide.rst b/docs/migration_guide.rst index 8d40e1114..8b80ee70d 100644 --- a/docs/migration_guide.rst +++ b/docs/migration_guide.rst @@ -27,7 +27,8 @@ Migration guide 1. :meth:`Client.get_storage_proof` replaced argument ``get_storage_proof`` with arguments ``block_hash`` and ``block_hash``. 2. :meth:`Client.wait_for_tx` will now wait until transaction ``finality_status`` is either ``ACCEPTED_ON_L2`` or ``ACCEPTED_ON_L1``. -3. :meth:`Client.get_messages_status` changed ``transaction_hash`` type from ``str`` to ``Hash``. +3. :meth:`Client.wait_for_tx` will no longer raise ``TransactionRejectedError``, see the method docs for details. +4. :meth:`Client.get_messages_status` changed ``transaction_hash`` type from ``str`` to ``Hash``. ********************** 0.27.0 Migration guide diff --git a/starknet_py/net/client.py b/starknet_py/net/client.py index 367d76188..bce166f38 100644 --- a/starknet_py/net/client.py +++ b/starknet_py/net/client.py @@ -202,11 +202,15 @@ async def wait_for_tx( ) -> TransactionReceipt: # pylint: disable=too-many-branches """ - Awaits for transaction to get accepted or at least pending by polling its status. + Awaits the transaction until its status is either ``ACCEPTED_ON_L2`` or ``ACCEPTED_ON_L1`` + and its receipt can be fetched. :param tx_hash: Transaction's hash. - :param check_interval: Defines interval between checks. + :param check_interval: Defines the interval between checks. :param retries: Defines how many times the transaction is checked until an error is thrown. + :raises TransactionRevertedError: If the transaction execution status is ``REVERTED``. + :raises TransactionNotReceivedError: If the transaction is not received within the given number of checks, + i.e., its status never reaches ``ACCEPTED_ON_L2`` or ``ACCEPTED_ON_L1``. :return: Transaction receipt. """ if check_interval <= 0: @@ -256,7 +260,7 @@ async def wait_for_tx( except asyncio.CancelledError as exc: raise TransactionNotReceivedError from exc except ClientError as exc: - if "Transaction hash not found" not in exc.message: + if exc.code != 29: raise exc if retries == 0: From dd2249fb524073a8ea80d248bff08234cbb1c64a Mon Sep 17 00:00:00 2001 From: Artur Michalek Date: Fri, 4 Jul 2025 18:06:22 +0200 Subject: [PATCH 10/32] Fix tests --- starknet_py/tests/e2e/account/account_test.py | 16 ++++++-- starknet_py/tests/e2e/client/client_test.py | 41 ++++++++++++++----- 2 files changed, 43 insertions(+), 14 deletions(-) diff --git a/starknet_py/tests/e2e/account/account_test.py b/starknet_py/tests/e2e/account/account_test.py index 4d70492ae..cb515881e 100644 --- a/starknet_py/tests/e2e/account/account_test.py +++ b/starknet_py/tests/e2e/account/account_test.py @@ -74,7 +74,9 @@ async def test_estimated_fee_greater_than_zero(account, erc20_contract): ) assert estimated_fee.overall_fee > 0 - assert estimated_fee.calculate_overall_fee() == estimated_fee.overall_fee + # `overall_fee` returned from the node includes the tip which is not part of the response itself and cannot + # be calculated using the ` calculate_overall_fee ` method. + assert estimated_fee.calculate_overall_fee() <= estimated_fee.overall_fee @pytest.mark.asyncio @@ -92,7 +94,9 @@ async def test_estimate_fee_for_declare_transaction( assert isinstance(estimated_fee.overall_fee, int) assert estimated_fee.overall_fee > 0 - assert estimated_fee.calculate_overall_fee() == estimated_fee.overall_fee + # `overall_fee` returned from the node includes the tip which is not part of the response itself and cannot + # be calculated using the ` calculate_overall_fee ` method. + assert estimated_fee.calculate_overall_fee() <= estimated_fee.overall_fee @pytest.mark.asyncio @@ -111,7 +115,9 @@ async def test_account_estimate_fee_for_declare_transaction( assert estimated_fee.unit == PriceUnit.FRI assert isinstance(estimated_fee.overall_fee, int) assert estimated_fee.overall_fee > 0 - assert estimated_fee.calculate_overall_fee() == estimated_fee.overall_fee + # `overall_fee` returned from the node includes the tip which is not part of the response itself and cannot + # be calculated using the ` calculate_overall_fee ` method. + assert estimated_fee.calculate_overall_fee() <= estimated_fee.overall_fee @pytest.mark.asyncio @@ -137,7 +143,9 @@ async def test_account_estimate_fee_for_transactions(account, map_contract): assert estimated_fee[1].unit == PriceUnit.FRI assert isinstance(estimated_fee[0].overall_fee, int) assert estimated_fee[0].overall_fee > 0 - assert estimated_fee[0].calculate_overall_fee() == estimated_fee[0].overall_fee + # `overall_fee` returned from the node includes the tip which is not part of the response itself and cannot + # be calculated using the ` calculate_overall_fee ` method. + assert estimated_fee[0].calculate_overall_fee() <= estimated_fee[0].overall_fee @pytest.mark.asyncio diff --git a/starknet_py/tests/e2e/client/client_test.py b/starknet_py/tests/e2e/client/client_test.py index eeba82102..7497dd30a 100644 --- a/starknet_py/tests/e2e/client/client_test.py +++ b/starknet_py/tests/e2e/client/client_test.py @@ -140,10 +140,12 @@ async def test_get_messages_status(client): { "transaction_hash": "0x1", "finality_status": "ACCEPTED_ON_L2", + "execution_status": "SUCCEEDED", }, { "transaction_hash": "0x2", - "finality_status": "REJECTED", + "finality_status": "ACCEPTED_ON_L2", + "execution_status": "REVERTED", "failure_reason": "Some failure reason", }, ] @@ -217,7 +219,7 @@ async def test_get_storage_proof(client): mocked_message_status_call_rpc.return_value = return_value["result"] storage_proof = await client.get_storage_proof( - block_id="latest", + block_hash="latest", contract_addresses=[123], contracts_storage_keys=[ ContractsStorageKeys( @@ -240,20 +242,20 @@ async def test_get_storage_proof(client): @pytest.mark.asyncio async def test_get_compiled_casm(client): strk_devnet_class_hash = ( - 0x11374319A6E07B4F2738FA3BFA8CF2181BFB0DBB4D800215BAA87B83A57877E + 0x76791EF97C042F81FBF352AD95F39A22554EE8D7927B2CE3C681F3418B5206A ) compiled_casm = await client.get_compiled_casm(class_hash=strk_devnet_class_hash) assert isinstance(compiled_casm, CasmClass) - assert len(compiled_casm.bytecode) == 9732 - assert len(compiled_casm.hints) == 113 + assert len(compiled_casm.bytecode) == 23286 + assert len(compiled_casm.hints) == 954 first_hint = compiled_casm.hints[0][1][0] assert isinstance(first_hint, TestLessThanOrEqual) assert first_hint.test_less_than_or_equal.dst.offset == 0 assert first_hint.test_less_than_or_equal.dst.register == "AP" assert isinstance(first_hint.test_less_than_or_equal.lhs, Immediate) - assert first_hint.test_less_than_or_equal.lhs.immediate == 0x37BE + assert first_hint.test_less_than_or_equal.lhs.immediate == 0 assert isinstance(first_hint.test_less_than_or_equal.rhs, Deref) assert first_hint.test_less_than_or_equal.rhs.deref.offset == -6 assert first_hint.test_less_than_or_equal.rhs.deref.register == "FP" @@ -437,13 +439,32 @@ async def test_wait_for_tx_accepted(client, get_tx_receipt_path, get_tx_status_p ) mocked_status.return_value = TransactionStatusResponse( - finality_status=TransactionStatus.RECEIVED + finality_status=TransactionStatus.ACCEPTED_ON_L2, ) tx_receipt = await client.wait_for_tx(tx_hash=0x1) assert tx_receipt.finality_status == TransactionFinalityStatus.ACCEPTED_ON_L2 +@pytest.mark.asyncio +async def test_wait_for_tx_not_received( + client, get_tx_receipt_path, get_tx_status_path +): + exc_message = "Transaction not received." + + with patch(get_tx_status_path, AsyncMock()) as mocked_status: + mocked_status.return_value = TransactionStatusResponse( + finality_status=TransactionStatus.RECEIVED + ) + + with pytest.raises(TransactionNotReceivedError) as err: + # We set `retires` to 1, otherwise `wait_for_tx` will try to fetch tx status until + # it is either `ACCEPTED_ON_L2` or `ACCEPTED_ON_L1` + await client.wait_for_tx(tx_hash=0x1, retries=1) + + assert exc_message in err.value.message + + @pytest.mark.asyncio async def test_wait_for_tx_reverted(client, get_tx_receipt_path, get_tx_status_path): exc_message = "Unknown Starknet error" @@ -457,14 +478,14 @@ async def test_wait_for_tx_reverted(client, get_tx_receipt_path, get_tx_status_p block_number=1, type=TransactionType.INVOKE, execution_status=TransactionExecutionStatus.REVERTED, - finality_status=Mock(spec=TransactionFinalityStatus), + finality_status=TransactionFinalityStatus.ACCEPTED_ON_L2, execution_resources=Mock(spec=ExecutionResources), revert_reason=exc_message, actual_fee=FeePayment(amount=1, unit=PriceUnit.WEI), ) mocked_status.return_value = TransactionStatusResponse( - finality_status=TransactionStatus.RECEIVED + finality_status=TransactionStatus.ACCEPTED_ON_L2, ) with pytest.raises(TransactionRevertedError) as err: @@ -483,7 +504,7 @@ async def test_wait_for_tx_unknown_error( ) as mocked_receipt, patch(get_tx_status_path, AsyncMock()) as mocked_status: mocked_receipt.side_effect = ClientError(message="Unknown error") mocked_status.return_value = TransactionStatusResponse( - finality_status=TransactionStatus.RECEIVED + finality_status=TransactionStatus.ACCEPTED_ON_L2 ) with pytest.raises(ClientError, match="Unknown error"): From 29c51d717a6ceb195b223e65a40746b76c4bdb8f Mon Sep 17 00:00:00 2001 From: Artur Michalek Date: Fri, 4 Jul 2025 18:06:26 +0200 Subject: [PATCH 11/32] Fix docs --- docs/api/transaction_errors.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/api/transaction_errors.rst b/docs/api/transaction_errors.rst index 7cc98fbbb..60a735340 100644 --- a/docs/api/transaction_errors.rst +++ b/docs/api/transaction_errors.rst @@ -6,9 +6,6 @@ Transaction errors .. autoclass:: TransactionFailedError :exclude-members: __init__, __new__ -.. autoclass:: TransactionRejectedError - :exclude-members: __init__, __new__ - .. autoclass:: TransactionRevertedError :exclude-members: __init__, __new__ From 4117ce6b270be4ea9f9a821d7b3480e2f5a5b335 Mon Sep 17 00:00:00 2001 From: Artur Michalek Date: Fri, 4 Jul 2025 18:07:16 +0200 Subject: [PATCH 12/32] Lint and format --- starknet_py/net/schemas/common.py | 2 +- starknet_py/net/schemas/rpc/transactions.py | 2 +- starknet_py/tests/e2e/client/client_test.py | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/starknet_py/net/schemas/common.py b/starknet_py/net/schemas/common.py index 0a6e8d108..d354bc8b6 100644 --- a/starknet_py/net/schemas/common.py +++ b/starknet_py/net/schemas/common.py @@ -11,13 +11,13 @@ DAMode, EntryPointType, L1DAMode, + MessageFinalityStatus, PriceUnit, StorageEntry, TransactionExecutionStatus, TransactionFinalityStatus, TransactionStatus, TransactionType, - MessageFinalityStatus, ) # pylint: disable=unused-argument diff --git a/starknet_py/net/schemas/rpc/transactions.py b/starknet_py/net/schemas/rpc/transactions.py index da977e080..bf1a18b94 100644 --- a/starknet_py/net/schemas/rpc/transactions.py +++ b/starknet_py/net/schemas/rpc/transactions.py @@ -31,13 +31,13 @@ ExecutionStatusField, Felt, FinalityStatusField, + MessageFinalityStatusField, NumberAsHex, PriceUnitField, StatusField, TransactionTypeField, Uint64, Uint128, - MessageFinalityStatusField, ) from starknet_py.net.schemas.rpc.event import EventSchema from starknet_py.net.schemas.rpc.general import ExecutionResourcesSchema diff --git a/starknet_py/tests/e2e/client/client_test.py b/starknet_py/tests/e2e/client/client_test.py index 7497dd30a..8e7ce68ed 100644 --- a/starknet_py/tests/e2e/client/client_test.py +++ b/starknet_py/tests/e2e/client/client_test.py @@ -447,9 +447,7 @@ async def test_wait_for_tx_accepted(client, get_tx_receipt_path, get_tx_status_p @pytest.mark.asyncio -async def test_wait_for_tx_not_received( - client, get_tx_receipt_path, get_tx_status_path -): +async def test_wait_for_tx_not_received(client, get_tx_status_path): exc_message = "Transaction not received." with patch(get_tx_status_path, AsyncMock()) as mocked_status: From bfa0c25c11e950ba85ecf5cdd80edf7c6e1f7cad Mon Sep 17 00:00:00 2001 From: Artur Michalek Date: Mon, 7 Jul 2025 09:59:48 +0200 Subject: [PATCH 13/32] Fix `test_get_compiled_casm` --- starknet_py/tests/e2e/client/client_test.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/starknet_py/tests/e2e/client/client_test.py b/starknet_py/tests/e2e/client/client_test.py index 8e7ce68ed..9d2528839 100644 --- a/starknet_py/tests/e2e/client/client_test.py +++ b/starknet_py/tests/e2e/client/client_test.py @@ -39,6 +39,7 @@ CasmClass, Deref, Immediate, + TestLessThan, TestLessThanOrEqual, ) from starknet_py.net.full_node_client import FullNodeClient @@ -261,15 +262,17 @@ async def test_get_compiled_casm(client): assert first_hint.test_less_than_or_equal.rhs.deref.register == "FP" second_hint = compiled_casm.hints[1][1][0] - assert isinstance(second_hint, TestLessThanOrEqual) - assert isinstance(second_hint.test_less_than_or_equal.lhs, Deref) - assert second_hint.test_less_than_or_equal.lhs.deref.register == "AP" - assert second_hint.test_less_than_or_equal.lhs.deref.offset == -1 - assert isinstance(second_hint.test_less_than_or_equal.rhs, Deref) - assert second_hint.test_less_than_or_equal.rhs.deref.register == "AP" - assert second_hint.test_less_than_or_equal.rhs.deref.offset == -169 - assert second_hint.test_less_than_or_equal.dst.register == "AP" - assert second_hint.test_less_than_or_equal.dst.offset == 0 + assert isinstance(second_hint, TestLessThan) + assert isinstance(second_hint.test_less_than.lhs, Deref) + assert second_hint.test_less_than.lhs.deref.register == "AP" + assert second_hint.test_less_than.lhs.deref.offset == -2 + assert isinstance(second_hint.test_less_than.rhs, Immediate) + assert ( + second_hint.test_less_than.rhs.immediate + == 3618502788666131106986593281521497120414687020801267626233049500247285301248 + ) + assert second_hint.test_less_than.dst.register == "AP" + assert second_hint.test_less_than.dst.offset == 4 @pytest.mark.asyncio From 6d33811e6bc08bcacdc1c94253f2463521c23356 Mon Sep 17 00:00:00 2001 From: Artur Michalek Date: Mon, 7 Jul 2025 10:21:21 +0200 Subject: [PATCH 14/32] Fix `test_get_storage_proof` --- starknet_py/tests/e2e/tests_on_networks/client_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/starknet_py/tests/e2e/tests_on_networks/client_test.py b/starknet_py/tests/e2e/tests_on_networks/client_test.py index 3cd62ac25..4e9a5a6f0 100644 --- a/starknet_py/tests/e2e/tests_on_networks/client_test.py +++ b/starknet_py/tests/e2e/tests_on_networks/client_test.py @@ -518,7 +518,7 @@ async def test_get_storage_proof(client_sepolia_testnet): # Nodes don't support storage proofs for blocks that are too far in the past, hence we need to get last block number block_number = await client_sepolia_testnet.get_block_number() storage_proof = await client_sepolia_testnet.get_storage_proof( - block_id={"block_number": block_number}, + block_number=block_number, contract_addresses=[int(STRK_FEE_CONTRACT_ADDRESS, 16)], contracts_storage_keys=[ ContractsStorageKeys( From 6296505fdec9b6ae96122d162dc4f1096dfc4ff9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artur=20Micha=C5=82ek?= <52135326+cptartur@users.noreply.github.com> Date: Mon, 7 Jul 2025 23:14:45 +0200 Subject: [PATCH 15/32] Update starknet_py/net/client_models.py Co-authored-by: Franciszek Job <54181625+franciszekjob@users.noreply.github.com> --- starknet_py/net/client_models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/starknet_py/net/client_models.py b/starknet_py/net/client_models.py index d3a315e55..d8c623287 100644 --- a/starknet_py/net/client_models.py +++ b/starknet_py/net/client_models.py @@ -531,7 +531,7 @@ class PreConfirmedBlockHeader: @dataclass class PreConfirmedStarknetBlock(PreConfirmedBlockHeader): """ - Dataclass representing a pre_confirmed block on Starknet. + Dataclass representing a pre-confirmed block on Starknet. """ transactions: List[Transaction] From 1be41808f0ba37ed64f97fe8723fccda3a348312 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artur=20Micha=C5=82ek?= <52135326+cptartur@users.noreply.github.com> Date: Mon, 7 Jul 2025 23:14:57 +0200 Subject: [PATCH 16/32] Update starknet_py/tests/e2e/client/client_test.py Co-authored-by: Franciszek Job <54181625+franciszekjob@users.noreply.github.com> --- starknet_py/tests/e2e/client/client_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/starknet_py/tests/e2e/client/client_test.py b/starknet_py/tests/e2e/client/client_test.py index 9d2528839..fe99d7dce 100644 --- a/starknet_py/tests/e2e/client/client_test.py +++ b/starknet_py/tests/e2e/client/client_test.py @@ -459,7 +459,7 @@ async def test_wait_for_tx_not_received(client, get_tx_status_path): ) with pytest.raises(TransactionNotReceivedError) as err: - # We set `retires` to 1, otherwise `wait_for_tx` will try to fetch tx status until + # We set `retries` to 1, otherwise `wait_for_tx` will try to fetch tx status until # it is either `ACCEPTED_ON_L2` or `ACCEPTED_ON_L1` await client.wait_for_tx(tx_hash=0x1, retries=1) From 442b99ce3a09600e771d8389f97e02696c62f7cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artur=20Micha=C5=82ek?= <52135326+cptartur@users.noreply.github.com> Date: Mon, 7 Jul 2025 23:15:09 +0200 Subject: [PATCH 17/32] Update starknet_py/net/client.py Co-authored-by: Franciszek Job <54181625+franciszekjob@users.noreply.github.com> --- starknet_py/net/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/starknet_py/net/client.py b/starknet_py/net/client.py index bce166f38..83b5ad043 100644 --- a/starknet_py/net/client.py +++ b/starknet_py/net/client.py @@ -70,7 +70,7 @@ async def get_block_with_txs( block_hash: Optional[Union[Hash, Tag]] = None, block_number: Optional[Union[int, Tag]] = None, ) -> Union[StarknetBlock, PreConfirmedStarknetBlock]: - """ " + """ TODO docstring """ From ec246fe620a1a34ec727504e8b4fe231ef4df368 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artur=20Micha=C5=82ek?= <52135326+cptartur@users.noreply.github.com> Date: Mon, 7 Jul 2025 23:15:37 +0200 Subject: [PATCH 18/32] Update docs/migration_guide.rst Co-authored-by: Franciszek Job <54181625+franciszekjob@users.noreply.github.com> --- docs/migration_guide.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/migration_guide.rst b/docs/migration_guide.rst index 8b80ee70d..52c7fd696 100644 --- a/docs/migration_guide.rst +++ b/docs/migration_guide.rst @@ -28,7 +28,7 @@ Migration guide 1. :meth:`Client.get_storage_proof` replaced argument ``get_storage_proof`` with arguments ``block_hash`` and ``block_hash``. 2. :meth:`Client.wait_for_tx` will now wait until transaction ``finality_status`` is either ``ACCEPTED_ON_L2`` or ``ACCEPTED_ON_L1``. 3. :meth:`Client.wait_for_tx` will no longer raise ``TransactionRejectedError``, see the method docs for details. -4. :meth:`Client.get_messages_status` changed ``transaction_hash`` type from ``str`` to ``Hash``. +4. :meth:`Client.get_messages_status`: changed ``transaction_hash`` type from ``str`` to ``Hash``. ********************** 0.27.0 Migration guide From 08f145f3fcf1e527d56dde065b22befa9f7f78e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Artur=20Micha=C5=82ek?= <52135326+cptartur@users.noreply.github.com> Date: Mon, 7 Jul 2025 23:15:46 +0200 Subject: [PATCH 19/32] Update docs/migration_guide.rst Co-authored-by: Franciszek Job <54181625+franciszekjob@users.noreply.github.com> --- docs/migration_guide.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/migration_guide.rst b/docs/migration_guide.rst index 52c7fd696..6ec18cd1f 100644 --- a/docs/migration_guide.rst +++ b/docs/migration_guide.rst @@ -25,7 +25,7 @@ Migration guide .. py:currentmodule:: starknet_py.net.client -1. :meth:`Client.get_storage_proof` replaced argument ``get_storage_proof`` with arguments ``block_hash`` and ``block_hash``. +1. :meth:`Client.get_storage_proof`: replaced param ``block_id`` with ``block_hash`` and ``block_number``. 2. :meth:`Client.wait_for_tx` will now wait until transaction ``finality_status`` is either ``ACCEPTED_ON_L2`` or ``ACCEPTED_ON_L1``. 3. :meth:`Client.wait_for_tx` will no longer raise ``TransactionRejectedError``, see the method docs for details. 4. :meth:`Client.get_messages_status`: changed ``transaction_hash`` type from ``str`` to ``Hash``. From b9b496dbf7bf374f941c52e124f53575c68870a8 Mon Sep 17 00:00:00 2001 From: Artur Michalek Date: Mon, 7 Jul 2025 23:16:48 +0200 Subject: [PATCH 20/32] Fix comments --- starknet_py/tests/e2e/account/account_test.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/starknet_py/tests/e2e/account/account_test.py b/starknet_py/tests/e2e/account/account_test.py index cb515881e..8324ce9a6 100644 --- a/starknet_py/tests/e2e/account/account_test.py +++ b/starknet_py/tests/e2e/account/account_test.py @@ -75,7 +75,7 @@ async def test_estimated_fee_greater_than_zero(account, erc20_contract): assert estimated_fee.overall_fee > 0 # `overall_fee` returned from the node includes the tip which is not part of the response itself and cannot - # be calculated using the ` calculate_overall_fee ` method. + # be calculated using the `calculate_overall_fee` method. assert estimated_fee.calculate_overall_fee() <= estimated_fee.overall_fee @@ -95,7 +95,7 @@ async def test_estimate_fee_for_declare_transaction( assert isinstance(estimated_fee.overall_fee, int) assert estimated_fee.overall_fee > 0 # `overall_fee` returned from the node includes the tip which is not part of the response itself and cannot - # be calculated using the ` calculate_overall_fee ` method. + # be calculated using the `calculate_overall_fee` method. assert estimated_fee.calculate_overall_fee() <= estimated_fee.overall_fee @@ -116,7 +116,7 @@ async def test_account_estimate_fee_for_declare_transaction( assert isinstance(estimated_fee.overall_fee, int) assert estimated_fee.overall_fee > 0 # `overall_fee` returned from the node includes the tip which is not part of the response itself and cannot - # be calculated using the ` calculate_overall_fee ` method. + # be calculated using the `calculate_overall_fee` method. assert estimated_fee.calculate_overall_fee() <= estimated_fee.overall_fee @@ -144,7 +144,7 @@ async def test_account_estimate_fee_for_transactions(account, map_contract): assert isinstance(estimated_fee[0].overall_fee, int) assert estimated_fee[0].overall_fee > 0 # `overall_fee` returned from the node includes the tip which is not part of the response itself and cannot - # be calculated using the ` calculate_overall_fee ` method. + # be calculated using the `calculate_overall_fee` method. assert estimated_fee[0].calculate_overall_fee() <= estimated_fee[0].overall_fee From c3d9abef268f4060b090cc38c6dbda27115a1945 Mon Sep 17 00:00:00 2001 From: Artur Michalek Date: Mon, 7 Jul 2025 23:21:19 +0200 Subject: [PATCH 21/32] Mention supported rpc version in migration_guide.rst --- docs/migration_guide.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/migration_guide.rst b/docs/migration_guide.rst index 6ec18cd1f..169497858 100644 --- a/docs/migration_guide.rst +++ b/docs/migration_guide.rst @@ -5,6 +5,8 @@ Migration guide 0.28.0 Migration guide ********************** +Version 0.28.0 of **starknet.py** comes with support for RPC 0.9.0-rc.1! + ``starknet_py.net.client_models`` --------------------------------- From c7a70f9f748552ba7005ce0618547bcd638d63f4 Mon Sep 17 00:00:00 2001 From: Artur Michalek Date: Mon, 7 Jul 2025 23:22:47 +0200 Subject: [PATCH 22/32] Replace `_to_hex_number` with `_to_rpc_felt` --- starknet_py/net/client_utils.py | 9 --------- starknet_py/net/full_node_client.py | 3 +-- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/starknet_py/net/client_utils.py b/starknet_py/net/client_utils.py index 171d4040f..583886bfe 100644 --- a/starknet_py/net/client_utils.py +++ b/starknet_py/net/client_utils.py @@ -74,15 +74,6 @@ def _to_rpc_felt(value: Hash) -> str: return rpc_felt -def _to_hex_number(value: Hash) -> str: - """ - Convert the value to hex encoded number - """ - if isinstance(value, str): - return value - return hex(value) - - def _is_valid_eth_address(address: str) -> bool: """ A function checking if an address matches Ethereum address regex. Note that it doesn't validate any checksums etc. diff --git a/starknet_py/net/full_node_client.py b/starknet_py/net/full_node_client.py index b36166c86..1525c3c42 100644 --- a/starknet_py/net/full_node_client.py +++ b/starknet_py/net/full_node_client.py @@ -43,7 +43,6 @@ _create_broadcasted_txn, _get_raw_block_identifier, _is_valid_eth_address, - _to_hex_number, _to_rpc_felt, _to_storage_key, encode_l1_message, @@ -524,7 +523,7 @@ async def get_chain_id(self) -> str: async def get_messages_status(self, transaction_hash: Hash) -> List[MessageStatus]: res = await self._client.call( method_name="getMessagesStatus", - params={"transaction_hash": _to_hex_number(transaction_hash)}, + params={"transaction_hash": _to_rpc_felt(transaction_hash)}, ) return cast( List[MessageStatus], From 6c3fc7c7f75bd6fc2624d29aada60d40f9e9a398 Mon Sep 17 00:00:00 2001 From: Artur Michalek Date: Mon, 7 Jul 2025 23:33:06 +0200 Subject: [PATCH 23/32] Add docstrings --- starknet_py/net/client.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/starknet_py/net/client.py b/starknet_py/net/client.py index 83b5ad043..add68ccef 100644 --- a/starknet_py/net/client.py +++ b/starknet_py/net/client.py @@ -59,6 +59,8 @@ async def get_block( """ Retrieve the block's data by its number or hash + Alias of :meth:`get_block_with_txs`. + :param block_hash: Block's hash or literals `"pre_confirmed"` or `"latest"` :param block_number: Block's number or literals `"pre_confirmed"` or `"latest"` :return: StarknetBlock object representing retrieved block @@ -71,7 +73,11 @@ async def get_block_with_txs( block_number: Optional[Union[int, Tag]] = None, ) -> Union[StarknetBlock, PreConfirmedStarknetBlock]: """ - TODO docstring + Retrieve the block's data by its number or hash. + + :param block_hash: Block's hash or literals `"pre_confirmed"` or `"latest"` + :param block_number: Block's number or literals `"pre_confirmed"` or `"latest"` + :return: StarknetBlock object representing retrieved block with transactions. """ @abstractmethod @@ -81,7 +87,11 @@ async def get_block_with_tx_hashes( block_number: Optional[Union[int, Tag]] = None, ) -> Union[StarknetBlockWithTxHashes, PreConfirmedStarknetBlockWithTxHashes]: """ - TODO docstring + Retrieve the block's data with a list of contained transaction hashes. + + :param block_hash: Block's hash or literals `"pre_confirmed"` or `"latest"` + :param block_number: Block's number or literals `"pre_confirmed"` or `"latest"` + :return: StarknetBlockWithTxHashes object representing retrieved block with transactions. """ @abstractmethod @@ -91,7 +101,11 @@ async def get_block_with_receipts( block_number: Optional[Union[int, Tag]] = None, ) -> Union[StarknetBlockWithReceipts, PreConfirmedStarknetBlockWithReceipts]: """ - TODO docstring + Retrieve the block's data with a list of receipts for contained transactions. + + :param block_hash: Block's hash or literals `"pre_confirmed"` or `"latest"` + :param block_number: Block's number or literals `"pre_confirmed"` or `"latest"` + :return: StarknetBlockWithReceipts object representing retrieved block with transactions. """ @abstractmethod From cd610fcb702fdbe64612924133d24750e50ad030 Mon Sep 17 00:00:00 2001 From: Artur Michalek Date: Mon, 7 Jul 2025 23:42:45 +0200 Subject: [PATCH 24/32] Update devnet to 0.5.0-rc.1 --- .github/workflows/checks.yml | 2 +- starknet_py/tests/e2e/account/account_test.py | 16 ++++------------ starknet_py/tests/install_devnet.sh | 2 +- 3 files changed, 6 insertions(+), 14 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index a03becd29..0b304f1b7 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -3,7 +3,7 @@ name: Checks env: CAIRO_LANG_VERSION: "0.13.1" # TODO(#1611) - DEVNET_SHA: dab72df7e14b5117c8daec35ecf7c432411e9561 # v0.5.0-rc.0 + DEVNET_SHA: aafa74e4297734bacba72d0faa7c711eacecfc7a # v0.5.0-rc.0 LEDGER_APP_SHA: 768a7b47b0da681b28112342edd76e2c9b292c4e # v2.3.1 LEDGER_APP_DEV_TOOLS_SHA: a845b2ab0b5dd824133f73858f6f373edea85ec1bd828245bf50ce9700f33bcb # v4.5.0 diff --git a/starknet_py/tests/e2e/account/account_test.py b/starknet_py/tests/e2e/account/account_test.py index 8324ce9a6..4d70492ae 100644 --- a/starknet_py/tests/e2e/account/account_test.py +++ b/starknet_py/tests/e2e/account/account_test.py @@ -74,9 +74,7 @@ async def test_estimated_fee_greater_than_zero(account, erc20_contract): ) assert estimated_fee.overall_fee > 0 - # `overall_fee` returned from the node includes the tip which is not part of the response itself and cannot - # be calculated using the `calculate_overall_fee` method. - assert estimated_fee.calculate_overall_fee() <= estimated_fee.overall_fee + assert estimated_fee.calculate_overall_fee() == estimated_fee.overall_fee @pytest.mark.asyncio @@ -94,9 +92,7 @@ async def test_estimate_fee_for_declare_transaction( assert isinstance(estimated_fee.overall_fee, int) assert estimated_fee.overall_fee > 0 - # `overall_fee` returned from the node includes the tip which is not part of the response itself and cannot - # be calculated using the `calculate_overall_fee` method. - assert estimated_fee.calculate_overall_fee() <= estimated_fee.overall_fee + assert estimated_fee.calculate_overall_fee() == estimated_fee.overall_fee @pytest.mark.asyncio @@ -115,9 +111,7 @@ async def test_account_estimate_fee_for_declare_transaction( assert estimated_fee.unit == PriceUnit.FRI assert isinstance(estimated_fee.overall_fee, int) assert estimated_fee.overall_fee > 0 - # `overall_fee` returned from the node includes the tip which is not part of the response itself and cannot - # be calculated using the `calculate_overall_fee` method. - assert estimated_fee.calculate_overall_fee() <= estimated_fee.overall_fee + assert estimated_fee.calculate_overall_fee() == estimated_fee.overall_fee @pytest.mark.asyncio @@ -143,9 +137,7 @@ async def test_account_estimate_fee_for_transactions(account, map_contract): assert estimated_fee[1].unit == PriceUnit.FRI assert isinstance(estimated_fee[0].overall_fee, int) assert estimated_fee[0].overall_fee > 0 - # `overall_fee` returned from the node includes the tip which is not part of the response itself and cannot - # be calculated using the `calculate_overall_fee` method. - assert estimated_fee[0].calculate_overall_fee() <= estimated_fee[0].overall_fee + assert estimated_fee[0].calculate_overall_fee() == estimated_fee[0].overall_fee @pytest.mark.asyncio diff --git a/starknet_py/tests/install_devnet.sh b/starknet_py/tests/install_devnet.sh index db615c391..ba1f191b0 100755 --- a/starknet_py/tests/install_devnet.sh +++ b/starknet_py/tests/install_devnet.sh @@ -3,7 +3,7 @@ set -e DEVNET_INSTALL_DIR="$(git rev-parse --show-toplevel)/starknet_py/tests/e2e/devnet/bin" DEVNET_REPO="https://github.com/0xSpaceShard/starknet-devnet-rs" -DEVNET_VERSION="v0.5.0-rc.0" +DEVNET_VERSION="v0.5.0-rc.1" require_cmd() { if ! command -v "$1" >/dev/null 2>&1; then From 1f2d21e05f0ace254b9b7ea9b70a01c42223a9be Mon Sep 17 00:00:00 2001 From: Artur Michalek Date: Tue, 8 Jul 2025 12:11:22 +0200 Subject: [PATCH 25/32] Add support for tip --- starknet_py/contract.py | 12 ++++++++++++ starknet_py/net/account/account.py | 20 ++++++++++++++++++-- starknet_py/net/account/base_account.py | 9 +++++++++ starknet_py/net/models/transaction.py | 2 +- starknet_py/net/schemas/rpc/transactions.py | 2 +- 5 files changed, 41 insertions(+), 4 deletions(-) diff --git a/starknet_py/contract.py b/starknet_py/contract.py index 25966d5d5..12bcaa7c5 100644 --- a/starknet_py/contract.py +++ b/starknet_py/contract.py @@ -197,6 +197,7 @@ async def deploy_v3( nonce: Optional[int] = None, resource_bounds: Optional[ResourceBoundsMapping] = None, auto_estimate: bool = False, + tip: int = 0, ) -> "DeployResult": """ Deploys a contract. @@ -210,6 +211,7 @@ async def deploy_v3( :param nonce: Nonce of the transaction with call to deployer. :param resource_bounds: Resource limits (L1 and L2) used when executing this transaction. :param auto_estimate: Use automatic fee estimation (not recommended, as it may lead to high costs). + :param tip: The tip amount to be added to the transaction fee. :return: DeployResult instance. """ # pylint: disable=too-many-arguments, too-many-locals @@ -227,6 +229,7 @@ async def deploy_v3( auto_estimate=auto_estimate, salt=salt, unique=unique, + tip=tip, ) def _get_abi(self) -> List: @@ -379,6 +382,7 @@ async def invoke( auto_estimate: bool = False, *, nonce: Optional[int] = None, + tip: int = 0, ) -> InvokeResult: """ Send an Invoke transaction version 3 for the prepared data. @@ -386,6 +390,7 @@ async def invoke( :param resource_bounds: Resource limits (L1 and L2) used when executing this transaction. :param auto_estimate: Use automatic fee estimation (not recommended, as it may lead to high costs). :param nonce: Nonce of the transaction. + :param tip: The tip amount to be added to the transaction fee. :return: InvokeResult. """ @@ -394,6 +399,7 @@ async def invoke( nonce=nonce, resource_bounds=resource_bounds or self.resource_bounds, auto_estimate=auto_estimate, + tip=tip, ) return await self._invoke(transaction) @@ -679,6 +685,7 @@ async def declare_v3( nonce: Optional[int] = None, resource_bounds: Optional[ResourceBoundsMapping] = None, auto_estimate: bool = False, + tip: int = 0, ) -> DeclareResult: # pylint: disable=too-many-arguments @@ -692,6 +699,7 @@ async def declare_v3( :param nonce: Nonce of the transaction. :param resource_bounds: Resource limits (L1 and L2) used when executing this transaction. :param auto_estimate: Use automatic fee estimation (not recommended, as it may lead to high costs). + :param tip: The tip amount to be added to the transaction fee. :return: DeclareResult instance. """ @@ -705,6 +713,7 @@ async def declare_v3( nonce=nonce, resource_bounds=resource_bounds, auto_estimate=auto_estimate, + tip=tip, ) return await _declare_contract( @@ -725,6 +734,7 @@ async def deploy_contract_v3( auto_estimate: bool = False, salt: Optional[int] = None, unique: bool = True, + tip: int = 0, ) -> "DeployResult": """ Deploys a contract through Universal Deployer Contract. @@ -743,6 +753,7 @@ async def deploy_contract_v3( :param auto_estimate: Use automatic fee estimation (not recommended, as it may lead to high costs). :param salt: Optional salt. Random value is selected if it is not provided. :param unique: Determines if the contract should be salted with the account address. + :param tip: The tip amount to be added to the transaction fee. :return: DeployResult instance. """ # pylint: disable=too-many-arguments, too-many-locals @@ -763,6 +774,7 @@ async def deploy_contract_v3( nonce=nonce, resource_bounds=resource_bounds, auto_estimate=auto_estimate, + tip=tip, ) if abi is None: diff --git a/starknet_py/net/account/account.py b/starknet_py/net/account/account.py index 702518f34..a8c415166 100644 --- a/starknet_py/net/account/account.py +++ b/starknet_py/net/account/account.py @@ -169,6 +169,7 @@ async def _prepare_invoke_v3( resource_bounds: Optional[ResourceBoundsMapping] = None, nonce: Optional[int] = None, auto_estimate: bool = False, + tip: int, ) -> InvokeV3: """ Takes calls and creates InvokeV3 from them. @@ -190,6 +191,7 @@ async def _prepare_invoke_v3( nonce=nonce, sender_address=self.address, version=3, + tip=tip, ) resource_bounds = await self._get_resource_bounds( @@ -377,12 +379,14 @@ async def sign_invoke_v3( nonce: Optional[int] = None, resource_bounds: Optional[ResourceBoundsMapping] = None, auto_estimate: bool = False, + tip: int = 0, ) -> InvokeV3: invoke_tx = await self._prepare_invoke_v3( calls, resource_bounds=resource_bounds, nonce=nonce, auto_estimate=auto_estimate, + tip=tip, ) signature = self.signer.sign_transaction(invoke_tx) return _add_signature_to_transaction(invoke_tx, signature) @@ -395,11 +399,14 @@ async def sign_declare_v3( nonce: Optional[int] = None, resource_bounds: Optional[ResourceBoundsMapping] = None, auto_estimate: bool = False, + tip: int = 0, ) -> DeclareV3: + # pylint: disable=too-many-arguments declare_tx = await self._make_declare_v3_transaction( compiled_contract, compiled_class_hash, nonce=nonce, + tip=tip, ) resource_bounds = await self._get_resource_bounds( declare_tx, resource_bounds, auto_estimate @@ -415,6 +422,7 @@ async def _make_declare_v3_transaction( compiled_class_hash: int, *, nonce: Optional[int] = None, + tip: int, ) -> DeclareV3: contract_class = create_sierra_compiled_contract( compiled_contract=compiled_contract @@ -431,6 +439,7 @@ async def _make_declare_v3_transaction( nonce=nonce, version=3, resource_bounds=ResourceBoundsMapping.init_with_zeros(), + tip=tip, ) return declare_tx @@ -443,6 +452,7 @@ async def sign_deploy_account_v3( nonce: int = 0, resource_bounds: Optional[ResourceBoundsMapping] = None, auto_estimate: bool = False, + tip: int = 0, ) -> DeployAccountV3: # pylint: disable=too-many-arguments deploy_account_tx = DeployAccountV3( @@ -453,6 +463,7 @@ async def sign_deploy_account_v3( resource_bounds=ResourceBoundsMapping.init_with_zeros(), signature=[], nonce=nonce, + tip=tip, ) resource_bounds = await self._get_resource_bounds( deploy_account_tx, resource_bounds, auto_estimate @@ -471,14 +482,16 @@ async def execute_v3( resource_bounds: Optional[ResourceBoundsMapping] = None, nonce: Optional[int] = None, auto_estimate: bool = False, + tip: int = 0, ) -> SentTransactionResponse: # TODO(#1582): Remove below adjustment when braavos integration is restored try: execute_transaction = await self.sign_invoke_v3( calls, - resource_bounds=resource_bounds, nonce=nonce, + resource_bounds=resource_bounds, auto_estimate=auto_estimate, + tip=tip, ) return await self._client.send_transaction(execute_transaction) @@ -512,8 +525,9 @@ async def deploy_account_v3( nonce: int = 0, resource_bounds: Optional[ResourceBoundsMapping] = None, auto_estimate: bool = False, + tip: int = 0, ) -> AccountDeploymentResult: - # pylint: disable=too-many-arguments + # pylint: disable=too-many-arguments, too-many-locals """ Deploys an account contract with provided class_hash on Starknet and returns @@ -531,6 +545,7 @@ async def deploy_account_v3( :param nonce: Nonce of the transaction. :param resource_bounds: Resource limits (L1 and L2) used when executing this transaction. :param auto_estimate: Use automatic fee estimation, not recommend as it may lead to high costs. + :param tip: The tip amount to be added to the transaction fee. """ # TODO(#1582): Remove below adjustment when braavos integration is restored try: @@ -559,6 +574,7 @@ async def deploy_account_v3( nonce=nonce, resource_bounds=resource_bounds, auto_estimate=auto_estimate, + tip=tip, ) result = await client.deploy_account(deploy_account_tx) diff --git a/starknet_py/net/account/base_account.py b/starknet_py/net/account/base_account.py index 5c45ebd19..06d14d773 100644 --- a/starknet_py/net/account/base_account.py +++ b/starknet_py/net/account/base_account.py @@ -165,6 +165,7 @@ async def sign_invoke_v3( nonce: Optional[int] = None, resource_bounds: Optional[ResourceBoundsMapping] = None, auto_estimate: bool = False, + tip: int = 0, ) -> InvokeV3: """ Takes calls and creates signed Invoke. @@ -173,6 +174,7 @@ async def sign_invoke_v3( :param nonce: Nonce of the transaction. :param resource_bounds: Resource limits (L1 and L2) that can be used in this transaction. :param auto_estimate: Use automatic fee estimation, not recommend as it may lead to high costs. + :param tip: The tip amount to be added to the transaction fee. :return: Invoke created from the calls. """ @@ -185,7 +187,9 @@ async def sign_declare_v3( nonce: Optional[int] = None, resource_bounds: Optional[ResourceBoundsMapping] = None, auto_estimate: bool = False, + tip: int = 0, ) -> DeclareV3: + # pylint: disable=too-many-arguments """ Create and sign declare transaction version 3 using sierra contract. @@ -196,6 +200,7 @@ async def sign_declare_v3( :param nonce: Nonce of the transaction. :param resource_bounds: Resource limits (L1 and L2) that can be used in this transaction. :param auto_estimate: Use automatic fee estimation, not recommend as it may lead to high costs. + :param tip: The tip amount to be added to the transaction fee. :return: Signed DeclareV3 transaction. """ @@ -209,6 +214,7 @@ async def sign_deploy_account_v3( nonce: int = 0, resource_bounds: Optional[ResourceBoundsMapping] = None, auto_estimate: bool = False, + tip: int = 0, ) -> DeployAccountV3: # pylint: disable=too-many-arguments """ @@ -222,6 +228,7 @@ async def sign_deploy_account_v3( :param resource_bounds: Resource limits (L1 and L2) that can be used in this transaction. Enough tokens must be prefunded before sending the transaction for it to succeed. :param auto_estimate: Use automatic fee estimation, not recommend as it may lead to high costs. + :param tip: The tip amount to be added to the transaction fee. :return: Signed DeployAccountV3 transaction. """ @@ -233,6 +240,7 @@ async def execute_v3( resource_bounds: Optional[ResourceBoundsMapping] = None, nonce: Optional[int] = None, auto_estimate: bool = False, + tip: int = 0, ) -> SentTransactionResponse: """ Takes calls and executes transaction. @@ -241,6 +249,7 @@ async def execute_v3( :param resource_bounds: Resource limits (L1 and L2) that can be used in this transaction. :param nonce: Nonce of the transaction. :param auto_estimate: Use automatic fee estimation, not recommend as it may lead to high costs. + :param tip: The tip amount to be added to the transaction fee. :return: SentTransactionResponse. """ diff --git a/starknet_py/net/models/transaction.py b/starknet_py/net/models/transaction.py index 33c62f2ed..3f0c10e7f 100644 --- a/starknet_py/net/models/transaction.py +++ b/starknet_py/net/models/transaction.py @@ -94,10 +94,10 @@ class _DeprecatedAccountTransaction(AccountTransaction, ABC): @dataclass(frozen=True) class _AccountTransactionV3(AccountTransaction, ABC): resource_bounds: ResourceBoundsMapping - tip: int = field(init=False, default=0) nonce_data_availability_mode: DAMode = field(init=False, default=DAMode.L1) fee_data_availability_mode: DAMode = field(init=False, default=DAMode.L1) paymaster_data: List[int] = field(init=False, default_factory=list) + tip: int def get_common_fields( self, diff --git a/starknet_py/net/schemas/rpc/transactions.py b/starknet_py/net/schemas/rpc/transactions.py index bf1a18b94..7d08b8858 100644 --- a/starknet_py/net/schemas/rpc/transactions.py +++ b/starknet_py/net/schemas/rpc/transactions.py @@ -134,7 +134,7 @@ class DeprecatedTransactionSchema(TransactionSchema): class TransactionV3Schema(TransactionSchema): - tip = Uint64(data_key="tip", load_default=0) + tip = Uint64(data_key="tip", required=True) nonce_data_availability_mode = DAModeField( data_key="nonce_data_availability_mode", load_default=DAMode.L1 ) From cd8a70b3c9e9814a53cda59f16696faacc1cff31 Mon Sep 17 00:00:00 2001 From: Artur Michalek Date: Tue, 8 Jul 2025 12:11:47 +0200 Subject: [PATCH 26/32] Add tests --- starknet_py/tests/e2e/account/account_test.py | 66 +++++++++++++++++++ starknet_py/tests/e2e/client/client_test.py | 40 +++++++++++ .../e2e/contract_interaction/declare_test.py | 21 +++++- .../e2e/contract_interaction/deploy_test.py | 11 +++- .../e2e/tests_on_networks/client_test.py | 2 +- .../tests/unit/signer/test_ledger_signer.py | 4 +- 6 files changed, 140 insertions(+), 4 deletions(-) diff --git a/starknet_py/tests/e2e/account/account_test.py b/starknet_py/tests/e2e/account/account_test.py index 4d70492ae..28891b123 100644 --- a/starknet_py/tests/e2e/account/account_test.py +++ b/starknet_py/tests/e2e/account/account_test.py @@ -12,6 +12,7 @@ from starknet_py.net.client_errors import ClientError from starknet_py.net.client_models import ( Call, + DeclareTransactionV3, DeployAccountTransactionResponse, DeployAccountTransactionV3, EstimatedFee, @@ -646,3 +647,68 @@ async def test_account_execute_v3(account, deployed_balance_contract): call=get_balance_call ) assert initial_balance + 100 == balance_after_increase + + +@pytest.mark.asyncio +async def test_invoke_with_tip(account, hello_starknet_class_hash): + deployment = Deployer().create_contract_deployment(hello_starknet_class_hash) + + invoke_tx = await account.execute_v3( + Call( + deployment.address, + get_selector_from_name("increase_balance"), + [20000], + ), + resource_bounds=MAX_RESOURCE_BOUNDS, + tip=123456, + ) + + transaction = await account.client.get_transaction( + tx_hash=invoke_tx.transaction_hash + ) + + assert isinstance(transaction, InvokeTransactionV3) + assert transaction.tip == 123456 + + +@pytest.mark.asyncio +async def test_deploy_account_v3_with_tip(client, deploy_account_details_factory): + address, key_pair, salt, class_hash = await deploy_account_details_factory.get() + + tip = 12345 + deploy_result = await Account.deploy_account_v3( + address=address, + class_hash=class_hash, + salt=salt, + key_pair=key_pair, + client=client, + resource_bounds=MAX_RESOURCE_BOUNDS, + tip=tip, + ) + await deploy_result.wait_for_acceptance() + + transaction = await client.get_transaction(tx_hash=deploy_result.hash) + assert isinstance(transaction, DeployAccountTransactionV3) + assert transaction.tip == tip + + +@pytest.mark.asyncio +async def test_declare_v3_with_tip( + account, sierra_minimal_compiled_contract_and_class_hash +): + ( + compiled_contract, + compiled_class_hash, + ) = sierra_minimal_compiled_contract_and_class_hash + tip = 12345 + signed_tx = await account.sign_declare_v3( + compiled_contract, + compiled_class_hash, + resource_bounds=MAX_RESOURCE_BOUNDS, + tip=tip, + ) + + result = await account.client.declare(signed_tx) + transaction = await account.client.get_transaction(tx_hash=result.transaction_hash) + assert isinstance(transaction, DeclareTransactionV3) + assert transaction.tip == tip diff --git a/starknet_py/tests/e2e/client/client_test.py b/starknet_py/tests/e2e/client/client_test.py index fe99d7dce..6d1f7dc91 100644 --- a/starknet_py/tests/e2e/client/client_test.py +++ b/starknet_py/tests/e2e/client/client_test.py @@ -407,6 +407,46 @@ async def test_add_transaction(map_contract, client, account): assert transaction_receipt.type == TransactionType.INVOKE +@pytest.mark.asyncio +async def test_add_transaction_with_tip(map_contract, client, account): + prepared_function_call = map_contract.functions["put"].prepare_invoke_v3( + key=100, value=200 + ) + tip = 20000 + signed_invoke = await account.sign_invoke_v3( + calls=prepared_function_call, resource_bounds=MAX_RESOURCE_BOUNDS, tip=tip + ) + + result = await client.send_transaction(signed_invoke) + await client.wait_for_tx(result.transaction_hash) + transaction_receipt = await client.get_transaction_receipt(result.transaction_hash) + + assert transaction_receipt.execution_status == TransactionExecutionStatus.SUCCEEDED + assert transaction_receipt.type == TransactionType.INVOKE + + transaction = await client.get_transaction(result.transaction_hash) + assert isinstance(transaction, InvokeTransactionV3) + assert transaction.tip == tip + + +@pytest.mark.asyncio +async def test_add_declare_transaction_with_tip( + client, account, abi_types_compiled_contract_and_class_hash +): + tip = 12345 + declare = await account.sign_declare_v3( + compiled_contract=abi_types_compiled_contract_and_class_hash[0], + compiled_class_hash=abi_types_compiled_contract_and_class_hash[1], + resource_bounds=MAX_RESOURCE_BOUNDS, + tip=tip, + ) + result = await client.declare(declare) + + transaction = await client.get_transaction(result.transaction_hash) + assert isinstance(transaction, DeclareTransactionV3) + assert transaction.tip == tip + + @pytest.mark.asyncio async def test_get_class_hash_at(client, contract_address, class_hash): received_class_hash = await client.get_class_hash_at( diff --git a/starknet_py/tests/e2e/contract_interaction/declare_test.py b/starknet_py/tests/e2e/contract_interaction/declare_test.py index 5cb71f243..6ff7f6c2a 100644 --- a/starknet_py/tests/e2e/contract_interaction/declare_test.py +++ b/starknet_py/tests/e2e/contract_interaction/declare_test.py @@ -2,7 +2,7 @@ from starknet_py.contract import Contract from starknet_py.tests.e2e.fixtures.constants import MAX_RESOURCE_BOUNDS -from starknet_py.tests.e2e.fixtures.misc import load_contract +from starknet_py.tests.e2e.fixtures.misc import ContractVersion, load_contract @pytest.mark.asyncio @@ -28,3 +28,22 @@ async def test_throws_when_cairo1_without_compiled_contract_casm_and_class_hash( compiled_contract=compiled_contract, resource_bounds=MAX_RESOURCE_BOUNDS, ) + + +@pytest.mark.asyncio +async def test_declare( + account, +): + contract = load_contract(contract_name="TestContract", version=ContractVersion.V2) + + tip = 12345 + declare_result = await Contract.declare_v3( + account, + compiled_contract=contract["sierra"], + compiled_contract_casm=contract["casm"], + resource_bounds=MAX_RESOURCE_BOUNDS, + tip=tip, + ) + + await declare_result.wait_for_acceptance() + assert declare_result.declare_transaction.tip == tip diff --git a/starknet_py/tests/e2e/contract_interaction/deploy_test.py b/starknet_py/tests/e2e/contract_interaction/deploy_test.py index 60018414e..df937e1ff 100644 --- a/starknet_py/tests/e2e/contract_interaction/deploy_test.py +++ b/starknet_py/tests/e2e/contract_interaction/deploy_test.py @@ -29,12 +29,18 @@ async def test_declare_deploy_v3( declare_transaction=Mock(spec=DeclareV3), ) - deploy_result = await declare_result.deploy_v3(resource_bounds=MAX_RESOURCE_BOUNDS) + tip = 12345 + deploy_result = await declare_result.deploy_v3( + resource_bounds=MAX_RESOURCE_BOUNDS, tip=tip + ) await deploy_result.wait_for_acceptance() assert isinstance(deploy_result.hash, int) assert deploy_result.hash != 0 assert deploy_result.deployed_contract.address != 0 + transaction = await account.client.get_transaction(deploy_result.hash) + assert isinstance(transaction, InvokeTransactionV3) + assert transaction.tip == tip @pytest.mark.asyncio @@ -73,11 +79,13 @@ async def test_deploy_contract_v3(account, hello_starknet_class_hash: int): compiled_contract=compiled_contract ).parsed_abi + tip = 12345 deploy_result = await Contract.deploy_contract_v3( class_hash=hello_starknet_class_hash, account=account, abi=abi, resource_bounds=MAX_RESOURCE_BOUNDS, + tip=tip, ) await deploy_result.wait_for_acceptance() @@ -88,6 +96,7 @@ async def test_deploy_contract_v3(account, hello_starknet_class_hash: int): transaction = await account.client.get_transaction(tx_hash=deploy_result.hash) assert isinstance(transaction, InvokeTransactionV3) + assert transaction.tip == tip class_hash = await account.client.get_class_hash_at( contract_address=contract.address diff --git a/starknet_py/tests/e2e/tests_on_networks/client_test.py b/starknet_py/tests/e2e/tests_on_networks/client_test.py index 4e9a5a6f0..3a327380a 100644 --- a/starknet_py/tests/e2e/tests_on_networks/client_test.py +++ b/starknet_py/tests/e2e/tests_on_networks/client_test.py @@ -184,7 +184,7 @@ async def test_transaction_not_received_invalid_nonce(account_sepolia_testnet): calldata=[], ) sign_invoke = await account.sign_invoke_v3( - calls=call, resource_bounds=MAX_RESOURCE_BOUNDS_SEPOLIA, nonce=0 + calls=call, nonce=0, resource_bounds=MAX_RESOURCE_BOUNDS_SEPOLIA ) with pytest.raises(ClientError, match=r".*nonce.*"): diff --git a/starknet_py/tests/unit/signer/test_ledger_signer.py b/starknet_py/tests/unit/signer/test_ledger_signer.py index a6ab5b9f9..d00467237 100644 --- a/starknet_py/tests/unit/signer/test_ledger_signer.py +++ b/starknet_py/tests/unit/signer/test_ledger_signer.py @@ -37,6 +37,7 @@ 0, ], sender_address=0x123, + tip=0, ), DeployAccountV3( class_hash=0x123, @@ -46,6 +47,7 @@ signature=[], nonce=0, resource_bounds=MAX_RESOURCE_BOUNDS_SEPOLIA, + tip=0, ), ], ) @@ -129,7 +131,6 @@ def test_blind_sign_warning(): "the contents and leaving you vulnerable to unknowingly authorizing malicious transactions. " "⚠️ Use at your own risk" ) - tx = InvokeV3( version=3, signature=[], @@ -145,6 +146,7 @@ def test_blind_sign_warning(): 0, ], sender_address=0x123, + tip=0, ) with pytest.warns(BlindSigningModeWarning, match=pattern): signer.sign_transaction(tx) From 15d06ba39ed2271c7862b70e70ea1c4ba7d7fd2b Mon Sep 17 00:00:00 2001 From: Artur Michalek Date: Tue, 8 Jul 2025 14:52:49 +0200 Subject: [PATCH 27/32] Update migration_guide.rst --- docs/migration_guide.rst | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/docs/migration_guide.rst b/docs/migration_guide.rst index 169497858..51db45be7 100644 --- a/docs/migration_guide.rst +++ b/docs/migration_guide.rst @@ -32,6 +32,36 @@ Version 0.28.0 of **starknet.py** comes with support for RPC 0.9.0-rc.1! 3. :meth:`Client.wait_for_tx` will no longer raise ``TransactionRejectedError``, see the method docs for details. 4. :meth:`Client.get_messages_status`: changed ``transaction_hash`` type from ``str`` to ``Hash``. +Tip Support +----------- + +Ability to pass tip for the transaction has been added to following methods. +These methods will use the default value o ``0`` for tip if not provided. + +.. py:currentmodule:: starknet_py.contract + +- :meth:`DeclareResult.deploy_v3` +- :meth:`PreparedFunctionInvokeV3.invoke` +- :meth:`Contract.declare_v3` +- :meth:`Contract.deploy_contract_v3` + +.. py:currentmodule:: starknet_py.net.account.account + +- :meth:`Account.sign_invoke_v3` +- :meth:`Account.sign_declare_v3` +- :meth:`Account.sign_deploy_account_v3` +- :meth:`Account.execute_v3` +- :meth:`Account.deploy_account_v3` + +Additionally, dataclasses representing transactions now require passing a tip. +No default value is used for tip and it is a required parameter. + +.. py:currentmodule:: starknet_py.net.models.transaction + +- :class:`InvokeV3`, tip is now required +- :class:`DeclareV3`, tip is now required +- :class:`DeployAccountV3`, tip is now required + ********************** 0.27.0 Migration guide ********************** From 0059ce3813d88710d4522501f938aa9d92a382fa Mon Sep 17 00:00:00 2001 From: Fiiranek Date: Thu, 20 Nov 2025 17:15:27 +0100 Subject: [PATCH 28/32] Remove changes --- docs/migration_guide.rst | 61 ---------------------------------------- 1 file changed, 61 deletions(-) diff --git a/docs/migration_guide.rst b/docs/migration_guide.rst index 487e69bc2..f0797da81 100644 --- a/docs/migration_guide.rst +++ b/docs/migration_guide.rst @@ -275,67 +275,6 @@ No default value is used for tip and it is a required parameter. - :class:`DeclareV3`, tip is now required - :class:`DeployAccountV3`, tip is now required -********************** -0.28.0 Migration guide -********************** - -Version 0.28.0 of **starknet.py** comes with support for RPC 0.9.0-rc.1! - -``starknet_py.net.client_models`` ---------------------------------- - -.. py:currentmodule:: starknet_py.net.client_models - -1. Renamed :class:`PendingBlockHeader` to :class:`PreConfirmedBlockHeader`, changed field ``parent_hash`` to ``block_number``. -2. Renamed :class:`PendingStarknetBlock` to :class:`PreConfirmedStarknetBlock` -3. Renamed :class:`PendingStarknetBlockWithTxHashes` to :class:`PreConfirmedStarknetBlockWithTxHashes` -4. Renamed :class:`PendingStarknetBlockWithReceipts` to :class:`PreConfirmedStarknetBlockWithReceipts` -5. Renamed :class:`PendingBlockStateUpdate` to :class:`PreConfirmedBlockStateUpdate` -6. Enum :class:`BlockStatus` variant ``PENDING`` removed, added ``PRE_CONFIRMED`` -7. Enum :class:`TransactionFinalityStatus`, added variant ``PRE_CONFIRMED`` -8. Enum :class:`TransactionStatus` variant ``REJECTED`` removed, added ``CANDIDATE``, ``PRE_CONFIRMED`` - -``starknet_py.net.client`` --------------------------- - -.. py:currentmodule:: starknet_py.net.client - - -1. :meth:`Client.get_storage_proof`: replaced param ``block_id`` with ``block_hash`` and ``block_number``. -2. :meth:`Client.wait_for_tx` will now wait until transaction ``finality_status`` is either ``ACCEPTED_ON_L2`` or ``ACCEPTED_ON_L1``. -3. :meth:`Client.wait_for_tx` will no longer raise ``TransactionRejectedError``, see the method docs for details. -4. :meth:`Client.get_messages_status`: changed ``transaction_hash`` type from ``str`` to ``Hash``. - -Tip Support ------------ - -Ability to pass tip for the transaction has been added to following methods. -These methods will use the default value o ``0`` for tip if not provided. - -.. py:currentmodule:: starknet_py.contract - -- :meth:`DeclareResult.deploy_v3` -- :meth:`PreparedFunctionInvokeV3.invoke` -- :meth:`Contract.declare_v3` -- :meth:`Contract.deploy_contract_v3` - -.. py:currentmodule:: starknet_py.net.account.account - -- :meth:`Account.sign_invoke_v3` -- :meth:`Account.sign_declare_v3` -- :meth:`Account.sign_deploy_account_v3` -- :meth:`Account.execute_v3` -- :meth:`Account.deploy_account_v3` - -Additionally, dataclasses representing transactions now require passing a tip. -No default value is used for tip and it is a required parameter. - -.. py:currentmodule:: starknet_py.net.models.transaction - -- :class:`InvokeV3`, tip is now required -- :class:`DeclareV3`, tip is now required -- :class:`DeployAccountV3`, tip is now required - ********************** 0.27.0 Migration guide ********************** From 68aafd36fca5da4d678a7d1780d234e078689340 Mon Sep 17 00:00:00 2001 From: Fiiranek Date: Thu, 20 Nov 2025 17:26:15 +0100 Subject: [PATCH 29/32] Support blake hash --- docs/migration_guide.rst | 6 ++ poetry.lock | 14 +++- pyproject.toml | 1 + starknet_py/contract.py | 8 +- starknet_py/contract_utils.py | 4 +- starknet_py/hash/casm_class_hash.py | 39 ++++++---- .../hash/compiled_class_hash_objects.py | 32 +++++--- starknet_py/hash/hash_method.py | 6 ++ .../mock/contracts_v1/src/test_contract.cairo | 5 ++ .../tests/unit/hash/casm_class_hash_test.py | 75 +++++++++++++++++-- 10 files changed, 155 insertions(+), 35 deletions(-) diff --git a/docs/migration_guide.rst b/docs/migration_guide.rst index 487e69bc2..899d56711 100644 --- a/docs/migration_guide.rst +++ b/docs/migration_guide.rst @@ -1,6 +1,12 @@ Migration guide =============== +*************************** +0.28.1 Migration guide +*************************** + +1. This version adds support for Blake hash, in order to allow compatibility for Starknet versions >= 0.14.1. + *************************** 0.28.0 Migration guide *************************** diff --git a/poetry.lock b/poetry.lock index 40c235726..57544ad4c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2777,6 +2777,18 @@ files = [ {file = "ruamel.yaml.clib-0.2.12.tar.gz", hash = "sha256:6c8fbb13ec503f99a91901ab46e0b07ae7941cd527393187039aec586fdfd36f"}, ] +[[package]] +name = "semver" +version = "3.0.4" +description = "Python helper for Semantic Versioning (https://semver.org)" +optional = false +python-versions = ">=3.7" +groups = ["main"] +files = [ + {file = "semver-3.0.4-py3-none-any.whl", hash = "sha256:9c824d87ba7f7ab4a1890799cec8596f15c1241cb473404ea1cb0c55e4b04746"}, + {file = "semver-3.0.4.tar.gz", hash = "sha256:afc7d8c584a5ed0a11033af086e8af226a9c0b206f313e0301f8dd7b6b589602"}, +] + [[package]] name = "setuptools" version = "70.3.0" @@ -3467,4 +3479,4 @@ ledger = ["ledgerwallet"] [metadata] lock-version = "2.1" python-versions = ">=3.9, <3.13" -content-hash = "9576f7e71e2313c1efd9c0eb8d48583c3c8f4003e39c650d2c36bcf47621bb55" +content-hash = "db9850601a790f8c95f75d85fc3dac235303c99dab41a36ff8d0bfcda1180156" diff --git a/pyproject.toml b/pyproject.toml index 22eee7cb0..5cca2eb71 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -23,6 +23,7 @@ dependencies = [ "eth-keyfile>=0.8.1,<1.0.0", "eth-keys==0.7.0", "websockets>=15.0.1,<16.0.0", + "semver>=3.0.0,<4.0.0", ] [project.optional-dependencies] diff --git a/starknet_py/contract.py b/starknet_py/contract.py index 39532f606..2802f35e0 100644 --- a/starknet_py/contract.py +++ b/starknet_py/contract.py @@ -7,6 +7,7 @@ from typing import Dict, List, Optional, Tuple, TypeVar, Union from marshmallow import ValidationError +from semver import Version from starknet_py.abi.v0 import Abi as AbiV0 from starknet_py.abi.v0 import AbiParser as AbiParserV0 @@ -23,6 +24,7 @@ from starknet_py.common import create_compiled_contract, create_sierra_compiled_contract from starknet_py.constants import DEFAULT_DEPLOYER_ADDRESS from starknet_py.contract_utils import _extract_compiled_class_hash, _unpack_provider +from starknet_py.hash.casm_class_hash import get_casm_hash_method_for_starknet_version from starknet_py.hash.selector import get_selector_from_name from starknet_py.net.account.base_account import BaseAccount from starknet_py.net.client import Client @@ -721,8 +723,12 @@ async def declare_v3( :return: DeclareResult instance. """ + block = await account.client.get_block() + starknet_version = Version.parse(block.starknet_version) + hash_method = get_casm_hash_method_for_starknet_version(starknet_version) + compiled_class_hash = _extract_compiled_class_hash( - compiled_contract_casm, compiled_class_hash + compiled_contract_casm, compiled_class_hash, hash_method=hash_method ) declare_tx = await account.sign_declare_v3( diff --git a/starknet_py/contract_utils.py b/starknet_py/contract_utils.py index 161d03991..e3bad7b37 100644 --- a/starknet_py/contract_utils.py +++ b/starknet_py/contract_utils.py @@ -2,6 +2,7 @@ from starknet_py.common import create_casm_class from starknet_py.hash.casm_class_hash import compute_casm_class_hash +from starknet_py.hash.hash_method import HashMethod from starknet_py.net.account.base_account import BaseAccount from starknet_py.net.client import Client @@ -9,6 +10,7 @@ def _extract_compiled_class_hash( compiled_contract_casm: Optional[str] = None, compiled_class_hash: Optional[int] = None, + hash_method: HashMethod = HashMethod.BLAKE2S, ) -> int: if compiled_class_hash is None and compiled_contract_casm is None: raise ValueError( @@ -19,7 +21,7 @@ def _extract_compiled_class_hash( if compiled_class_hash is None: assert compiled_contract_casm is not None compiled_class_hash = compute_casm_class_hash( - create_casm_class(compiled_contract_casm) + create_casm_class(compiled_contract_casm), hash_method=hash_method ) return compiled_class_hash diff --git a/starknet_py/hash/casm_class_hash.py b/starknet_py/hash/casm_class_hash.py index f9804c9a1..5c3180aaf 100644 --- a/starknet_py/hash/casm_class_hash.py +++ b/starknet_py/hash/casm_class_hash.py @@ -1,6 +1,6 @@ from typing import List, Optional, Sequence, Tuple -from poseidon_py.poseidon_hash import poseidon_hash_many +from semver import Version from starknet_py.cairo.felt import encode_shortstring from starknet_py.hash.compiled_class_hash_objects import ( @@ -10,13 +10,24 @@ BytecodeSegmentStructure, NestedIntList, ) +from starknet_py.hash.hash_method import HashMethod from starknet_py.net.client_models import CasmClassEntryPoint from starknet_py.net.executable_models import CasmClass CASM_CLASS_VERSION = "COMPILED_CLASS_V1" -def compute_casm_class_hash(casm_contract_class: CasmClass) -> int: +def get_casm_hash_method_for_starknet_version(starknet_version: Version) -> HashMethod: + # Starknet 0.14.1 and later use Blake2s + if starknet_version >= Version.parse("0.14.1"): + return HashMethod.BLAKE2S + + return HashMethod.POSEIDON + + +def compute_casm_class_hash( + casm_contract_class: CasmClass, hash_method: HashMethod = HashMethod.POSEIDON +) -> int: """ Calculate class hash of a CasmClass. """ @@ -24,14 +35,14 @@ def compute_casm_class_hash(casm_contract_class: CasmClass) -> int: _entry_points = casm_contract_class.entry_points_by_type - external_entry_points_hash = poseidon_hash_many( - _entry_points_array(_entry_points.external) + external_entry_points_hash = hash_method.hash_many( + _entry_points_array(_entry_points.external, hash_method) ) - l1_handler_entry_points_hash = poseidon_hash_many( - _entry_points_array(_entry_points.l1_handler) + l1_handler_entry_points_hash = hash_method.hash_many( + _entry_points_array(_entry_points.l1_handler, hash_method) ) - constructor_entry_points_hash = poseidon_hash_many( - _entry_points_array(_entry_points.constructor) + constructor_entry_points_hash = hash_method.hash_many( + _entry_points_array(_entry_points.constructor, hash_method) ) if casm_contract_class.bytecode_segment_lengths is not None: @@ -39,11 +50,11 @@ def compute_casm_class_hash(casm_contract_class: CasmClass) -> int: bytecode=casm_contract_class.bytecode, bytecode_segment_lengths=casm_contract_class.bytecode_segment_lengths, visited_pcs=None, - ).hash() + ).hash(hash_method) else: - bytecode_hash = poseidon_hash_many(casm_contract_class.bytecode) + bytecode_hash = hash_method.hash_many(casm_contract_class.bytecode) - return poseidon_hash_many( + return hash_method.hash_many( [ casm_class_version, external_entry_points_hash, @@ -54,12 +65,14 @@ def compute_casm_class_hash(casm_contract_class: CasmClass) -> int: ) -def _entry_points_array(entry_points: List[CasmClassEntryPoint]) -> List[int]: +def _entry_points_array( + entry_points: List[CasmClassEntryPoint], hash_method: HashMethod +) -> List[int]: entry_points_array = [] for entry_point in entry_points: assert entry_point.builtins is not None _encoded_builtins = [encode_shortstring(val) for val in entry_point.builtins] - builtins_hash = poseidon_hash_many(_encoded_builtins) + builtins_hash = hash_method.hash_many(_encoded_builtins) entry_points_array.extend( [entry_point.selector, entry_point.offset, builtins_hash] diff --git a/starknet_py/hash/compiled_class_hash_objects.py b/starknet_py/hash/compiled_class_hash_objects.py index 9472744b4..ae2a5ced1 100644 --- a/starknet_py/hash/compiled_class_hash_objects.py +++ b/starknet_py/hash/compiled_class_hash_objects.py @@ -4,9 +4,10 @@ import dataclasses import itertools from abc import ABC, abstractmethod -from typing import Any, List, Union +from typing import TYPE_CHECKING, Any, List, Union -from poseidon_py.poseidon_hash import poseidon_hash_many +if TYPE_CHECKING: + from starknet_py.hash.hash_method import HashMethod class BytecodeSegmentStructure(ABC): @@ -17,9 +18,11 @@ class BytecodeSegmentStructure(ABC): """ @abstractmethod - def hash(self) -> int: + def hash(self, hash_method: "HashMethod") -> int: """ Computes the hash of the node. + + :param hash_method: Hash method to use. """ def bytecode_with_skipped_segments(self): @@ -46,8 +49,8 @@ class BytecodeLeaf(BytecodeSegmentStructure): data: List[int] - def hash(self) -> int: - return poseidon_hash_many(self.data) + def hash(self, hash_method: "HashMethod") -> int: + return hash_method.hash_many(self.data) def add_bytecode_with_skipped_segments(self, data: List[int]): data.extend(self.data) @@ -62,14 +65,19 @@ class BytecodeSegmentedNode(BytecodeSegmentStructure): segments: List["BytecodeSegment"] - def hash(self) -> int: + def hash(self, hash_method: "HashMethod") -> int: return ( - poseidon_hash_many( - itertools.chain( # pyright: ignore - *[ - (node.segment_length, node.inner_structure.hash()) - for node in self.segments - ] + hash_method.hash_many( + list( + itertools.chain( + *[ + ( + node.segment_length, + node.inner_structure.hash(hash_method), + ) + for node in self.segments + ] + ) ) ) + 1 diff --git a/starknet_py/hash/hash_method.py b/starknet_py/hash/hash_method.py index dddced0f6..61d747c8c 100644 --- a/starknet_py/hash/hash_method.py +++ b/starknet_py/hash/hash_method.py @@ -3,6 +3,7 @@ from poseidon_py.poseidon_hash import poseidon_hash, poseidon_hash_many +from starknet_py.hash.blake2s import blake2s_hash_many from starknet_py.hash.utils import compute_hash_on_elements, pedersen_hash @@ -13,12 +14,15 @@ class HashMethod(Enum): PEDERSEN = "pedersen" POSEIDON = "poseidon" + BLAKE2S = "blake2s" def hash(self, left: int, right: int): if self == HashMethod.PEDERSEN: return pedersen_hash(left, right) if self == HashMethod.POSEIDON: return poseidon_hash(left, right) + if self == HashMethod.BLAKE2S: + return blake2s_hash_many([left, right]) raise ValueError(f"Unsupported hash method: {self}.") def hash_many(self, values: List[int]): @@ -26,4 +30,6 @@ def hash_many(self, values: List[int]): return compute_hash_on_elements(values) if self == HashMethod.POSEIDON: return poseidon_hash_many(values) + if self == HashMethod.BLAKE2S: + return blake2s_hash_many(values) raise ValueError(f"Unsupported hash method: {self}.") diff --git a/starknet_py/tests/e2e/mock/contracts_v1/src/test_contract.cairo b/starknet_py/tests/e2e/mock/contracts_v1/src/test_contract.cairo index e0543dabb..372387d30 100644 --- a/starknet_py/tests/e2e/mock/contracts_v1/src/test_contract.cairo +++ b/starknet_py/tests/e2e/mock/contracts_v1/src/test_contract.cairo @@ -32,6 +32,11 @@ mod TestContract { IAnotherContractDispatcher { contract_address: another_contract_address }.foo(a) } + #[external] + fn call_foo_2(another_contract_address: starknet::ContractAddress, a: u128) -> u128 { + IAnotherContractDispatcher { contract_address: another_contract_address }.foo(a) + } + #[external] fn libcall_foo(a: u128) -> u128 { IAnotherContractLibraryDispatcher { class_hash: starknet::class_hash_const::<0>() }.foo(a) diff --git a/starknet_py/tests/unit/hash/casm_class_hash_test.py b/starknet_py/tests/unit/hash/casm_class_hash_test.py index d1a039c67..794e245fa 100644 --- a/starknet_py/tests/unit/hash/casm_class_hash_test.py +++ b/starknet_py/tests/unit/hash/casm_class_hash_test.py @@ -2,7 +2,11 @@ import pytest from starknet_py.common import create_casm_class -from starknet_py.hash.casm_class_hash import compute_casm_class_hash +from starknet_py.hash.casm_class_hash import ( + compute_casm_class_hash, + get_casm_hash_method_for_starknet_version, +) +from starknet_py.hash.hash_method import HashMethod from starknet_py.tests.e2e.fixtures.constants import PRECOMPILED_CONTRACTS_DIR from starknet_py.tests.e2e.fixtures.misc import ( ContractVersion, @@ -12,7 +16,7 @@ @pytest.mark.parametrize( - "contract, expected_casm_class_hash", + "contract, expected_casm_class_hash_poseidon", [ ("Account", 0x778bce178afd1b39abd9729b80931e8c71661103b16de928c3187057254f601), ("ERC20", 0x3748ca8b6c53d65b5862e6f17850033baa117075e887708474aba110cc0e77a), @@ -21,17 +25,17 @@ ("TokenBridge", 0xf364f0d735b07f5a9a50a886e1f5bf6f0d82175d1955dc737f998d33990f8e), ], ) -def test_compute_casm_class_hash(contract, expected_casm_class_hash): +def test_compute_casm_class_hash_with_poseidon(contract, expected_casm_class_hash_poseidon): casm_contract_class_str = load_contract( contract, version=ContractVersion.V2 )['casm'] casm_class = create_casm_class(casm_contract_class_str) casm_class_hash = compute_casm_class_hash(casm_class) - assert casm_class_hash == expected_casm_class_hash + assert casm_class_hash == expected_casm_class_hash_poseidon @pytest.mark.parametrize( - "casm_contract_class_source, expected_casm_class_hash", + "casm_contract_class_source, expected_casm_class_hash_poseidon", [ ("minimal_contract_compiled_v2_1.casm", 0x186f6c4ca3af40dbcbf3f08f828ab0ee072938aaaedccc74ef3b9840cbd9fb3), @@ -40,11 +44,68 @@ def test_compute_casm_class_hash(contract, expected_casm_class_hash): ("starknet_contract_v2_6.casm", 0x603dd72504d8b0bc54df4f1102fdcf87fc3b2b94750a9083a5876913eec08e4), ], ) -def test_precompiled_compute_casm_class_hash(casm_contract_class_source, expected_casm_class_hash): +def test_precompiled_compute_casm_class_hash_with_poseidon(casm_contract_class_source, expected_casm_class_hash_poseidon): # pylint: disable=line-too-long casm_contract_class_str = read_contract( casm_contract_class_source, directory=PRECOMPILED_CONTRACTS_DIR ) casm_class = create_casm_class(casm_contract_class_str) casm_class_hash = compute_casm_class_hash(casm_class) - assert casm_class_hash == expected_casm_class_hash + assert casm_class_hash == expected_casm_class_hash_poseidon + + +@pytest.mark.parametrize( + "rpc_version, expected_hash_method", + [ + ("0.13.5", HashMethod.POSEIDON), + ("0.14.0", HashMethod.POSEIDON), + ("0.14.1", HashMethod.BLAKE2S), + ("0.15.0", HashMethod.BLAKE2S), + ("1.0.0", HashMethod.BLAKE2S), + ("1.10.0", HashMethod.BLAKE2S), + ], +) +def test_get_casm_hash_method_for_starknet_version(rpc_version, expected_hash_method): + """Test that the correct hash method is returned for different Starknet versions.""" + hash_method = get_casm_hash_method_for_starknet_version(rpc_version) + assert hash_method == expected_hash_method + + +@pytest.mark.parametrize( + "contract, expected_casm_class_hash_blake2s", + [ + ("Account", 0x714c833f7b359955f6a4a495ba995cca2114158db2178aff587f643daa19c80), + ("ERC20", 0x44312efaec9c719168eee3586314b01ed7a1fd7e31d3cf0c5a17e0a5b4fbe7d), + ("HelloStarknet", 0x5aaedd0566b5dd234f5f8d3d6b8cfd299cf0a99541aa9ca34db9259d546e82f), + ("TestContract", 0x3135acde04efbc96d422c01822a517ae5b4e61f132d26bf8542e3b9d0d1500f), + ("TokenBridge", 0x6409448fd244060b15748b02b6e0bdb185d5271be231492ca33a7147e43994c), + ], +) + +def test_compute_casm_class_hash_with_blake2s(contract, expected_casm_class_hash_blake2s): + casm_contract_class_str = load_contract( + contract, version=ContractVersion.V2 + )['casm'] + + casm_class = create_casm_class(casm_contract_class_str) + casm_class_hash = compute_casm_class_hash(casm_class, hash_method=HashMethod.BLAKE2S) + assert casm_class_hash == expected_casm_class_hash_blake2s + +@pytest.mark.parametrize( + "casm_contract_class_source, expected_casm_class_hash_blake2s", + [ + ("minimal_contract_compiled_v2_1.casm", + 0x195cfeec43b384e0f0ec83937149a1a4d88571772b2806ed7e4f41a1ecb4c74), + ("minimal_contract_compiled_v2_5_4.casm", + 0x5ac03c50c46fc7b374d4e11d15693ae0d21e13f61c1704700294d1f378980f7), + ("starknet_contract_v2_6.casm", 0xf8c27dd667e50ba127e5e0e469381606ffece27d8c5148548b6bbc4cacf717), + ], +) +def test_precompiled_compute_casm_class_hash_with_blake2s(casm_contract_class_source, expected_casm_class_hash_blake2s): + casm_contract_class_str = read_contract( + casm_contract_class_source, directory=PRECOMPILED_CONTRACTS_DIR + ) + + casm_class = create_casm_class(casm_contract_class_str) + casm_class_hash = compute_casm_class_hash(casm_class, hash_method=HashMethod.BLAKE2S) + assert casm_class_hash == expected_casm_class_hash_blake2s From b1eb73f6d53a138069f158b3b2090fe26eaee6ec Mon Sep 17 00:00:00 2001 From: Fiiranek Date: Thu, 20 Nov 2025 17:44:14 +0100 Subject: [PATCH 30/32] Add missing files --- starknet_py/hash/blake2s.py | 97 +++++++++++++++++++++ starknet_py/tests/unit/hash/blake2s_test.py | 52 +++++++++++ 2 files changed, 149 insertions(+) create mode 100644 starknet_py/hash/blake2s.py create mode 100644 starknet_py/tests/unit/hash/blake2s_test.py diff --git a/starknet_py/hash/blake2s.py b/starknet_py/hash/blake2s.py new file mode 100644 index 000000000..81b7cc2fe --- /dev/null +++ b/starknet_py/hash/blake2s.py @@ -0,0 +1,97 @@ +""" +This module's Blake2s felt encoding and hashing logic is based on StarkWare's +sequencer implementation: +https://github.com/starkware-libs/sequencer/blob/b29c0e8c61f7b2340209e256cf87dfe9f2c811aa/crates/blake2s/src/lib.rs +""" + +import hashlib +from typing import List + +from starknet_py.constants import FIELD_PRIME + +SMALL_THRESHOLD = 2**63 +BIG_MARKER = 1 << 31 # MSB mask for the first u32 in the 8-limb case + + +def encode_felts_to_u32s(felts: List[int]) -> List[int]: + """ + Encode each Felt into 32-bit words following Cairo's encoding scheme. + Small values (< 2^63) are encoded as 2 words: [high_32_bits, low_32_bits] from the last 8 bytes. + Large values (>= 2^63) are encoded as 8 words: the full 32-byte big-endian split, + with the MSB of the first word set as a marker (+2^255). + :param felts: List of Felt values to encode + :return: Flat list of u32 values + """ + unpacked_u32s = [] + for felt in felts: + # Convert felt to 32-byte big-endian representation + felt_as_be_bytes = felt.to_bytes(32, byteorder="big") + + if felt < SMALL_THRESHOLD: + # Small: 2 limbs only, high-32 then low-32 of the last 8 bytes + high = int.from_bytes(felt_as_be_bytes[24:28], byteorder="big") + low = int.from_bytes(felt_as_be_bytes[28:32], byteorder="big") + unpacked_u32s.append(high) + unpacked_u32s.append(low) + else: + # Big: 8 limbs, big-endian order + start = len(unpacked_u32s) + for i in range(0, 32, 4): + limb = int.from_bytes(felt_as_be_bytes[i : i + 4], byteorder="big") + unpacked_u32s.append(limb) + # Set the MSB of the very first limb as the Cairo hint does with "+ 2**255" + unpacked_u32s[start] |= BIG_MARKER + + return unpacked_u32s + + +def pack_256_le_to_felt(hash_bytes: bytes) -> int: + """ + Packs the first 32 bytes (256 bits) of hash_bytes into a Felt (252 bits). + Interprets the bytes as a Felt (252 bits) + :param hash_bytes: Hash bytes (at least 32 bytes required) + :return: Felt value (252-bit field element) + """ + assert len(hash_bytes) >= 32, "need at least 32 bytes to pack" + # Interpret the 32-byte buffer as a little-endian integer and convert to Felt + return int.from_bytes(hash_bytes[:32], byteorder="little") % FIELD_PRIME + + +def blake2s_to_felt(data: bytes) -> int: + """ + Compute Blake2s-256 hash over data and return as a Felt. + :param data: Input data to hash + :return: Blake2s-256 hash as a 252-bit field element + """ + hash_bytes = hashlib.blake2s(data, digest_size=32).digest() + return pack_256_le_to_felt(hash_bytes) + + +def encode_felt252_data_and_calc_blake_hash(felts: List[int]) -> int: + """ + Encodes Felt values using Cairo's encoding scheme and computes Blake2s hash. + This function matches Cairo's encode_felt252_to_u32s hint behavior. It encodes + each Felt into 32-bit words, serializes them as little-endian bytes, then + computes Blake2s-256 hash over the byte stream. + :param felts: List of Felt values to encode and hash + :return: Blake2s-256 hash as a 252-bit field element + """ + # Unpack each Felt into 2 or 8 u32 limbs + u32_words = encode_felts_to_u32s(felts) + + # Serialize the u32 limbs into a little-endian byte stream + byte_stream = b"".join(word.to_bytes(4, byteorder="little") for word in u32_words) + + # Compute Blake2s-256 over the bytes and pack the result into a Felt + return blake2s_to_felt(byte_stream) + + +def blake2s_hash_many(values: List[int]) -> int: + """ + Hash multiple Felt values using Cairo-compatible Blake2s encoding. + This is the recommended way to hash Felt values for Starknet when using + Blake2s as the hash method. + :param values: List of Felt values to hash + :return: Blake2s-256 hash as a 252-bit field element + """ + return encode_felt252_data_and_calc_blake_hash(values) diff --git a/starknet_py/tests/unit/hash/blake2s_test.py b/starknet_py/tests/unit/hash/blake2s_test.py new file mode 100644 index 000000000..c597293fc --- /dev/null +++ b/starknet_py/tests/unit/hash/blake2s_test.py @@ -0,0 +1,52 @@ +""" +The test values are taken from sequencer repository: +https://github.com/starkware-libs/sequencer/blob/b29c0e8c61f7b2340209e256cf87dfe9f2c811aa/crates/blake2s/tests/blake2s_tests.rs +""" + +import pytest + +from starknet_py.hash.blake2s import encode_felt252_data_and_calc_blake_hash + + +@pytest.mark.parametrize( + "input_felts, expected_result", + [ + # Empty array + ( + [], + 874258848688468311465623299960361657518391155660316941922502367727700287818, + ), + # Boundary: small felt at (2^63 - 1) + ( + [(1 << 63) - 1], + 94160078030592802631039216199460125121854007413180444742120780261703604445, + ), + # Boundary: at 2^63 + ( + [1 << 63], + 318549634615606806810268830802792194529205864650702991817600345489579978482, + ), + # Very large felt + ( + [0x800000000000011000000000000000000000000000000000000000000000000], + 3505594194634492896230805823524239179921427575619914728883524629460058657521, + ), + # Mixed: small and large felts + ( + [42, 1 << 63, 1337], + 1127477916086913892828040583976438888091205536601278656613505514972451246501, + ), + ], + ids=[ + "empty", + "boundary_small_felt", + "boundary_at_2_63", + "very_large_felt", + "mixed_small_large", + ], +) +def test_encode_felt252_data_and_calc_blake_hash(input_felts, expected_result): + result = encode_felt252_data_and_calc_blake_hash(input_felts) + assert ( + result == expected_result + ), f"StarknetPy implementation: {result} != Cairo implementation: {expected_result}" From 772ea1d1d42b08fb085677f91a3ec53971cc70dc Mon Sep 17 00:00:00 2001 From: Fiiranek Date: Thu, 20 Nov 2025 17:53:34 +0100 Subject: [PATCH 31/32] Little fixes --- .../tests/e2e/mock/contracts_v1/src/test_contract.cairo | 5 ----- starknet_py/tests/unit/hash/casm_class_hash_test.py | 8 +++++--- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/starknet_py/tests/e2e/mock/contracts_v1/src/test_contract.cairo b/starknet_py/tests/e2e/mock/contracts_v1/src/test_contract.cairo index 372387d30..e0543dabb 100644 --- a/starknet_py/tests/e2e/mock/contracts_v1/src/test_contract.cairo +++ b/starknet_py/tests/e2e/mock/contracts_v1/src/test_contract.cairo @@ -32,11 +32,6 @@ mod TestContract { IAnotherContractDispatcher { contract_address: another_contract_address }.foo(a) } - #[external] - fn call_foo_2(another_contract_address: starknet::ContractAddress, a: u128) -> u128 { - IAnotherContractDispatcher { contract_address: another_contract_address }.foo(a) - } - #[external] fn libcall_foo(a: u128) -> u128 { IAnotherContractLibraryDispatcher { class_hash: starknet::class_hash_const::<0>() }.foo(a) diff --git a/starknet_py/tests/unit/hash/casm_class_hash_test.py b/starknet_py/tests/unit/hash/casm_class_hash_test.py index 794e245fa..44a7e1dd9 100644 --- a/starknet_py/tests/unit/hash/casm_class_hash_test.py +++ b/starknet_py/tests/unit/hash/casm_class_hash_test.py @@ -1,5 +1,6 @@ # fmt: off import pytest +from semver import Version from starknet_py.common import create_casm_class from starknet_py.hash.casm_class_hash import ( @@ -55,7 +56,7 @@ def test_precompiled_compute_casm_class_hash_with_poseidon(casm_contract_class_s @pytest.mark.parametrize( - "rpc_version, expected_hash_method", + "starknet_version, expected_hash_method", [ ("0.13.5", HashMethod.POSEIDON), ("0.14.0", HashMethod.POSEIDON), @@ -65,9 +66,10 @@ def test_precompiled_compute_casm_class_hash_with_poseidon(casm_contract_class_s ("1.10.0", HashMethod.BLAKE2S), ], ) -def test_get_casm_hash_method_for_starknet_version(rpc_version, expected_hash_method): +def test_get_casm_hash_method_for_starknet_version(starknet_version, expected_hash_method): """Test that the correct hash method is returned for different Starknet versions.""" - hash_method = get_casm_hash_method_for_starknet_version(rpc_version) + starknet_version = Version.parse(starknet_version) + hash_method = get_casm_hash_method_for_starknet_version(starknet_version) assert hash_method == expected_hash_method From 765949adc2077dd29d54a8165ba091de6f2e5e46 Mon Sep 17 00:00:00 2001 From: Fiiranek Date: Thu, 20 Nov 2025 18:09:02 +0100 Subject: [PATCH 32/32] update migration guide --- docs/migration_guide.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/migration_guide.rst b/docs/migration_guide.rst index dcb379b86..9551336af 100644 --- a/docs/migration_guide.rst +++ b/docs/migration_guide.rst @@ -7,6 +7,15 @@ Migration guide 1. This version adds support for Blake hash, in order to allow compatibility for Starknet versions >= 0.14.1. +0.28.1 Breaking changes +----------------------- + +.. py:currentmodule:: starknet_py.hash.compiled_class_hash_objects + +1. :meth:`BytecodeSegmentStructure.hash` has new param ``hash_method``. +2. :meth:`BytecodeLeaf.hash` has new param ``hash_method``. +3. :meth:`BytecodeSegmentedNode.hash` has new param ``hash_method``. + *************************** 0.28.0 Migration guide ***************************