diff --git a/.circleci/config.yml b/.circleci/config.yml index cbf7399..6477279 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -71,17 +71,43 @@ jobs: - run: name: run fast tests command: | - make lint - make test + make ci - upload-results + deploy: + executor: bitcartcc/docker-python + docker: + - image: cimg/python:3.8 + steps: + - checkout + + - run: + name: setup credentials + command: | + echo -e "[pypi]" >> ~/.pypirc + echo -e "username = $PYPI_USER" >> ~/.pypirc + echo -e "password = $PYPI_PASS" >> ~/.pypirc + - run: + name: create env, build dist and upload + command: | + virtualenv ~/venv + . ~/venv/bin/activate + pip install -U wheel twine + python setup.py sdist + python setup.py bdist_wheel + twine upload dist/* + workflows: version: 2 test_and_deploy: jobs: + - bitcartcc/lint: + name: lint - test: name: test-<< matrix.v >> + requires: + - lint matrix: parameters: v: @@ -90,3 +116,10 @@ workflows: - "3.9" - "3.10" - "3.11" + - deploy: + context: global + filters: + tags: + only: /[0-9]+(\.[0-9]+)*/ + branches: + ignore: /.*/ diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5abc5b2 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,20 @@ +# editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[Makefile] +indent_style = tab + +[*.{yml,yaml}] +indent_size = 2 + +[*.md] +indent_size = 2 +trim_trailing_whitespace = false diff --git a/.flake8 b/.flake8 index 92e5b76..caa88c8 100644 --- a/.flake8 +++ b/.flake8 @@ -1,4 +1,4 @@ [flake8] max-complexity=11 max-line-length=127 -ignore=E266, E203, W503 \ No newline at end of file +ignore=E266, E203, W503 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..75207f7 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,48 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: check-merge-conflict + - repo: https://github.com/asottile/yesqa + rev: v1.4.0 + hooks: + - id: yesqa + - repo: https://github.com/psf/black + rev: 23.1.0 + hooks: + - id: black + - repo: https://github.com/pycqa/isort + rev: 5.12.0 + hooks: + - id: isort + - repo: https://github.com/pycqa/flake8 + rev: 6.0.0 + hooks: + - id: flake8 + - repo: https://github.com/asottile/pyupgrade + rev: v3.3.1 + hooks: + - id: pyupgrade + args: ["--py37-plus"] + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: end-of-file-fixer + - id: trailing-whitespace + - id: mixed-line-ending + - id: requirements-txt-fixer + - id: check-case-conflict + - id: check-shebang-scripts-are-executable + - id: check-json + - id: check-toml + - id: check-yaml + - id: check-symlinks + - id: debug-statements + - id: fix-byte-order-marker + - id: fix-encoding-pragma + args: ["--remove"] + - id: detect-private-key + - repo: https://github.com/pre-commit/mirrors-prettier + rev: v3.0.0-alpha.4 + hooks: + - id: prettier diff --git a/Makefile b/Makefile index 072f04f..7482a51 100644 --- a/Makefile +++ b/Makefile @@ -15,4 +15,4 @@ format: test: pytest tests/ ${TEST_ARGS} -ci: checkformat lint test \ No newline at end of file +ci: checkformat lint test diff --git a/README.md b/README.md index 6937f16..26df8bb 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,12 @@ # tronpy [![PyPI version](https://badge.fury.io/py/tronpy.svg)](https://pypi.org/project/tronpy/) +[![CircleCI](https://dl.circleci.com/status-badge/img/gh/andelf/tronpy/tree/master.svg?style=svg)](https://dl.circleci.com/status-badge/redirect/gh/andelf/tronpy/tree/master) TRON Python Client Library. [Documentation](https://tronpy.readthedocs.io/en/latest/index.html) +> Note: in case you want to use cryptocurrency functions in an universal way or e.g. reliably calculate transaction fees for BTC, ETH, Tron and many others, check out the [BitcartCC project](https://bitcartcc.com) + ## How to use ```python diff --git a/docs/conf.py b/docs/conf.py index 378d1b2..e657104 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -17,9 +17,9 @@ # -- Project information ----------------------------------------------------- -project = 'TronPy' -copyright = '2020, Andelf' -author = 'Andelf' +project = "TronPy" +copyright = "2020, Andelf" +author = "Andelf" # -- General configuration --------------------------------------------------- @@ -27,15 +27,15 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ['sphinx.ext.autodoc'] +extensions = ["sphinx.ext.autodoc"] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # -- Options for HTML output ------------------------------------------------- @@ -43,11 +43,11 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'classic' # 'alabaster' +html_theme = "classic" # 'alabaster' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] -master_doc = 'index' +master_doc = "index" diff --git a/docs/keys.rst b/docs/keys.rst index 7835315..2c53123 100644 --- a/docs/keys.rst +++ b/docs/keys.rst @@ -23,4 +23,4 @@ Key API reference .. automethod:: hex() .. autoclass:: tronpy.keys.Signature - :members: \ No newline at end of file + :members: diff --git a/examples/check_balance.py b/examples/check_balance.py index 1525aee..d251fc9 100644 --- a/examples/check_balance.py +++ b/examples/check_balance.py @@ -1,6 +1,7 @@ +from pprint import pprint + from tronpy import Tron from tronpy.exceptions import AddressNotFound -from pprint import pprint client = Tron() @@ -10,7 +11,7 @@ def check_balance(address): balance = client.get_account_balance(address) return balance except AddressNotFound: - return 'Adress not found..!' + return "Adress not found..!" -pprint(check_balance('
')) +pprint(check_balance("
")) diff --git a/examples/generate_address.py b/examples/generate_address.py index 049bf4f..0e7fcd9 100644 --- a/examples/generate_address.py +++ b/examples/generate_address.py @@ -1,6 +1,6 @@ -from tronpy import Tron from pprint import pprint +from tronpy import Tron client = Tron() pprint(client.generate_address()) diff --git a/examples/justswap.py b/examples/justswap.py index 17060bf..26465c6 100644 --- a/examples/justswap.py +++ b/examples/justswap.py @@ -1,16 +1,13 @@ - - import json import os import time -from tronpy import Tron -from tronpy import keys +from tronpy import Tron, keys __dir__ = os.path.dirname(__file__) -dzi_trade = 'TSMssi9ojNkzj5fT5bAjzuGjrLmsKau8Xj' -from_addr = 'TVrSWkL6a9xvtxRKq5RHg2HjUpGdPN3wBa' +dzi_trade = "TSMssi9ojNkzj5fT5bAjzuGjrLmsKau8Xj" +from_addr = "TVrSWkL6a9xvtxRKq5RHg2HjUpGdPN3wBa" priv_key = keys.PrivateKey.fromhex("975a98.....(omitted)..........86b98d97b") @@ -23,7 +20,7 @@ def timestamp(): swap_abi = json.load(fp) -client = Tron(network='nile') +client = Tron(network="nile") cntr = client.get_contract(dzi_trade) cntr.abi = swap_abi diff --git a/pyproject.toml b/pyproject.toml index e49a3e5..922ff48 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "tronpy" -version = "0.2.6" +version = "0.3.0" description = "TRON Python client library" authors = ["andelf "] license = "MIT" @@ -49,28 +49,33 @@ filterwarnings = [ ] [tool.black] -line-length = 100 -target-version = ['py36'] -include = '\.pyi?$' -exclude = ''' -( - /( - \.eggs # exclude a few common directories in the - | \.git # root of the project - | \.hg - | \.mypy_cache - | \.tox - | \.venv - | _build - | buck-out - | build - | dist - )/ - | foo.py # also separately exclude a file named foo.py in - # the root of the project -) -''' +line-length = 127 + +[tool.isort] +profile = "black" +line_length = 127 + +[tool.mypy] +warn_redundant_casts = true +warn_unused_ignores = true +disallow_untyped_calls = true +disallow_untyped_defs = true +check_untyped_defs = true +warn_return_any = true +no_implicit_optional = true +strict_optional = true +ignore_missing_imports = true + + +[tool.coverage.run] +omit = [ + "*__init__.py", + "tests/*", + "venv/*", + "env/*", + "setup.py", +] [build-system] requires = ["poetry-core"] -build-backend = "poetry.core.masonry.api" \ No newline at end of file +build-backend = "poetry.core.masonry.api" diff --git a/setup.py b/setup.py index 3987659..2b4dce9 100644 --- a/setup.py +++ b/setup.py @@ -1,34 +1,33 @@ -# -*- coding: utf-8 -*- from setuptools import setup -packages = ['tronpy', 'tronpy.keys', 'tronpy.providers'] +packages = ["tronpy", "tronpy.keys", "tronpy.providers"] -package_data = {'': ['*']} +package_data = {"": ["*"]} install_requires = [ - 'base58', - 'coincurve', - 'eth_abi>=4.0.0a,<5.0.0', - 'httpx', - 'pycryptodome<4', - 'requests', + "base58", + "coincurve", + "eth_abi>=4.0.0a,<5.0.0", + "httpx", + "pycryptodome<4", + "requests", ] setup_kwargs = { - 'name': 'tronpy', - 'version': '0.2.6', - 'description': 'TRON Python client library', - 'long_description': open("README.md").read(), - 'long_description_content_type': "text/markdown", - 'author': 'andelf', - 'author_email': 'andelf@gmail.com', - 'maintainer': None, - 'maintainer_email': None, - 'url': 'https://github.com/andelf/tronpy', - 'packages': packages, - 'package_data': package_data, - 'install_requires': install_requires, - 'python_requires': '>=3.6,<4.0', + "name": "tronpy", + "version": "0.3.0", + "description": "TRON Python client library", + "long_description": open("README.md").read(), + "long_description_content_type": "text/markdown", + "author": "andelf", + "author_email": "andelf@gmail.com", + "maintainer": None, + "maintainer_email": None, + "url": "https://github.com/andelf/tronpy", + "packages": packages, + "package_data": package_data, + "install_requires": install_requires, + "python_requires": ">=3.7", } diff --git a/tests/test_abi.py b/tests/test_abi.py index 3200068..b23d6ec 100644 --- a/tests/test_abi.py +++ b/tests/test_abi.py @@ -4,20 +4,20 @@ def test_abi_encode(): assert ( trx_abi.encode_single("address", "TLfuw4tRywtxCusvTudbjf7PbcXjfe7qrw").hex() - == '0000000000000000000000007564105e977516c53be337314c7e53838967bdac' + == "0000000000000000000000007564105e977516c53be337314c7e53838967bdac" ) assert trx_abi.encode_single("(address,uint256)", ["TLfuw4tRywtxCusvTudbjf7PbcXjfe7qrw", 100_000_000]).hex() == ( - '0000000000000000000000007564105e977516c53be337314c7e53838967bdac' - + '0000000000000000000000000000000000000000000000000000000005f5e100' + "0000000000000000000000007564105e977516c53be337314c7e53838967bdac" + + "0000000000000000000000000000000000000000000000000000000005f5e100" ) def test_abi_decode(): assert trx_abi.decode_abi( - ['address', 'uint256'], + ["address", "uint256"], bytes.fromhex( - '0000000000000000000000007564105e977516c53be337314c7e53838967bdac' - + '0000000000000000000000000000000000000000000000000000000005f5e100' + "0000000000000000000000007564105e977516c53be337314c7e53838967bdac" + + "0000000000000000000000000000000000000000000000000000000005f5e100" ), - ) == ('TLfuw4tRywtxCusvTudbjf7PbcXjfe7qrw', 100000000) + ) == ("TLfuw4tRywtxCusvTudbjf7PbcXjfe7qrw", 100000000) diff --git a/tests/test_client.py b/tests/test_client.py index f317168..67feb64 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -11,9 +11,7 @@ # test_net address FROM_ADDR = "TBDCyrZ1hT1PDDFf2yRABwPrFica5qqPUX" # test_net private key -FROM_PRIV_KEY = PrivateKey( - bytes.fromhex("fd605fb953fcdabb952be161265a75b8a3ce1c0def2c7db72265f9db9a471be4") -) +FROM_PRIV_KEY = PrivateKey(bytes.fromhex("fd605fb953fcdabb952be161265a75b8a3ce1c0def2c7db72265f9db9a471be4")) # test_net address TO_ADDR = "TFVfhkyJAULWQbHMgVfgbkmgeGBkHo5zru" CNR_ADDR = "THi2qJf6XmvTJSpZHc17HgQsmJop6kb3ia" @@ -89,20 +87,11 @@ def test_client_sign_offline(): @pytest.mark.asyncio async def test_async_client_sign_offline(): async with AsyncTron(network="nile") as client: - tx = ( - await client.trx.transfer(FROM_ADDR, TO_ADDR, 1) - .memo("test memo") - .fee_limit(100_000_000) - .build() - ) + tx = await client.trx.transfer(FROM_ADDR, TO_ADDR, 1).memo("test memo").fee_limit(100_000_000).build() tx_j = tx.to_json() - check_transaction_structure( - tx_j, TRANSFER_EXPECTED_RESP, 100_000_000, expect_signature=False - ) + check_transaction_structure(tx_j, TRANSFER_EXPECTED_RESP, 100_000_000, expect_signature=False) # offline - tx_offline = await AsyncTransaction.from_json( - tx_j - ) # tx_offline._client is None so it's offline + tx_offline = await AsyncTransaction.from_json(tx_j) # tx_offline._client is None so it's offline tx_offline.sign(FROM_PRIV_KEY) tx_j2 = tx_offline.to_json() check_transaction_structure(tx_j2, TRANSFER_EXPECTED_RESP, 100_000_000) @@ -113,9 +102,7 @@ async def test_async_client_sign_offline(): def test_client_update_tx(): client = Tron(network="nile") - tx: Transaction = ( - client.trx.transfer(FROM_ADDR, TO_ADDR, 1).memo("test memo").fee_limit(100_000_000).build() - ) + tx: Transaction = client.trx.transfer(FROM_ADDR, TO_ADDR, 1).memo("test memo").fee_limit(100_000_000).build() tx.sign(FROM_PRIV_KEY) tx_id = tx.txid # update and transfer again @@ -128,12 +115,9 @@ def test_client_update_tx(): @pytest.mark.asyncio async def test_async_client(): async with AsyncTron(network="nile") as client: - tx = ( - await client.trx.transfer(FROM_ADDR, TO_ADDR, 1) - .memo("test memo") - .fee_limit(100_000_000) - .build() - ).sign(FROM_PRIV_KEY) + tx = (await client.trx.transfer(FROM_ADDR, TO_ADDR, 1).memo("test memo").fee_limit(100_000_000).build()).sign( + FROM_PRIV_KEY + ) check_transaction_structure(tx.to_json(), TRANSFER_EXPECTED_RESP, 100_000_000) @@ -151,12 +135,9 @@ async def test_async_manual_client(): provider = AsyncHTTPProvider(CONF_NILE, client=_http_client) client = AsyncTron(provider=provider) - tx = ( - await client.trx.transfer(FROM_ADDR, TO_ADDR, 1) - .memo("test memo") - .fee_limit(100_000_000) - .build() - ).sign(FROM_PRIV_KEY) + tx = (await client.trx.transfer(FROM_ADDR, TO_ADDR, 1).memo("test memo").fee_limit(100_000_000).build()).sign( + FROM_PRIV_KEY + ) check_transaction_structure(tx.to_json(), TRANSFER_EXPECTED_RESP, 100_000_000) # must call .close at end to release connections diff --git a/tests/test_contract.py b/tests/test_contract.py index c5fb697..2c005ef 100644 --- a/tests/test_contract.py +++ b/tests/test_contract.py @@ -7,9 +7,7 @@ # test_net address FROM_ADDR = "TBDCyrZ1hT1PDDFf2yRABwPrFica5qqPUX" # test_net private key -FROM_PRIV_KEY = PrivateKey( - bytes.fromhex("fd605fb953fcdabb952be161265a75b8a3ce1c0def2c7db72265f9db9a471be4") -) +FROM_PRIV_KEY = PrivateKey(bytes.fromhex("fd605fb953fcdabb952be161265a75b8a3ce1c0def2c7db72265f9db9a471be4")) # test_net address TO_ADDR = "TFVfhkyJAULWQbHMgVfgbkmgeGBkHo5zru" CNR_ADDR = "THi2qJf6XmvTJSpZHc17HgQsmJop6kb3ia" @@ -50,9 +48,7 @@ { "inputs": [], "name": "get", - "outputs": [ - {"internalType": "uint256", "name": "retVal", "type": "uint256"} - ], + "outputs": [{"internalType": "uint256", "name": "retVal", "type": "uint256"}], "stateMutability": "view", "type": "function", } @@ -95,13 +91,7 @@ async def test_async_const_functions(): def test_trc20_transfer(): client = Tron(network="nile") contract = client.get_contract(CNR_ADDR) - tx = ( - contract.functions.transfer(TO_ADDR, 1_000) - .with_owner(FROM_ADDR) - .fee_limit(5_000_000) - .build() - .sign(FROM_PRIV_KEY) - ) + tx = contract.functions.transfer(TO_ADDR, 1_000).with_owner(FROM_ADDR).fee_limit(5_000_000).build().sign(FROM_PRIV_KEY) check_transaction_structure(tx.to_json(), TRC20_EXPECTED_RESP, 5_000_000, expect_memo=False) @@ -110,10 +100,7 @@ async def test_async_trc20_transfer(): async with AsyncTron(network="nile") as client: contract = await client.get_contract(CNR_ADDR) tx = ( - await (await contract.functions.transfer(TO_ADDR, 1_000)) - .with_owner(FROM_ADDR) - .fee_limit(5_000_000) - .build() + await (await contract.functions.transfer(TO_ADDR, 1_000)).with_owner(FROM_ADDR).fee_limit(5_000_000).build() ).sign(FROM_PRIV_KEY) check_transaction_structure(tx.to_json(), TRC20_EXPECTED_RESP, 5_000_000, expect_memo=False) @@ -131,12 +118,8 @@ def test_contract_create(): } ] cntr = Contract(name="SimpleStore", bytecode=BYTECODE, abi=abi) - tx = ( - client.trx.deploy_contract(FROM_ADDR, cntr).fee_limit(5_000_000).build().sign(FROM_PRIV_KEY) - ) - check_transaction_structure( - tx.to_json(), CREATE_CONTRACT_EXPECTED_RESP, 5_000_000, expect_memo=False - ) + tx = client.trx.deploy_contract(FROM_ADDR, cntr).fee_limit(5_000_000).build().sign(FROM_PRIV_KEY) + check_transaction_structure(tx.to_json(), CREATE_CONTRACT_EXPECTED_RESP, 5_000_000, expect_memo=False) @pytest.mark.asyncio @@ -152,9 +135,5 @@ async def test_async_contract_create(): } ] cntr = AsyncContract(name="SimpleStore", bytecode=BYTECODE, abi=abi) - tx = (await client.trx.deploy_contract(FROM_ADDR, cntr).fee_limit(5_000_000).build()).sign( - FROM_PRIV_KEY - ) - check_transaction_structure( - tx.to_json(), CREATE_CONTRACT_EXPECTED_RESP, 5_000_000, expect_memo=False - ) + tx = (await client.trx.deploy_contract(FROM_ADDR, cntr).fee_limit(5_000_000).build()).sign(FROM_PRIV_KEY) + check_transaction_structure(tx.to_json(), CREATE_CONTRACT_EXPECTED_RESP, 5_000_000, expect_memo=False) diff --git a/tests/test_keys.py b/tests/test_keys.py index 346f64a..787584c 100644 --- a/tests/test_keys.py +++ b/tests/test_keys.py @@ -44,9 +44,7 @@ def address(): def test_private_key(): with pytest.raises(BadKey): - PrivateKey( - bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000000") - ) + PrivateKey(bytes.fromhex("0000000000000000000000000000000000000000000000000000000000000000")) def test_public_key(): @@ -55,9 +53,7 @@ def test_public_key(): def test_key_convert(): - priv_key = PrivateKey.fromhex( - "0000000000000000000000000000000000000000000000000000000000000001" - ) + priv_key = PrivateKey.fromhex("0000000000000000000000000000000000000000000000000000000000000001") assert priv_key.hex() == "0000000000000000000000000000000000000000000000000000000000000001" @@ -75,9 +71,7 @@ def test_signature_verify(signature, txid, raw_data, pub_key): def test_signature_sign(signature: Signature, raw_data: bytes, txid: bytes): - priv_key = PrivateKey.fromhex( - "0000000000000000000000000000000000000000000000000000000000000001" - ) + priv_key = PrivateKey.fromhex("0000000000000000000000000000000000000000000000000000000000000001") sig = priv_key.sign_msg(raw_data) pub_key = sig.recover_public_key_from_msg(raw_data) @@ -89,9 +83,7 @@ def test_signature_sign(signature: Signature, raw_data: bytes, txid: bytes): def test_key_derivation(): - priv_key = PrivateKey.fromhex( - "fd605fb953fcdabb952be161265a75b8a3ce1c0def2c7db72265f9db9a471be4" - ) + priv_key = PrivateKey.fromhex("fd605fb953fcdabb952be161265a75b8a3ce1c0def2c7db72265f9db9a471be4") assert priv_key.hex() == "fd605fb953fcdabb952be161265a75b8a3ce1c0def2c7db72265f9db9a471be4" public_key = priv_key.public_key assert ( diff --git a/tests/test_query.py b/tests/test_query.py index 47cb421..347ceb5 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -33,9 +33,7 @@ async def test_async_query_account(): def test_query_event_logs(): client = Tron(network="nile") - txi = client.get_transaction_info( - "927c27150f70f0d5762486e3edd626775fe1edab1069ff2182d133807c37f705" - ) + txi = client.get_transaction_info("927c27150f70f0d5762486e3edd626775fe1edab1069ff2182d133807c37f705") cnr = client.get_contract("TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf") events = list(cnr.events.Transfer.process_receipt(txi)) assert events @@ -51,9 +49,7 @@ def test_query_event_logs(): @pytest.mark.asyncio async def test_async_query_event_logs(): async with AsyncTron(network="nile") as client: - txi = await client.get_transaction_info( - "927c27150f70f0d5762486e3edd626775fe1edab1069ff2182d133807c37f705" - ) + txi = await client.get_transaction_info("927c27150f70f0d5762486e3edd626775fe1edab1069ff2182d133807c37f705") cnr = await client.get_contract("TXYZopYRdj2D9XRtbG411XZZ3kM5VkAeBf") events = list(cnr.events.Transfer.process_receipt(txi)) assert events diff --git a/tests/utils.py b/tests/utils.py index 2fb70ad..f6d396a 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -8,14 +8,10 @@ } -def check_transaction_structure( - tx, expected, fee_limit, *, expect_signature=True, expect_memo=True -): +def check_transaction_structure(tx, expected, fee_limit, *, expect_signature=True, expect_memo=True): assert set(tx.keys()) == {"txID", "raw_data", "signature", "permission"} assert tx["permission"] is None - assert set(tx["raw_data"].keys()) == ( - RAW_DATA_KEYS | {"data"} if expect_memo else RAW_DATA_KEYS - ) + assert set(tx["raw_data"].keys()) == (RAW_DATA_KEYS | {"data"} if expect_memo else RAW_DATA_KEYS) if fee_limit is not None: assert tx["raw_data"]["fee_limit"] == fee_limit assert len(tx["raw_data"]["contract"]) == 1 diff --git a/tronpy/__init__.py b/tronpy/__init__.py index eb4db9b..a579f00 100644 --- a/tronpy/__init__.py +++ b/tronpy/__init__.py @@ -1,7 +1,7 @@ -from tronpy.tron import Tron +from tronpy.async_contract import AsyncContract from tronpy.async_tron import AsyncTron from tronpy.contract import Contract, ShieldedTRC20 -from tronpy.async_contract import AsyncContract +from tronpy.tron import Tron TRX = 1_000_000 SUN = 1 diff --git a/tronpy/abi.py b/tronpy/abi.py index 6a3a670..6056376 100644 --- a/tronpy/abi.py +++ b/tronpy/abi.py @@ -1,14 +1,13 @@ +import eth_abi +from eth_abi.base import parse_type_str +from eth_abi.codec import ABICodec as ETHABICodec from eth_abi.decoding import Fixed32ByteSizeDecoder from eth_abi.encoding import Fixed32ByteSizeEncoder from eth_abi.exceptions import NonEmptyPaddingBytes from eth_abi.registry import BaseEquals -from eth_abi.base import parse_type_str -from eth_abi.codec import ABICodec as ETHABICodec -import eth_abi from eth_abi.registry import registry as default_registry - -from tronpy.keys import to_base58check_address, is_address, to_tvm_address +from tronpy.keys import is_address, to_base58check_address, to_tvm_address class TronAddressDecoder(Fixed32ByteSizeDecoder): @@ -24,10 +23,8 @@ def validate_padding_bytes(self, value, padding_bytes): value_byte_size = self._get_value_byte_size() padding_size = self.data_byte_size - value_byte_size - if padding_bytes != b'\x00' * padding_size and padding_bytes != b'\x00' * (padding_size - 2) + b'\x00A': - raise NonEmptyPaddingBytes( - "Padding bytes were not empty: {0}".format(repr(padding_bytes)) - ) + if padding_bytes != b"\x00" * padding_size and padding_bytes != b"\x00" * (padding_size - 2) + b"\x00A": + raise NonEmptyPaddingBytes(f"Padding bytes were not empty: {repr(padding_bytes)}") class TronAddressEncoder(Fixed32ByteSizeEncoder): @@ -55,14 +52,17 @@ def do_patching(registry): registry.unregister("address") registry.register( - BaseEquals("address"), TronAddressEncoder, TronAddressDecoder, label="address", + BaseEquals("address"), + TronAddressEncoder, + TronAddressDecoder, + label="address", ) registry.register( - BaseEquals('trcToken'), + BaseEquals("trcToken"), eth_abi.encoding.UnsignedIntegerEncoder, eth_abi.decoding.UnsignedIntegerDecoder, - label='trcToken', + label="trcToken", ) diff --git a/tronpy/async_contract.py b/tronpy/async_contract.py index 723de80..bc16023 100644 --- a/tronpy/async_contract.py +++ b/tronpy/async_contract.py @@ -1,13 +1,14 @@ -from typing import Union, Tuple +from typing import Tuple, Union import tronpy from tronpy import keys +from tronpy.contract import Contract, ContractFunctions, ContractMethod, ShieldedTRC20 from tronpy.exceptions import DoubleSpending -from tronpy.contract import Contract, ContractMethod, ContractFunctions, ShieldedTRC20 class AsyncContract(Contract): """A smart contract object.""" + @property def functions(self) -> "ContractFunctions": """The :class:`~ContractFunctions` object, wraps all contract methods.""" @@ -28,7 +29,7 @@ def __getitem__(self, method: str): if method_abi.get("type", "").lower() == "function" and method_abi["name"] == method: return AsyncContractMethod(method_abi, self._contract) - raise KeyError("contract has no method named '{}'".format(method)) + raise KeyError(f"contract has no method named '{method}'") # noinspection PyProtectedMember @@ -46,7 +47,10 @@ async def _async_trigger_contract(self, parameter): if self._abi.get("stateMutability", None).lower() in ["view", "pure"]: # const call, contract ret ret = await self._client.trigger_const_smart_contract_function( - self._owner_address, self._contract.contract_address, self.function_signature, parameter, + self._owner_address, + self._contract.contract_address, + self.function_signature, + parameter, ) return self.parse_output(ret) @@ -69,6 +73,7 @@ async def _async_trigger_contract(self, parameter): # noinspection PyProtectedMember class AsyncShieldedTRC20(ShieldedTRC20): """Async Shielded TRC20 Wrapper.""" + @property async def trc20(self) -> AsyncContract: """The corresponding TRC20 contract.""" @@ -87,9 +92,7 @@ async def scale_factor(self) -> int: async def get_rcm(self) -> str: return (await self._client.provider.make_request("wallet/getrcm"))["value"] - async def mint( - self, taddr: str, zaddr: str, amount: int, memo: str = "" - ) -> "tronpy.async_tron.AsyncTransactionBuilder": + async def mint(self, taddr: str, zaddr: str, amount: int, memo: str = "") -> "tronpy.async_tron.AsyncTransactionBuilder": """Mint, transfer from T-address to z-address.""" rcm = await self.get_rcm() payload = { @@ -120,7 +123,10 @@ async def mint( ) async def transfer( - self, zkey: dict, notes: Union[list, dict], *to: Union[Tuple[str, int], Tuple[str, int, str]], + self, + zkey: dict, + notes: Union[list, dict], + *to: Union[Tuple[str, int], Tuple[str, int, str]], ) -> "tronpy.async_tron.AsyncTransactionBuilder": """Transfer from z-address to z-address.""" if isinstance(notes, (dict,)): @@ -135,9 +141,7 @@ async def transfer( raise DoubleSpending alpha = await self.get_rcm() root, path = self.get_path(note.get("position", 0)) - spends.append( - {"note": note["note"], "alpha": alpha, "root": root, "path": path, "pos": note.get("position", 0)} - ) + spends.append({"note": note["note"], "alpha": alpha, "root": root, "path": path, "pos": note.get("position", 0)}) spend_amount += note["note"]["value"] receives = [] @@ -153,9 +157,7 @@ async def transfer( rcm = await self.get_rcm() - receives.append( - {"note": {"value": amount, "payment_address": addr, "rcm": rcm, "memo": memo.encode().hex()}} - ) + receives.append({"note": {"value": amount, "payment_address": addr, "rcm": rcm, "memo": memo.encode().hex()}}) if spend_amount != receive_amount: raise ValueError("spend amount is not equal to receive amount") @@ -191,16 +193,14 @@ async def burn( root, path = self.get_path(note.get("position", 0)) if note.get("is_spent", False): raise DoubleSpending - spends.append( - {"note": note["note"], "alpha": alpha, "root": root, "path": path, "pos": note.get("position", 0)} - ) + spends.append({"note": note["note"], "alpha": alpha, "root": root, "path": path, "pos": note.get("position", 0)}) change_amount = 0 receives = [] to_addr = None to_amount = 0 - to_memo = '' + to_memo = "" if not to: - raise ValueError('burn must have a output') + raise ValueError("burn must have a output") for receive in to: addr = receive[0] amount = receive[1] @@ -209,12 +209,10 @@ async def burn( else: memo = "" - if addr.startswith('ztron1'): + if addr.startswith("ztron1"): change_amount += amount rcm = await self.get_rcm() - receives = [ - {"note": {"value": amount, "payment_address": addr, "rcm": rcm, "memo": memo.encode().hex()}} - ] + receives = [{"note": {"value": amount, "payment_address": addr, "rcm": rcm, "memo": memo.encode().hex()}}] else: # assume T-address to_addr = addr @@ -301,4 +299,4 @@ async def is_note_spent(self, zkey: dict, note: dict) -> bool: ret = await self._client.provider.make_request("wallet/isshieldedtrc20contractnotespent", payload) - return ret.get('is_spent', None) + return ret.get("is_spent", None) diff --git a/tronpy/async_tron.py b/tronpy/async_tron.py index 27a3165..c9f3851 100644 --- a/tronpy/async_tron.py +++ b/tronpy/async_tron.py @@ -1,38 +1,38 @@ -import time -import json import asyncio -from pprint import pprint +import json +import time from decimal import Decimal -from typing import Union, Tuple, Optional +from pprint import pprint +from typing import Optional, Tuple, Union from tronpy import keys -from tronpy.async_contract import AsyncContract, ShieldedTRC20, AsyncContractMethod -from tronpy.keys import PrivateKey from tronpy.abi import tron_abi +from tronpy.async_contract import AsyncContract, AsyncContractMethod, ShieldedTRC20 from tronpy.defaults import conf_for_name -from tronpy.providers.async_http import AsyncHTTPProvider from tronpy.exceptions import ( - BadSignature, - BadKey, + AddressNotFound, + ApiError, + AssetNotFound, BadHash, + BadKey, + BadSignature, BlockNotFound, - AssetNotFound, + BugInJavaTron, TaposError, - UnknownError, TransactionError, - ValidationError, - ApiError, - AddressNotFound, TransactionNotFound, TvmError, - BugInJavaTron, + UnknownError, + ValidationError, ) +from tronpy.keys import PrivateKey +from tronpy.providers.async_http import AsyncHTTPProvider TAddress = str DEFAULT_CONF = { - 'fee_limit': 10_000_000, - 'timeout': 10.0, # in second + "fee_limit": 10_000_000, + "timeout": 10.0, # in second } @@ -83,36 +83,38 @@ async def result(self, timeout=30, interval=1.6, solid=False) -> dict: receipt = await self.wait(timeout, interval, solid) - if receipt.get('result', None) == 'FAILED': - msg = receipt.get('resMessage', receipt['result']) + if receipt.get("result", None) == "FAILED": + msg = receipt.get("resMessage", receipt["result"]) - if receipt['receipt']['result'] == 'REVERT': + if receipt["receipt"]["result"] == "REVERT": try: - result = receipt.get('contractResult', []) + result = receipt.get("contractResult", []) if result and len(result[0]) > (4 + 32) * 2: - error_msg = tron_abi.decode_single('string', bytes.fromhex(result[0])[4 + 32 :]) - msg = "{}: {}".format(msg, error_msg) + error_msg = tron_abi.decode_single("string", bytes.fromhex(result[0])[4 + 32 :]) + msg = f"{msg}: {error_msg}" except Exception: pass raise TvmError(msg) - return self._method.parse_output(receipt['contractResult'][0]) + return self._method.parse_output(receipt["contractResult"][0]) EMPTY = object() # noinspection PyBroadException,PyProtectedMember -class AsyncTransaction(object): +class AsyncTransaction: """The Transaction object, signed or unsigned.""" - def __init__(self, - raw_data: dict, - client: "AsyncTron" = None, - method: AsyncContractMethod = None, - txid: str = "", - permission: dict = None, - signature: list = None): + def __init__( + self, + raw_data: dict, + client: "AsyncTron" = None, + method: AsyncContractMethod = None, + txid: str = "", + permission: dict = None, + signature: list = None, + ): self._raw_data: dict = raw_data.get("raw_data", raw_data) self._signature: list = raw_data.get("signature", signature or []) self._client = client @@ -137,15 +139,17 @@ async def check_sign_weight(self): sign_weight = await self._client.get_sign_weight(self) if "transaction" not in sign_weight: self._client._handle_api_error(sign_weight) - raise TransactionError('transaction not in sign_weight') + raise TransactionError("transaction not in sign_weight") self.txid = sign_weight["transaction"]["transaction"]["txID"] # when account not exist on-chain self._permission = sign_weight.get("permission", None) def to_json(self) -> dict: return { - "txID": self.txid, "raw_data": self._raw_data, - "signature": self._signature, "permission": self._permission if self._permission is not EMPTY else None + "txID": self.txid, + "raw_data": self._raw_data, + "signature": self._signature, + "permission": self._permission if self._permission is not EMPTY else None, } @classmethod @@ -154,8 +158,10 @@ async def from_json(cls, data: Union[str, dict], client: "AsyncTron" = None) -> data = json.loads(data) return await cls.create( client=client, - txid=data['txID'], permission=data['permission'], - raw_data=data['raw_data'], signature=data['signature'] + txid=data["txID"], + permission=data["permission"], + raw_data=data["raw_data"], + signature=data["signature"], ) def inspect(self) -> "AsyncTransaction": @@ -166,7 +172,7 @@ def sign(self, priv_key: PrivateKey) -> "AsyncTransaction": """Sign the transaction with a private key.""" assert self.txid, "txID not calculated" - assert self.is_expired is False, 'expired' + assert self.is_expired is False, "expired" if self._permission is not None: addr_of_key = priv_key.public_key.to_hex_address() @@ -176,8 +182,8 @@ def sign(self, priv_key: PrivateKey) -> "AsyncTransaction": else: raise BadKey( "provided private key is not in the permission list", - "provided {}".format(priv_key.public_key.to_base58check_address()), - "required {}".format(self._permission), + f"provided {priv_key.public_key.to_base58check_address()}", + f"required {self._permission}", ) sig = priv_key.sign_msg_hash(bytes.fromhex(self.txid)) self._signature.append(sig.hex()) @@ -194,7 +200,7 @@ def set_signature(self, signature: list) -> "AsyncTransaction": @property def is_expired(self) -> bool: - return current_timestamp() >= self._raw_data['expiration'] + return current_timestamp() >= self._raw_data["expiration"] async def update(self): """update Transaction, change ref_block and txID, remove all signature""" @@ -225,7 +231,7 @@ def __str__(self): # noinspection PyBroadException -class AsyncTransactionBuilder(object): +class AsyncTransactionBuilder: """TransactionBuilder, to build a :class:`~Transaction` object.""" def __init__(self, inner: dict, client: "AsyncTron", method: AsyncContractMethod = None): @@ -238,8 +244,8 @@ def __init__(self, inner: dict, client: "AsyncTron", method: AsyncContractMethod "ref_block_hash": None, } - if inner.get('type', None) in ['TriggerSmartContract', 'CreateSmartContract']: - self._raw_data["fee_limit"] = self._client.conf['fee_limit'] + if inner.get("type", None) in ["TriggerSmartContract", "CreateSmartContract"]: + self._raw_data["fee_limit"] = self._client.conf["fee_limit"] self._method = method @@ -263,7 +269,7 @@ def memo(self, memo: Union[str, bytes]) -> "AsyncTransactionBuilder": return self def expiration(self, expiration: int) -> "AsyncTransactionBuilder": - self._raw_data['expiration'] = current_timestamp() + expiration + self._raw_data["expiration"] = current_timestamp() + expiration return self def fee_limit(self, value: int) -> "AsyncTransactionBuilder": @@ -286,7 +292,7 @@ async def build(self, options=None, **kwargs) -> AsyncTransaction: # noinspection PyBroadException -class AsyncTrx(object): +class AsyncTrx: """The Trx(transaction) API.""" def __init__(self, tron): @@ -296,11 +302,9 @@ def __init__(self, tron): def client(self) -> "AsyncTron": return self._tron - def _build_transaction( - self, type_: str, obj: dict, *, method: AsyncContractMethod = None - ) -> AsyncTransactionBuilder: + def _build_transaction(self, type_: str, obj: dict, *, method: AsyncContractMethod = None) -> AsyncTransactionBuilder: inner = { - "parameter": {"value": obj, "type_url": "type.googleapis.com/protocol.{}".format(type_)}, + "parameter": {"value": obj, "type_url": f"type.googleapis.com/protocol.{type_}"}, "type": type_, } if method: @@ -391,25 +395,27 @@ def account_permission_update(self, owner: TAddress, perm: dict) -> "AsyncTransa :param perm: Permission dict from :meth:`~tronpy.Tron.get_account_permission` """ - if 'owner' in perm: - for key in perm['owner']['keys']: - key['address'] = keys.to_hex_address(key['address']) - if 'actives' in perm: - for act in perm['actives']: - for key in act['keys']: - key['address'] = keys.to_hex_address(key['address']) - if perm.get('witness', None): - for key in perm['witness']['keys']: - key['address'] = keys.to_hex_address(key['address']) + if "owner" in perm: + for key in perm["owner"]["keys"]: + key["address"] = keys.to_hex_address(key["address"]) + if "actives" in perm: + for act in perm["actives"]: + for key in act["keys"]: + key["address"] = keys.to_hex_address(key["address"]) + if perm.get("witness", None): + for key in perm["witness"]["keys"]: + key["address"] = keys.to_hex_address(key["address"]) return self._build_transaction( - "AccountPermissionUpdateContract", dict(owner_address=keys.to_hex_address(owner), **perm), + "AccountPermissionUpdateContract", + dict(owner_address=keys.to_hex_address(owner), **perm), ) def account_update(self, owner: TAddress, name: str) -> "AsyncTransactionBuilder": """Update account name. An account can only set name once.""" return self._build_transaction( - "UpdateAccountContract", {"owner_address": keys.to_hex_address(owner), "account_name": name.encode().hex()}, + "UpdateAccountContract", + {"owner_address": keys.to_hex_address(owner), "account_name": name.encode().hex()}, ) def freeze_balance( @@ -475,7 +481,7 @@ def deploy_contract(self, owner: TAddress, contract: AsyncContract) -> "AsyncTra # noinspection PyBroadException -class AsyncTron(object): +class AsyncTron: """The Async TRON API Client. :param provider: An :class:`~tronpy.providers.HTTPProvider` object, can be configured to use private node @@ -508,7 +514,7 @@ def __init__(self, provider: AsyncHTTPProvider = None, *, network: str = "mainne self.conf = dict(DEFAULT_CONF, **conf) if provider is None: - self.provider = AsyncHTTPProvider(conf_for_name(network), self.conf['timeout']) + self.provider = AsyncHTTPProvider(conf_for_name(network), self.conf["timeout"]) elif isinstance(provider, (AsyncHTTPProvider,)): self.provider = provider else: @@ -595,7 +601,16 @@ async def get_zkey_from_sk(self, sk: str, d: str = None) -> dict: payment_address = ret["payment_address"] return dict( - sk=sk, ask=ask, nsk=nsk, ovk=ovk, ak=ak, nk=nk, ivk=ivk, d=d, pkD=pkD, payment_address=payment_address, + sk=sk, + ask=ask, + nsk=nsk, + ovk=ovk, + ak=ak, + nk=nk, + ivk=ivk, + d=d, + pkD=pkD, + payment_address=payment_address, ) # Account query @@ -614,12 +629,10 @@ async def get_account(self, addr: TAddress) -> dict: async def get_bandwidth(self, addr: TAddress) -> dict: """Query the bandwidth of the account""" - ret = await self.provider.make_request( - "wallet/getaccountnet", {"address": keys.to_base58check_address(addr)} - ) + ret = await self.provider.make_request("wallet/getaccountnet", {"address": keys.to_base58check_address(addr)}) if ret: # (freeNetLimit - freeNetUsed) + (NetLimit - NetUsed) - return ret['freeNetLimit'] - ret.get('freeNetUsed', 0) + ret.get('NetLimit', 0) - ret.get('NetUsed', 0) + return ret["freeNetLimit"] - ret.get("freeNetUsed", 0) + ret.get("NetLimit", 0) - ret.get("NetUsed", 0) else: raise AddressNotFound("account not found on-chain") @@ -627,7 +640,8 @@ async def get_account_resource(self, addr: TAddress) -> dict: """Get resource info of an account.""" ret = await self.provider.make_request( - "wallet/getaccountresource", {"address": keys.to_base58check_address(addr), "visible": True}, + "wallet/getaccountresource", + {"address": keys.to_base58check_address(addr), "visible": True}, ) if ret: return ret @@ -643,7 +657,7 @@ async def get_account_balance(self, addr: TAddress) -> Decimal: async def get_account_asset_balances(self, addr: TAddress) -> dict: """Get all TRC10 token balances of an account.""" info = await self.get_account(addr) - return {p['key']: p['value'] for p in info.get("assetV2", {}) if p['value'] > 0} + return {p["key"]: p["value"] for p in info.get("assetV2", {}) if p["value"] > 0} async def get_account_asset_balance(self, addr: TAddress, token_id: Union[int, str]) -> int: """Get TRC10 token balance of an account. Result is in raw amount.""" @@ -742,9 +756,9 @@ async def get_block(self, id_or_num: Union[None, str, int] = None, *, visible: b elif id_or_num is None: block = await self.provider.make_request("wallet/getnowblock", {"visible": visible}) else: - raise TypeError("can not infer type of {}".format(id_or_num)) + raise TypeError(f"can not infer type of {id_or_num}") - if 'Error' in (block or {}): + if "Error" in (block or {}): raise BugInJavaTron(block) elif block: return block @@ -793,9 +807,7 @@ async def get_solid_transaction_info(self, txn_id: str) -> dict: if len(txn_id) != 64: raise BadHash("wrong transaction hash length") - ret = await self.provider.make_request( - "walletsolidity/gettransactioninfobyid", {"value": txn_id, "visible": True} - ) + ret = await self.provider.make_request("walletsolidity/gettransactioninfobyid", {"value": txn_id, "visible": True}) self._handle_api_error(ret) if ret: return ret @@ -842,16 +854,17 @@ async def get_asset(self, id: int = None, issuer: TAddress = None) -> dict: return await self.provider.make_request("wallet/getassetissuebyid", {"value": id, "visible": True}) else: return await self.provider.make_request( - "wallet/getassetissuebyaccount", {"address": keys.to_base58check_address(issuer), "visible": True}, + "wallet/getassetissuebyaccount", + {"address": keys.to_base58check_address(issuer), "visible": True}, ) async def get_asset_from_name(self, name: str) -> dict: """Get asset info from its abbr name, might fail if there're duplicates.""" - assets = [asset for asset in await self.list_assets() if asset['abbr'] == name] + assets = [asset for asset in await self.list_assets() if asset["abbr"] == name] if assets: if len(assets) == 1: return assets[0] - raise ValueError("duplicated assets with the same name", [asset['id'] for asset in assets]) + raise ValueError("duplicated assets with the same name", [asset["id"] for asset in assets]) raise AssetNotFound async def list_assets(self) -> list: @@ -885,7 +898,7 @@ async def get_contract(self, addr: TAddress) -> AsyncContract: cntr = AsyncContract( addr=addr, - bytecode=info.get("bytecode", ''), + bytecode=info.get("bytecode", ""), name=info.get("name", ""), abi=info.get("abi", {}).get("entrys", []), origin_energy_limit=info.get("origin_energy_limit", 0), @@ -902,7 +915,11 @@ async def get_contract_as_shielded_trc20(self, addr: TAddress) -> ShieldedTRC20: return ShieldedTRC20(contract) async def trigger_const_smart_contract_function( - self, owner_address: TAddress, contract_address: TAddress, function_selector: str, parameter: str, + self, + owner_address: TAddress, + contract_address: TAddress, + function_selector: str, + parameter: str, ) -> str: ret = await self.provider.make_request( "wallet/triggerconstantcontract", @@ -915,13 +932,13 @@ async def trigger_const_smart_contract_function( }, ) self._handle_api_error(ret) - if 'message' in ret.get('result', {}): - msg = ret['result']['message'] - result = ret.get('constant_result', []) + if "message" in ret.get("result", {}): + msg = ret["result"]["message"] + result = ret.get("constant_result", []) try: if result and len(result[0]) > (4 + 32) * 2: - error_msg = tron_abi.decode_single('string', bytes.fromhex(result[0])[4 + 32 :]) - msg = "{}: {}".format(msg, error_msg) + error_msg = tron_abi.decode_single("string", bytes.fromhex(result[0])[4 + 32 :]) + msg = f"{msg}: {error_msg}" except Exception: pass raise TvmError(msg) diff --git a/tronpy/contract.py b/tronpy/contract.py index 5521c12..266f060 100644 --- a/tronpy/contract.py +++ b/tronpy/contract.py @@ -1,11 +1,12 @@ import itertools -from typing import Union, Optional, Any, Tuple, List, Generator +from typing import Any, Generator, List, Optional, Tuple, Union + from Crypto.Hash import keccak from eth_utils import decode_hex import tronpy -from tronpy.exceptions import DoubleSpending from tronpy.abi import trx_abi +from tronpy.exceptions import DoubleSpending from tronpy.keys import to_hex_address @@ -23,14 +24,14 @@ def assure_bytes(value: Union[str, bytes]) -> bytes: raise ValueError("bad bytes format") -class Contract(object): +class Contract: """A smart contract object.""" def __init__( self, addr=None, *, - bytecode: Union[str, bytes] = '', + bytecode: Union[str, bytes] = "", name: str = None, abi: Optional[Union[dict, List[dict]]] = None, user_resource_percent: int = 100, @@ -72,7 +73,7 @@ def __init__( self._client = client def __str__(self): - return "".format(self.name, self.contract_address) + return f"" @property def bytecode(self): @@ -85,7 +86,7 @@ def bytecode(self, value): def deploy(self) -> Any: if self.contract_address: - raise RuntimeError("this contract has already deployed to {}".format(self.contract_address)) + raise RuntimeError(f"this contract has already deployed to {self.contract_address}") if self.origin_address != self.owner_address: raise RuntimeError("origin address and owner address mismatch") @@ -161,7 +162,7 @@ def functions(self) -> "ContractFunctions": def constructor(self) -> "ContractConstructor": """The constructor of the contract.""" for method_abi in self.abi: - if method_abi.get('type', '').lower() == 'constructor': + if method_abi.get("type", "").lower() == "constructor": return ContractConstructor(method_abi, self) raise NameError("Contract has no constructor") @@ -176,7 +177,7 @@ def events(self) -> "ContractEvents": raise ValueError("can not call a contract without ABI") return self._events - def get_function_by_selector(self, selector) -> Union['ContractMethod', None]: + def get_function_by_selector(self, selector) -> Union["ContractMethod", None]: selector = selector.hex() for fn in self.functions: @@ -186,7 +187,7 @@ def get_function_by_selector(self, selector) -> Union['ContractMethod', None]: return None -class ContractEvents(object): +class ContractEvents: def __init__(self, contract): self._contract = contract @@ -195,14 +196,14 @@ def __getitem__(self, event_name: str): if _abi.get("type", "").lower() == "event" and _abi["name"] == event_name: return ContractEvent(_abi, self._contract, event_name) - raise KeyError("contract has no event named '{}'".format(event_name)) + raise KeyError(f"contract has no event named '{event_name}'") def __getattr__(self, event: str): """Get the actual contract event object.""" try: return self[event] except KeyError: - raise AttributeError("contract has no method named '{}'".format(event)) + raise AttributeError(f"contract has no method named '{event}'") def __dir__(self): return [event["name"] for event in self._contract.abi if event.get("type", "").lower() == "event"] @@ -211,53 +212,53 @@ def __iter__(self): yield from [self[event] for event in dir(self)] -class ContractEvent(object): +class ContractEvent: def __init__(self, abi: dict, contract: "Contract", event_name: str): self._abi = abi self._contract = contract self._event_name = event_name def process_receipt(self, txn_receipt: dict) -> Generator: - return self.parse_logs(txn_receipt['log']) + return self.parse_logs(txn_receipt["log"]) def parse_logs(self, logs: List[dict]): for log in logs: - if log['address'] != self._contract.contract_address: + if log["address"] != self._contract.contract_address: continue yield self.get_event_data(log) def get_event_data(self, log: dict): - data_types, data_names, topic_types, topic_names = [], [], [], [] # cannot use `[[]] * 4` - for arg in self._abi['inputs']: - if arg.get('indexed', False) is False: - data_types.append(arg.get('type', '')) - data_names.append(arg['name']) + data_types, data_names, topic_types, topic_names = [], [], [], [] # cannot use `[[]] * 4` + for arg in self._abi["inputs"]: + if arg.get("indexed", False) is False: + data_types.append(arg.get("type", "")) + data_names.append(arg["name"]) else: - topic_types.append(arg.get('type', '')) - topic_names.append(arg['name']) + topic_types.append(arg.get("type", "")) + topic_names.append(arg["name"]) - topics = log['topics'][1:] + topics = log["topics"][1:] decoded_topic_data = [ - trx_abi.decode_single(topic_type, decode_hex(topic_data)) - for topic_type, topic_data - in zip(topic_types, topics) + trx_abi.decode_single(topic_type, decode_hex(topic_data)) for topic_type, topic_data in zip(topic_types, topics) ] - data = decode_hex(log['data']) + data = decode_hex(log["data"]) decoded_data = trx_abi.decode_abi(data_types, data) - event_args = dict(itertools.chain( - zip(topic_names, decoded_topic_data), - zip(data_names, decoded_data), - )) + event_args = dict( + itertools.chain( + zip(topic_names, decoded_topic_data), + zip(data_names, decoded_data), + ) + ) return { - 'args': event_args, - 'event': self._event_name, - 'address': log['address'], + "args": event_args, + "event": self._event_name, + "address": log["address"], } -class ContractFunctions(object): +class ContractFunctions: def __init__(self, contract): self._contract = contract @@ -266,14 +267,14 @@ def __getitem__(self, method: str): if method_abi.get("type", "").lower() == "function" and method_abi["name"] == method: return ContractMethod(method_abi, self._contract) - raise KeyError("contract has no method named '{}'".format(method)) + raise KeyError(f"contract has no method named '{method}'") def __getattr__(self, method: str): """Get the actual contract method object.""" try: return self[method] except KeyError: - raise AttributeError("contract has no method named '{}'".format(method)) + raise AttributeError(f"contract has no method named '{method}'") def __dir__(self): return [method["name"] for method in self._contract.abi if method.get("type", "").lower() == "function"] @@ -282,11 +283,10 @@ def __iter__(self): yield from [self[method] for method in dir(self)] -class ContractConstructor(object): +class ContractConstructor: """The constructor method of a contract.""" def __init__(self, abi: dict, contract: Contract): - self._abi = abi self._contract = contract @@ -295,7 +295,7 @@ def __init__(self, abi: dict, contract: Contract): def __str__(self): types = ", ".join(arg.get("type", "") + " " + arg.get("name", "") for arg in self.inputs) - ret = "construct({})".format(types) + ret = f"construct({types})" return ret @property @@ -311,14 +311,14 @@ def encode_parameter(self, *args, **kwargs) -> str: if len(self.inputs) == 0: if args or kwargs: - raise TypeError("{} constructor requires {} arguments".format(self._contract.name, len(self.inputs))) + raise TypeError(f"{self._contract.name} constructor requires {len(self.inputs)} arguments") elif args: if len(args) != len(self.inputs): - raise TypeError("wrong number of arguments, require {} got {}".format(len(self.inputs), len(args))) + raise TypeError(f"wrong number of arguments, require {len(self.inputs)} got {len(args)}") parameter = trx_abi.encode_single(self.input_type, args).hex() elif kwargs: if len(kwargs) != len(self.inputs): - raise TypeError("wrong number of arguments, require {} got {}".format(len(self.inputs), len(args))) + raise TypeError(f"wrong number of arguments, require {len(self.inputs)} got {len(args)}") args = [] for arg in self.inputs: try: @@ -330,9 +330,8 @@ def encode_parameter(self, *args, **kwargs) -> str: return parameter -class ContractMethod(object): +class ContractMethod: def __init__(self, abi: dict, contract: Contract): - self._abi = abi self._contract = contract self._owner_address = contract.owner_address @@ -394,14 +393,14 @@ def _prepare_parameter(self, *args, **kwargs) -> "tronpy.tron.TransactionBuilder if len(self.inputs) == 0: if args or kwargs: - raise TypeError("{} expected {} arguments".format(self.name, len(self.inputs))) + raise TypeError(f"{self.name} expected {len(self.inputs)} arguments") elif args: if len(args) != len(self.inputs): - raise TypeError("wrong number of arguments, require {} got {}".format(len(self.inputs), len(args))) + raise TypeError(f"wrong number of arguments, require {len(self.inputs)} got {len(args)}") parameter = trx_abi.encode_single(self.input_type, args).hex() elif kwargs: if len(kwargs) != len(self.inputs): - raise TypeError("wrong number of arguments, require {} got {}".format(len(self.inputs), len(args))) + raise TypeError(f"wrong number of arguments, require {len(self.inputs)} got {len(args)}") args = [] for arg in self.inputs: try: @@ -410,14 +409,17 @@ def _prepare_parameter(self, *args, **kwargs) -> "tronpy.tron.TransactionBuilder raise TypeError("missing argument '{}'".format(arg["name"])) parameter = trx_abi.encode_single(self.input_type, args).hex() else: - raise TypeError("wrong number of arguments, require {}".format(len(self.inputs))) + raise TypeError(f"wrong number of arguments, require {len(self.inputs)}") return parameter def _trigger_contract(self, parameter): if self._abi.get("stateMutability", None).lower() in ["view", "pure"]: # const call, contract ret ret = self._client.trigger_const_smart_contract_function( - self._owner_address, self._contract.contract_address, self.function_signature, parameter, + self._owner_address, + self._contract.contract_address, + self.function_signature, + parameter, ) return self.parse_output(ret) @@ -449,15 +451,13 @@ def output_type(self) -> str: return "({})".format(",".join(self.__format_json_abi_type_entry(arg) for arg in self.outputs)) def __format_json_abi_type_entry(self, entry) -> str: - if entry.get('type', '').startswith('tuple'): - surfix = entry['type'][5:] - if 'components' not in entry: + if entry.get("type", "").startswith("tuple"): + surfix = entry["type"][5:] + if "components" not in entry: raise ValueError("ABIEncoderV2 used, ABI should be set by hand") - return "({}){}".format( - ",".join(self.__format_json_abi_type_entry(arg) for arg in entry['components']), surfix - ) + return "({}){}".format(",".join(self.__format_json_abi_type_entry(arg) for arg in entry["components"]), surfix) else: - return entry.get('type', '') + return entry.get("type", "") @property def function_signature(self) -> str: @@ -470,7 +470,7 @@ def function_signature_hash(self) -> str: @property def function_type(self) -> str: types = ", ".join(arg.get("type", "") + " " + arg.get("name", "") for arg in self.inputs) - ret = "function {}({})".format(self.name, types) + ret = f"function {self.name}({types})" if self._abi.get("stateMutability", None).lower() == "view": ret += " view" elif self._abi.get("stateMutability", None).lower() == "pure": @@ -483,7 +483,7 @@ def as_shielded_trc20(self, trc20_addr: str) -> "ShieldedTRC20": return ShieldedTRC20(self, trc20_addr) -class ShieldedTRC20(object): +class ShieldedTRC20: """Shielded TRC20 Wrapper.""" def __init__(self, contract: Contract): @@ -545,7 +545,10 @@ def mint(self, taddr: str, zaddr: str, amount: int, memo: str = "") -> "tronpy.t ) def transfer( - self, zkey: dict, notes: Union[list, dict], *to: Union[Tuple[str, int], Tuple[str, int, str]], + self, + zkey: dict, + notes: Union[list, dict], + *to: Union[Tuple[str, int], Tuple[str, int, str]], ) -> "tronpy.tron.TransactionBuilder": """Transfer from z-address to z-address.""" if isinstance(notes, (dict,)): @@ -560,9 +563,7 @@ def transfer( raise DoubleSpending alpha = self.get_rcm() root, path = self.get_path(note.get("position", 0)) - spends.append( - {"note": note["note"], "alpha": alpha, "root": root, "path": path, "pos": note.get("position", 0)} - ) + spends.append({"note": note["note"], "alpha": alpha, "root": root, "path": path, "pos": note.get("position", 0)}) spend_amount += note["note"]["value"] receives = [] @@ -578,9 +579,7 @@ def transfer( rcm = self.get_rcm() - receives.append( - {"note": {"value": amount, "payment_address": addr, "rcm": rcm, "memo": memo.encode().hex()}} - ) + receives.append({"note": {"value": amount, "payment_address": addr, "rcm": rcm, "memo": memo.encode().hex()}}) if spend_amount != receive_amount: raise ValueError("spend amount is not equal to receive amount") @@ -616,16 +615,14 @@ def burn( root, path = self.get_path(note.get("position", 0)) if note.get("is_spent", False): raise DoubleSpending - spends.append( - {"note": note["note"], "alpha": alpha, "root": root, "path": path, "pos": note.get("position", 0)} - ) + spends.append({"note": note["note"], "alpha": alpha, "root": root, "path": path, "pos": note.get("position", 0)}) change_amount = 0 receives = [] to_addr = None to_amount = 0 - to_memo = '' + to_memo = "" if not to: - raise ValueError('burn must have a output') + raise ValueError("burn must have a output") for receive in to: addr = receive[0] amount = receive[1] @@ -634,12 +631,10 @@ def burn( else: memo = "" - if addr.startswith('ztron1'): + if addr.startswith("ztron1"): change_amount += amount rcm = self.get_rcm() - receives = [ - {"note": {"value": amount, "payment_address": addr, "rcm": rcm, "memo": memo.encode().hex()}} - ] + receives = [{"note": {"value": amount, "payment_address": addr, "rcm": rcm, "memo": memo.encode().hex()}}] else: # assume T-address to_addr = addr @@ -743,4 +738,4 @@ def is_note_spent(self, zkey: dict, note: dict) -> bool: ret = self._client.provider.make_request("wallet/isshieldedtrc20contractnotespent", payload) - return ret.get('is_spent', None) + return ret.get("is_spent", None) diff --git a/tronpy/keys/__init__.py b/tronpy/keys/__init__.py index dcbe255..2f9ae3c 100644 --- a/tronpy/keys/__init__.py +++ b/tronpy/keys/__init__.py @@ -1,12 +1,14 @@ -from Crypto.Hash import keccak import hashlib -import base58 -from collections.abc import ByteString, Hashable import random -from typing import Any, Union, Iterator -from coincurve import PrivateKey as CoincurvePrivateKey, PublicKey as CoincurvePublicKey +from collections.abc import ByteString, Hashable +from typing import Any, Iterator, Union + +import base58 +from coincurve import PrivateKey as CoincurvePrivateKey +from coincurve import PublicKey as CoincurvePublicKey +from Crypto.Hash import keccak -from tronpy.exceptions import BadKey, BadSignature, BadAddress +from tronpy.exceptions import BadAddress, BadKey, BadSignature SECPK1_N = 115792089237316195423570985008687907852837564279074904382605163141518161494337 @@ -356,7 +358,7 @@ def hex(self) -> str: return self._raw_signature.hex() @classmethod - def fromhex(cls, hex_str: str) -> 'Signature': + def fromhex(cls, hex_str: str) -> "Signature": """Construct a Signature from hex str.""" return cls(bytes.fromhex(hex_str)) diff --git a/tronpy/providers/__init__.py b/tronpy/providers/__init__.py index 7dd429f..d8a1f38 100644 --- a/tronpy/providers/__init__.py +++ b/tronpy/providers/__init__.py @@ -1,4 +1,4 @@ -from .http import HTTPProvider from .async_http import AsyncHTTPProvider +from .http import HTTPProvider __all__ = [HTTPProvider, AsyncHTTPProvider] diff --git a/tronpy/providers/async_http.py b/tronpy/providers/async_http.py index 27f4219..bf2e3c2 100644 --- a/tronpy/providers/async_http.py +++ b/tronpy/providers/async_http.py @@ -1,15 +1,15 @@ import os -from urllib.parse import urljoin from typing import Any, Union +from urllib.parse import urljoin import httpx from httpx import Timeout DEFAULT_TIMEOUT = 10.0 -DEFAULT_API_KEY = 'f92221d5-7056-4366-b96f-65d3662ec2d9' +DEFAULT_API_KEY = "f92221d5-7056-4366-b96f-65d3662ec2d9" -class AsyncHTTPProvider(object): +class AsyncHTTPProvider: """An Async HTTP Provider for API request. :params endpoint_uri: HTTP API URL base. Default value is ``"https://api.trongrid.io/"``. Can also be configured via @@ -32,7 +32,7 @@ def __init__( elif isinstance(endpoint_uri, (str,)): self.endpoint_uri = endpoint_uri else: - raise TypeError("unknown endpoint uri {}".format(endpoint_uri)) + raise TypeError(f"unknown endpoint uri {endpoint_uri}") headers = {"User-Agent": "Tronpy/0.2", "Tron-Pro-Api-Key": api_key} if client is None: diff --git a/tronpy/providers/http.py b/tronpy/providers/http.py index 3257ee1..9859d67 100644 --- a/tronpy/providers/http.py +++ b/tronpy/providers/http.py @@ -1,23 +1,24 @@ import os -import requests -from urllib.parse import urljoin -from typing import Any, Union, List import random -import time import sys +import time +from typing import Any, List, Union +from urllib.parse import urljoin + +import requests DEFAULT_TIMEOUT = 10.0 DEFAULT_API_KEYS = [ - 'f92221d5-7056-4366-b96f-65d3662ec2d9', - '1e0a625f-cfa5-43ee-ba41-a09db1aae55f', - 'f399168e-2259-481c-90fc-6b3d984c5463', - 'da63253b-aa9c-46e7-a4e8-22d259a8026d', - '88c10958-af7b-4d5a-8eef-6e84bf5fb809', - '169bb4b3-cbe8-449a-984e-80e9adacac55', + "f92221d5-7056-4366-b96f-65d3662ec2d9", + "1e0a625f-cfa5-43ee-ba41-a09db1aae55f", + "f399168e-2259-481c-90fc-6b3d984c5463", + "da63253b-aa9c-46e7-a4e8-22d259a8026d", + "88c10958-af7b-4d5a-8eef-6e84bf5fb809", + "169bb4b3-cbe8-449a-984e-80e9adacac55", ] -class HTTPProvider(object): +class HTTPProvider: """An HTTP Provider for API request. :param endpoint_uri: HTTP API URL base. Default value is ``"https://api.trongrid.io/"``. Can also be configured via @@ -41,9 +42,9 @@ def __init__( elif isinstance(endpoint_uri, (str,)): self.endpoint_uri = endpoint_uri else: - raise TypeError("unknown endpoint uri {}".format(endpoint_uri)) + raise TypeError(f"unknown endpoint uri {endpoint_uri}") - if 'trongrid' in self.endpoint_uri: + if "trongrid" in self.endpoint_uri: self.use_api_key = True if isinstance(api_key, (str,)): self._api_keys = [api_key] @@ -72,8 +73,8 @@ def make_request(self, method: str, params: Any = None) -> dict: resp = self.sess.post(url, json=params, timeout=self.timeout) if self.use_api_key: - if resp.status_code == 403 and b'Exceed the user daily usage' in resp.content: - print("W:", resp.json().get('Error', 'rate limit!'), file=sys.stderr) + if resp.status_code == 403 and b"Exceed the user daily usage" in resp.content: + print("W:", resp.json().get("Error", "rate limit!"), file=sys.stderr) self._handle_rate_limit() return self.make_request(method, params) diff --git a/tronpy/tron.py b/tronpy/tron.py index eabad2a..7af9598 100644 --- a/tronpy/tron.py +++ b/tronpy/tron.py @@ -1,37 +1,37 @@ -import time import json -from pprint import pprint +import time from decimal import Decimal -from typing import Union, Tuple, Optional +from pprint import pprint +from typing import Optional, Tuple, Union from tronpy import keys -from tronpy.contract import Contract, ShieldedTRC20, ContractMethod -from tronpy.keys import PrivateKey -from tronpy.providers import HTTPProvider from tronpy.abi import tron_abi +from tronpy.contract import Contract, ContractMethod, ShieldedTRC20 from tronpy.defaults import conf_for_name from tronpy.exceptions import ( - BadSignature, - BadKey, + AddressNotFound, + ApiError, + AssetNotFound, BadHash, + BadKey, + BadSignature, BlockNotFound, - AssetNotFound, + BugInJavaTron, TaposError, - UnknownError, TransactionError, - ValidationError, - ApiError, - AddressNotFound, TransactionNotFound, TvmError, - BugInJavaTron, + UnknownError, + ValidationError, ) +from tronpy.keys import PrivateKey +from tronpy.providers import HTTPProvider TAddress = str DEFAULT_CONF = { - 'fee_limit': 10_000_000, - 'timeout': 10.0, # in second + "fee_limit": 10_000_000, + "timeout": 10.0, # in second } @@ -81,30 +81,37 @@ def result(self, timeout=30, interval=1.6, solid=False) -> dict: receipt = self.wait(timeout, interval, solid) - if receipt.get('result', None) == 'FAILED': - msg = receipt.get('resMessage', receipt['result']) + if receipt.get("result", None) == "FAILED": + msg = receipt.get("resMessage", receipt["result"]) - if receipt['receipt']['result'] == 'REVERT': + if receipt["receipt"]["result"] == "REVERT": try: - result = receipt.get('contractResult', []) + result = receipt.get("contractResult", []) if result and len(result[0]) > (4 + 32) * 2: - error_msg = tron_abi.decode_single('string', bytes.fromhex(result[0])[4 + 32 :]) - msg = "{}: {}".format(msg, error_msg) + error_msg = tron_abi.decode_single("string", bytes.fromhex(result[0])[4 + 32 :]) + msg = f"{msg}: {error_msg}" except Exception: pass raise TvmError(msg) - return self._method.parse_output(receipt['contractResult'][0]) + return self._method.parse_output(receipt["contractResult"][0]) EMPTY = object() -class Transaction(object): +class Transaction: """The Transaction object, signed or unsigned.""" - def __init__(self, raw_data: dict, client: "Tron" = None, method: ContractMethod = None, - txid: str = "", permission: dict = EMPTY, signature: list = None): + def __init__( + self, + raw_data: dict, + client: "Tron" = None, + method: ContractMethod = None, + txid: str = "", + permission: dict = EMPTY, + signature: list = None, + ): self._raw_data: dict = raw_data self._signature: list = signature or [] self._client = client @@ -128,8 +135,10 @@ def __init__(self, raw_data: dict, client: "Tron" = None, method: ContractMethod def to_json(self) -> dict: return { - "txID": self.txid, "raw_data": self._raw_data, - "signature": self._signature, "permission": self._permission if self._permission is not EMPTY else None, + "txID": self.txid, + "raw_data": self._raw_data, + "signature": self._signature, + "permission": self._permission if self._permission is not EMPTY else None, } @classmethod @@ -138,8 +147,10 @@ def from_json(cls, data: Union[str, dict], client: "Tron" = None): data = json.loads(data) return cls( client=client, - txid=data['txID'], permission=data['permission'], - raw_data=data['raw_data'], signature=data['signature'] + txid=data["txID"], + permission=data["permission"], + raw_data=data["raw_data"], + signature=data["signature"], ) def inspect(self) -> "Transaction": @@ -150,7 +161,7 @@ def sign(self, priv_key: PrivateKey) -> "Transaction": """Sign the transaction with a private key.""" assert self.txid, "txID not calculated" - assert self.is_expired is False, 'expired' + assert self.is_expired is False, "expired" if self._permission is not None: addr_of_key = priv_key.public_key.to_hex_address() @@ -160,8 +171,8 @@ def sign(self, priv_key: PrivateKey) -> "Transaction": else: raise BadKey( "provided private key is not in the permission list", - "provided {}".format(priv_key.public_key.to_base58check_address()), - "required {}".format(self._permission), + f"provided {priv_key.public_key.to_base58check_address()}", + f"required {self._permission}", ) sig = priv_key.sign_msg_hash(bytes.fromhex(self.txid)) self._signature.append(sig.hex()) @@ -178,7 +189,7 @@ def set_signature(self, signature: list) -> "Transaction": @property def is_expired(self) -> bool: - return current_timestamp() >= self._raw_data['expiration'] + return current_timestamp() >= self._raw_data["expiration"] def update(self): """update Transaction, change ref_block and txID, remove all signature""" @@ -208,7 +219,7 @@ def __str__(self): return json.dumps(self.to_json(), indent=2) -class TransactionBuilder(object): +class TransactionBuilder: """TransactionBuilder, to build a :class:`~Transaction` object.""" def __init__(self, inner: dict, client: "Tron", method: ContractMethod = None): @@ -221,8 +232,8 @@ def __init__(self, inner: dict, client: "Tron", method: ContractMethod = None): "ref_block_hash": None, } - if inner.get('type', None) in ['TriggerSmartContract', 'CreateSmartContract']: - self._raw_data["fee_limit"] = self._client.conf['fee_limit'] + if inner.get("type", None) in ["TriggerSmartContract", "CreateSmartContract"]: + self._raw_data["fee_limit"] = self._client.conf["fee_limit"] self._method = method @@ -246,7 +257,7 @@ def memo(self, memo: Union[str, bytes]) -> "TransactionBuilder": return self def expiration(self, expiration: int) -> "TransactionBuilder": - self._raw_data['expiration'] = current_timestamp() + expiration + self._raw_data["expiration"] = current_timestamp() + expiration return self def fee_limit(self, value: int) -> "TransactionBuilder": @@ -268,7 +279,7 @@ def build(self, options=None, **kwargs) -> Transaction: return Transaction(self._raw_data, client=self._client) -class Trx(object): +class Trx: """The Trx(transaction) API.""" def __init__(self, tron): @@ -280,7 +291,7 @@ def client(self) -> "Tron": def _build_transaction(self, type_: str, obj: dict, *, method: ContractMethod = None) -> TransactionBuilder: inner = { - "parameter": {"value": obj, "type_url": "type.googleapis.com/protocol.{}".format(type_)}, + "parameter": {"value": obj, "type_url": f"type.googleapis.com/protocol.{type_}"}, "type": type_, } if method: @@ -371,25 +382,27 @@ def account_permission_update(self, owner: TAddress, perm: dict) -> "Transaction :param perm: Permission dict from :meth:`~tronpy.Tron.get_account_permission` """ - if 'owner' in perm: - for key in perm['owner']['keys']: - key['address'] = keys.to_hex_address(key['address']) - if 'actives' in perm: - for act in perm['actives']: - for key in act['keys']: - key['address'] = keys.to_hex_address(key['address']) - if perm.get('witness', None): - for key in perm['witness']['keys']: - key['address'] = keys.to_hex_address(key['address']) + if "owner" in perm: + for key in perm["owner"]["keys"]: + key["address"] = keys.to_hex_address(key["address"]) + if "actives" in perm: + for act in perm["actives"]: + for key in act["keys"]: + key["address"] = keys.to_hex_address(key["address"]) + if perm.get("witness", None): + for key in perm["witness"]["keys"]: + key["address"] = keys.to_hex_address(key["address"]) return self._build_transaction( - "AccountPermissionUpdateContract", dict(owner_address=keys.to_hex_address(owner), **perm), + "AccountPermissionUpdateContract", + dict(owner_address=keys.to_hex_address(owner), **perm), ) def account_update(self, owner: TAddress, name: str) -> "TransactionBuilder": """Update account name. An account can only set name once.""" return self._build_transaction( - "UpdateAccountContract", {"owner_address": keys.to_hex_address(owner), "account_name": name.encode().hex()}, + "UpdateAccountContract", + {"owner_address": keys.to_hex_address(owner), "account_name": name.encode().hex()}, ) def freeze_balance( @@ -412,9 +425,7 @@ def freeze_balance( payload["receiver_address"] = keys.to_hex_address(receiver) return self._build_transaction("FreezeBalanceContract", payload) - def unfreeze_balance( - self, owner: TAddress, resource: str = "ENERGY", receiver: TAddress = None - ) -> "TransactionBuilder": + def unfreeze_balance(self, owner: TAddress, resource: str = "ENERGY", receiver: TAddress = None) -> "TransactionBuilder": """Unfreeze balance to get TRX back. :param owner: @@ -459,7 +470,7 @@ def deploy_contract(self, owner: TAddress, contract: Contract) -> "TransactionBu return contract.deploy() -class Tron(object): +class Tron: """The TRON API Client. :param provider: An :class:`~tronpy.providers.HTTPProvider` object, can be configured to use private node @@ -490,11 +501,11 @@ def __init__(self, provider: HTTPProvider = None, *, network: str = "mainnet", c if conf is not None: self.conf = dict(DEFAULT_CONF, **conf) - if provider is not None and self.conf['timeout'] != DEFAULT_CONF['timeout']: + if provider is not None and self.conf["timeout"] != DEFAULT_CONF["timeout"]: raise ValueError("timeout value should be set in provider") if provider is None: - self.provider = HTTPProvider(conf_for_name(network), self.conf['timeout']) + self.provider = HTTPProvider(conf_for_name(network), self.conf["timeout"]) elif isinstance(provider, (HTTPProvider,)): self.provider = provider else: @@ -582,7 +593,16 @@ def get_zkey_from_sk(self, sk: str, d: str = None) -> dict: payment_address = ret["payment_address"] return dict( - sk=sk, ask=ask, nsk=nsk, ovk=ovk, ak=ak, nk=nk, ivk=ivk, d=d, pkD=pkD, payment_address=payment_address, + sk=sk, + ask=ask, + nsk=nsk, + ovk=ovk, + ak=ak, + nk=nk, + ivk=ivk, + d=d, + pkD=pkD, + payment_address=payment_address, ) # Account query @@ -590,9 +610,7 @@ def get_zkey_from_sk(self, sk: str, d: str = None) -> dict: def get_account(self, addr: TAddress) -> dict: """Get account info from an address.""" - ret = self.provider.make_request( - "wallet/getaccount", {"address": keys.to_base58check_address(addr), "visible": True} - ) + ret = self.provider.make_request("wallet/getaccount", {"address": keys.to_base58check_address(addr), "visible": True}) if ret: return ret else: @@ -602,7 +620,8 @@ def get_account_resource(self, addr: TAddress) -> dict: """Get resource info of an account.""" ret = self.provider.make_request( - "wallet/getaccountresource", {"address": keys.to_base58check_address(addr), "visible": True}, + "wallet/getaccountresource", + {"address": keys.to_base58check_address(addr), "visible": True}, ) if ret: return ret @@ -617,19 +636,17 @@ def get_account_balance(self, addr: TAddress) -> Decimal: def get_bandwidth(self, addr: TAddress) -> int: """Query the bandwidth of the account""" - ret = self.provider.make_request( - "wallet/getaccountnet", {"address": keys.to_base58check_address(addr)} - ) + ret = self.provider.make_request("wallet/getaccountnet", {"address": keys.to_base58check_address(addr)}) if ret: # (freeNetLimit - freeNetUsed) + (NetLimit - NetUsed) - return ret['freeNetLimit'] - ret.get('freeNetUsed', 0) + ret.get('NetLimit', 0) - ret.get('NetUsed', 0) + return ret["freeNetLimit"] - ret.get("freeNetUsed", 0) + ret.get("NetLimit", 0) - ret.get("NetUsed", 0) else: raise AddressNotFound("account not found on-chain") def get_account_asset_balances(self, addr: TAddress) -> dict: """Get all TRC10 token balances of an account.""" info = self.get_account(addr) - return {p['key']: p['value'] for p in info.get("assetV2", {}) if p['value'] > 0} + return {p["key"]: p["value"] for p in info.get("assetV2", {}) if p["value"] > 0} def get_account_asset_balance(self, addr: TAddress, token_id: Union[int, str]) -> int: """Get TRC10 token balance of an account. Result is in raw amount.""" @@ -724,9 +741,9 @@ def get_block(self, id_or_num: Union[None, str, int] = None, *, visible: bool = elif id_or_num is None: block = self.provider.make_request("wallet/getnowblock", {"visible": visible}) else: - raise TypeError("can not infer type of {}".format(id_or_num)) + raise TypeError(f"can not infer type of {id_or_num}") - if 'Error' in (block or {}): + if "Error" in (block or {}): raise BugInJavaTron(block) elif block: return block @@ -808,16 +825,17 @@ def get_asset(self, id: int = None, issuer: TAddress = None) -> dict: return self.provider.make_request("wallet/getassetissuebyid", {"value": id, "visible": True}) else: return self.provider.make_request( - "wallet/getassetissuebyaccount", {"address": keys.to_base58check_address(issuer), "visible": True}, + "wallet/getassetissuebyaccount", + {"address": keys.to_base58check_address(issuer), "visible": True}, ) def get_asset_from_name(self, name: str) -> dict: """Get asset info from its abbr name, might fail if there're duplicates.""" - assets = [asset for asset in self.list_assets() if asset['abbr'] == name] + assets = [asset for asset in self.list_assets() if asset["abbr"] == name] if assets: if len(assets) == 1: return assets[0] - raise ValueError("duplicated assets with the same name", [asset['id'] for asset in assets]) + raise ValueError("duplicated assets with the same name", [asset["id"] for asset in assets]) raise AssetNotFound def list_assets(self) -> list: @@ -854,7 +872,7 @@ def get_contract(self, addr: TAddress) -> Contract: cntr = Contract( addr=addr, - bytecode=info.get("bytecode", ''), + bytecode=info.get("bytecode", ""), name=info.get("name", ""), abi=info.get("abi", {}).get("entrys", []), origin_energy_limit=info.get("origin_energy_limit", 0), @@ -871,7 +889,11 @@ def get_contract_as_shielded_trc20(self, addr: TAddress) -> ShieldedTRC20: return ShieldedTRC20(contract) def trigger_const_smart_contract_function( - self, owner_address: TAddress, contract_address: TAddress, function_selector: str, parameter: str, + self, + owner_address: TAddress, + contract_address: TAddress, + function_selector: str, + parameter: str, ) -> str: ret = self.provider.make_request( "wallet/triggerconstantcontract", @@ -884,13 +906,13 @@ def trigger_const_smart_contract_function( }, ) self._handle_api_error(ret) - if 'message' in ret.get('result', {}): - msg = ret['result']['message'] - result = ret.get('constant_result', []) + if "message" in ret.get("result", {}): + msg = ret["result"]["message"] + result = ret.get("constant_result", []) try: if result and len(result[0]) > (4 + 32) * 2: - error_msg = tron_abi.decode_single('string', bytes.fromhex(result[0])[4 + 32 :]) - msg = "{}: {}".format(msg, error_msg) + error_msg = tron_abi.decode_single("string", bytes.fromhex(result[0])[4 + 32 :]) + msg = f"{msg}: {error_msg}" except Exception: pass raise TvmError(msg) @@ -901,8 +923,8 @@ def trigger_const_smart_contract_function( def broadcast(self, txn: Transaction) -> dict: payload = self.provider.make_request("wallet/broadcasttransaction", txn.to_json()) self._handle_api_error(payload) - if payload.get('txid') is None: - payload['txid'] = txn.txid + if payload.get("txid") is None: + payload["txid"] = txn.txid return payload def get_sign_weight(self, txn: Transaction) -> dict: