Repository: iexbase/tron-api-python Branch: master Commit: 2da3b705d0bb Files: 50 Total size: 190.1 KB Directory structure: gitextract_aju8mwnx/ ├── .bumpversion.cfg ├── .coveragerc ├── .gitignore ├── .pyup.yml ├── .travis.yml ├── LICENSE ├── README.rst ├── codecov.yml ├── docs/ │ ├── index.rst │ ├── overview.rst │ ├── quickstart.rst │ ├── releases.rst │ └── v2_migration.rst ├── example.py ├── examples/ │ ├── account.py │ ├── address-hex.py │ ├── amount.py │ ├── contract.py │ ├── custom-nodes.py │ ├── find-transaction.py │ ├── send-transaction.py │ └── sign.py ├── requirements-docs.txt ├── setup.py └── tronapi/ ├── __init__.py ├── common/ │ ├── __init__.py │ ├── abi.py │ ├── account.py │ ├── blocks.py │ ├── contracts.py │ ├── datastructures.py │ ├── datatypes.py │ ├── encoding.py │ ├── formatters.py │ ├── normalizers.py │ ├── threads.py │ ├── toolz/ │ │ └── __init__.py │ ├── transactions.py │ └── validation.py ├── constants.py ├── contract.py ├── exceptions.py ├── main.py ├── manager.py ├── module.py ├── providers/ │ ├── __init__.py │ ├── base.py │ └── http.py ├── transactionbuilder.py └── trx.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .bumpversion.cfg ================================================ [bumpversion] current_version = 3.1.5 commit = True tag = True parse = (?P\d+)\.(?P\d+)\.(?P\d+)(-(?P[^.]*)\.(?P\d+))? serialize = {major}.{minor}.{patch}-{stage}.{devnum} {major}.{minor}.{patch} [bumpversion:part:stage] optional_value = stable first_value = stable values = alpha beta stable [bumpversion:part:devnum] [bumpversion:file:setup.py] search = version='{current_version}', replace = version='{new_version}', ================================================ FILE: .coveragerc ================================================ [run] branch = True include = */tronapi/* ================================================ FILE: .gitignore ================================================ .idea # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ .pytest_cache/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # Environments .env .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ ================================================ FILE: .pyup.yml ================================================ # autogenerated pyup.io config file # see https://pyup.io/docs/configuration/ for all available options update: all # update schedule # default: empty # allowed: "every day", "every week", .. schedule: "every week" pin: False ================================================ FILE: .travis.yml ================================================ sudo: required language: python os: linux cache: pip dist: trusty python: - "3.6" - "3.7-dev" - "nightly" # currently points to 3.7-dev # command to install dependencies install: "pip install -U setuptools setuptools_scm codecov pandas" # command to run tests script: - python setup.py test - coverage run setup.py test after_success: - codecov ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2018 iEXBase Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.rst ================================================ =================== TRON API for Python =================== A Python API for interacting with the Tron (TRX) .. image:: https://img.shields.io/pypi/v/tronapi.svg :target: https://pypi.python.org/pypi/tronapi .. image:: https://img.shields.io/pypi/pyversions/tronapi.svg :target: https://pypi.python.org/pypi/tronapi .. image:: https://api.travis-ci.com/iexbase/tron-api-python.svg?branch=master :target: https://travis-ci.com/iexbase/tron-api-python .. image:: https://img.shields.io/github/issues/iexbase/tron-api-python.svg :target: https://github.com/iexbase/tron-api-python/issues .. image:: https://img.shields.io/github/issues-pr/iexbase/tron-api-python.svg :target: https://github.com/iexbase/tron-api-python/pulls .. image:: https://api.codacy.com/project/badge/Grade/8a5ae1e1cc834869b1094ea3b0d24f78 :alt: Codacy Badge :target: https://app.codacy.com/app/serderovsh/tron-api-python?utm_source=github.com&utm_medium=referral&utm_content=iexbase/tron-api-python&utm_campaign=Badge_Grade_Dashboard ------------ **A Command-Line Interface framework** You can install it in a system-wide location via pip: .. code-block:: bash sudo pip3 install tronapi Or install it locally using `virtualenv `__: .. code-block:: bash virtualenv -p /usr/bin/python3 ~/tronapi source ~/tronapi/bin/activate pip3 install tronapi ------------ Usage ===== Specify the API endpoints: Smart Contract -------------- .. code-block:: python from tronapi import Tron from solc import compile_source full_node = 'https://api.trongrid.io' solidity_node = 'https://api.trongrid.io' event_server = 'https://api.trongrid.io' tron = Tron(full_node=full_node, solidity_node=solidity_node, event_server=event_server) # or default (tron = Tron()) # Solidity source code contract_source_code = ''' pragma solidity ^0.4.25; contract Hello { string public message; function Hello(string initialMessage) public { message = initialMessage; } function setMessage(string newMessage) public { message = newMessage; } } ''' compiled_sol = compile_source(contract_source_code) contract_interface = compiled_sol[':Hello'] hello = tron.trx.contract( abi=contract_interface['abi'], bytecode=contract_interface['bin'] ) # Submit the transaction that deploys the contract tx = hello.deploy( fee_limit=10**9, call_value=0, consume_user_resource_percent=1 ) .. Base Example ------------ .. code-block:: python from tronapi import Tron logging.basicConfig(level=logging.DEBUG, format="%(asctime)s - %(levelname)s - %(message)s") logger = logging.getLogger() full_node = 'https://api.trongrid.io' solidity_node = 'https://api.trongrid.io' event_server = 'https://api.trongrid.io' tron = Tron(full_node=full_node, solidity_node=solidity_node, event_server=event_server) account = tron.create_account is_valid = bool(tron.trx.is_address(account.address.hex)) logger.debug('Generated account: ') logger.debug('- Private Key: ' + account.private_key) logger.debug('- Public Key: ' + account.public_key) logger.debug('- Address: ') logger.debug('-- Base58: ' + account.address.base58) logger.debug('-- Hex: ' + account.address.hex) logger.debug('-- isValid: ' + str(is_valid)) logger.debug('-----------') transaction = tron.trx.get_transaction('757a14cef293c69b1cf9b9d3d19c2e40a330c640b05c6ffa4d54609a9628758c') logger.debug('Transaction: ') logger.debug('- Hash: ' + transaction['txID']) logger.debug('- Transaction: ' + json.dumps(transaction, indent=2)) logger.debug('-----------') # Events event_result = tron.trx.get_event_result('TGEJj8eus46QMHPgWQe1FJ2ymBXRm96fn1', 0, 'Notify') logger.debug('Event result:') logger.debug('Contract Address: TGEJj8eus46QMHPgWQe1FJ2ymBXRm96fn1') logger.debug('Event Name: Notify') logger.debug('Block Number: 32162') logger.debug('- Events: ' + json.dumps(event_result, indent=2)) More samples and snippets are available at `examples `__. Documentation ============= Documentation is available at `docs `__. Donations ============= TRON: TRWBqiqoFZysoAeyR1J35ibuyc8EvhUAoY ================================================ FILE: codecov.yml ================================================ # -------------------------------------------------------------------------------------------- # Copyright (c) iEXBase. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- codecov: branch: master # the branch to show by default coverage: precision: 2 round: down range: "70...100" status: project: default: target: auto if_no_uploads: error patch: default: target: "80%" if_no_uploads: error ignore: # files and folders that will be removed during processing - "docs/*" - "examples/*" comment: # @stevepeak (from codecov.io) suggested we change 'suggestions' to 'uncovered' # in the following line. Thanks Steve! layout: "header, diff, changes, sunburst, uncovered" behavior: default ================================================ FILE: docs/index.rst ================================================ TronAPI ======= TronAPI is a python library for interacting with Tron Protocol. Contents -------- .. toctree:: :maxdepth: 1 quickstart overview node v2_migration releases ================================================ FILE: docs/overview.rst ================================================ Overview ======== .. contents:: :local: The common entrypoint for interacting with the Tron library is the ``Tron`` object. The tron object provides APIs for interacting with the tron blockchain, typically by connecting to a HTTP server. Providers --------- *Providers* are how tron connects to the blockchain. The TronAPI library comes with a the following built-in providers that should be suitable for most normal use cases. - ``HttpProvider`` for connecting to http and https based servers. The ``HttpProvider`` takes the full URI where the server can be found. For local development this would be something like ``http://localhost:8090``. .. code-block:: python >>> from tronapi import HttpProvider, Tron # Note that you should create only one HttpProvider per # process, as it recycles underlying TCP/IP network connections between # your process and Tron node >>> full_node = HttpProvider('http://localhost:8090') >>> solidity_node = HttpProvider('http://localhost:8090') >>> event_server = HttpProvider('http://localhost:8090') >>> tron = Tron(full_node, solidity_node, event_server) Base API -------- The ``Tron`` class exposes the following convenience APIs. .. _overview_type_conversions: Type Conversions ~~~~~~~~~~~~~~~~ .. py:method:: Tron.toHex(primitive=None, hexstr=None, text=None) Takes a variety of inputs and returns it in its hexadecimal representation. .. code-block:: python >>> Tron.toHex(0) '0x0' >>> Tron.toHex(1) '0x1' >>> Tron.toHex(0x0) '0x0' >>> Tron.toHex(0x000F) '0xf' >>> Tron.toHex(b'') '0x' >>> Tron.toHex(b'\x00\x0F') '0x000f' >>> Tron.toHex(False) '0x0' >>> Tron.toHex(True) '0x1' >>> Tron.toHex(hexstr='0x000F') '0x000f' >>> Tron.toHex(hexstr='000F') '0x000f' >>> Tron.toHex(text='') '0x' >>> Tron.toHex(text='cowmö') '0x636f776dc3b6' .. py:method:: Tron.toText(primitive=None, hexstr=None, text=None) Takes a variety of inputs and returns its string equivalent. Text gets decoded as UTF-8. .. code-block:: python >>> Tron.toText(0x636f776dc3b6) 'cowmö' >>> Tron.toText(b'cowm\xc3\xb6') 'cowmö' >>> Tron.toText(hexstr='0x636f776dc3b6') 'cowmö' >>> Tron.toText(hexstr='636f776dc3b6') 'cowmö' >>> Tron.toText(text='cowmö') 'cowmö' .. py:method:: Tron.toBytes(primitive=None, hexstr=None, text=None) Takes a variety of inputs and returns its bytes equivalent. Text gets encoded as UTF-8. .. code-block:: python >>> Tron.toBytes(0) b'\x00' >>> Tron.toBytes(0x000F) b'\x0f' >>> Tron.toBytes(b'') b'' >>> Tron.toBytes(b'\x00\x0F') b'\x00\x0f' >>> Tron.toBytes(False) b'\x00' >>> Tron.toBytes(True) b'\x01' >>> Tron.toBytes(hexstr='0x000F') b'\x00\x0f' >>> Tron.toBytes(hexstr='000F') b'\x00\x0f' >>> Tron.toBytes(text='') b'' >>> Tron.toBytes(text='cowmö') b'cowm\xc3\xb6' .. py:method:: Tron.toInt(primitive=None, hexstr=None, text=None) Takes a variety of inputs and returns its integer equivalent. .. code-block:: python >>> Tron.toInt(0) 0 >>> Tron.toInt(0x000F) 15 >>> Tron.toInt(b'\x00\x0F') 15 >>> Tron.toInt(False) 0 >>> Tron.toInt(True) 1 >>> Tron.toInt(hexstr='0x000F') 15 >>> Tron.toInt(hexstr='000F') 15 .. _overview_currency_conversions: Currency Conversions ~~~~~~~~~~~~~~~~~~~~~ .. py:method:: Tron.toSun(value) Returns the value in the denomination specified by the ``currency`` argument converted to sun. .. code-block:: python >>> tron.toSun(1) 1000000 .. py:method:: Tron.fromSun(value) Returns the value in wei converted to the given currency. The value is returned as a ``Decimal`` to ensure precision down to the wei. .. code-block:: python >>> tron.fromSun(1000000) Decimal('1') .. _overview_addresses: Addresses ~~~~~~~~~~~~~~~~ .. py:method:: Tron.isAddress(value) Returns ``True`` if the value is one of the recognized address formats. .. code-block:: python >>> tron.isAddress('TRWBqiqoFZysoAeyR1J35ibuyc8EvhUAoY') True .. _overview_hashing: Cryptographic Hashing ~~~~~~~~~~~~~~~~~~~~~ .. py:classmethod:: Tron.sha3(primitive=None, hexstr=None, text=None) Returns the Keccak SHA256 of the given value. Text is encoded to UTF-8 before computing the hash, just like Solidity. Any of the following are valid and equivalent: .. code-block:: python >>> Tron.sha3(0x747874) >>> Tron.sha3(b'\x74\x78\x74') >>> Tron.sha3(hexstr='0x747874') >>> Tron.sha3(hexstr='747874') >>> Tron.sha3(text='txt') HexBytes('0xd7278090a36507640ea6b7a0034b69b0d240766fa3f98e3722be93c613b29d2e') ================================================ FILE: docs/quickstart.rst ================================================ Quickstart ========== .. contents:: :local: .. NOTE:: All code starting with a ``$`` is meant to run on your terminal. All code starting with a ``>>>`` is meant to run in a python interpreter, like `ipython `_. Installation ------------ TronAPI can be installed (preferably in a :ref:`virtualenv `) using ``pip`` as follows: .. code-block:: shell $ pip install tronapi .. NOTE:: If you run into problems during installation, you might have a broken environment. See the troubleshooting guide to :ref:`setup_environment`. Installation from source can be done from the root of the project with the following command. .. code-block:: shell $ pip install . Using TronAPI ---------- To use the tron library you will need to initialize the :class:`~tronapi` class. .. code-block:: python >>> from tronapi import Tron >>> full_node = HttpProvider('https://api.trongrid.io') >>> solidity_node = HttpProvider('https://api.trongrid.io') >>> event_server = 'https://api.trongrid.io' >>> >>> tron = Tron(full_node, solidity_node, event_server) >>> tron.default_block = 'latest' Getting Blockchain Info ---------------------------------------- It's time to start using TronAPI for Python! Try getting all the information about the latest block. .. code-block:: python >>> tron.get_block('latest') >>> { "blockID": "00000000003a5bbda4aea15cb5d99230674463e9d5f2c0c647316839b25fd5b9", "block_header": { "raw_data": { "number": 3824573, "txTrieRoot": "31ee3e2ed28f843bf1d53495beece2f5b9c76480772f0106e17156fb0066c3a2", "witness_address": "41f70386347e689e6308e4172ed7319c49c0f66e0b", "parentHash": "00000000003a5bbc1e78e3144ad52f01a27b8f7acceb98d3ca09c1abea5cd32a", "version": 3, "timestamp": 1541425827000 }, "witness_signature": "fddc729f55c0ecc6f9cf4ab17cf818ddc0e85d2c21382ed6b1430adb1dcd13006c24ae0e08f16d29362452ec8869d29a28d57a85d6cec30ef60c2a37332fdb4d00" }, "transactions": [ ] } ================================================ FILE: docs/releases.rst ================================================ ================================================ FILE: docs/v2_migration.rst ================================================ Migrating your code from v1 to v2 ======================================= Changes to base API convenience methods --------------------------------------- Tron.toDecimal() ~~~~~~~~~~~~~~~~~ In v4 ``Tron.toDecimal()`` is renamed: :meth:`~Tron.toInt` for improved clarity. It does not return a :class:`decimal.Decimal`, it returns an :class:`int`. Removed Methods ~~~~~~~~~~~~~~~~~~ - ``Tron.toUtf8`` was removed for :meth:`~Tron.toText`. - ``Tron.fromUtf8`` was removed for :meth:`~Tron.toHex`. - ``Tron.toAscii`` was removed for :meth:`~Tron.toBytes`. - ``Tron.fromAscii`` was removed for :meth:`~Tron.toHex`. - ``Tron.fromDecimal`` was removed for :meth:`~Tron.toHex`. Provider Access ~~~~~~~~~~~~~~~~~ In v2, ``tron.currentProvider`` was removed, in favor of ``tron.providers``. Disambiguating String Inputs ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ There are a number of places where an arbitrary string input might be either a byte-string that has been hex-encoded, or unicode characters in text. These are named ``hexstr`` and ``text`` in TronAPI. You specify which kind of :class:`str` you have by using the appropriate keyword argument. See examples in :ref:`overview_type_conversions`. In v1, some methods accepted a :class:`str` as the first positional argument. In v2, you must pass strings as one of ``hexstr`` or ``text`` keyword arguments. Notable methods that no longer accept ambiguous strings: - :meth:`~Tron.sha3` - :meth:`~Tron.toBytes` ================================================ FILE: example.py ================================================ import json import logging from tronapi import Tron logging.basicConfig(level=logging.DEBUG, format="%(asctime)s - %(levelname)s - %(message)s") logger = logging.getLogger() full_node = 'https://api.trongrid.io' solidity_node = 'https://api.trongrid.io' event_server = 'https://api.trongrid.io/' private_key = 'da146374a75310b9666e834ee4ad0866d6f4035967bfc76217c5a495fff9f0d0' tron = Tron(full_node=full_node, solidity_node=solidity_node, event_server=event_server) account = tron.create_account is_valid = bool(tron.isAddress(account.address.hex)) logger.debug('Generated account: ') logger.debug('- Private Key: ' + account.private_key) logger.debug('- Public Key: ' + account.public_key) logger.debug('- Address: ') logger.debug('-- Base58: ' + account.address.base58) logger.debug('-- Hex: ' + account.address.hex) logger.debug('-- isValid: ' + str(is_valid)) logger.debug('-----------') current_block = tron.trx.get_current_block() logger.debug('Current block: ') logger.debug(json.dumps(current_block, indent=2)) logger.debug('-----------') previous_block = tron.trx.get_block(0) logger.debug('Previous block #52: ') logger.debug(json.dumps(previous_block, indent=2)) logger.debug('-----------') genesis_block_count = tron.trx.get_block_transaction_count('earliest') logger.debug('Genesis Block Transaction Count: ') logger.debug('Transactions:' + str(genesis_block_count)) logger.debug('-----------') transaction = tron.trx.get_transaction('757a14cef293c69b1cf9b9d3d19c2e40a330c640b05c6ffa4d54609a9628758c') logger.debug('Transaction: ') logger.debug('- Hash: ' + transaction['txID']) logger.debug('- Transaction: ' + json.dumps(transaction, indent=2)) logger.debug('-----------') account_info = tron.trx.get_account('TKLnCNY5EsLNCvCXQTCn1dtqvc6vHhJUyJ') logger.debug('Account information: ') logger.debug('- Address: TKLnCNY5EsLNCvCXQTCn1dtqvc6vHhJUyJ') logger.debug('- Account:' + json.dumps(account_info, indent=2)) logger.debug('-----------') balance = tron.trx.get_account('TKLnCNY5EsLNCvCXQTCn1dtqvc6vHhJUyJ') logger.debug('Account balance: ') logger.debug('- Address: TKLnCNY5EsLNCvCXQTCn1dtqvc6vHhJUyJ') logger.debug('- Account:' + json.dumps(balance, indent=2)) logger.debug('-----------') band_width = tron.trx.get_band_width('TKLnCNY5EsLNCvCXQTCn1dtqvc6vHhJUyJ') logger.debug('Account bandwidth: ') logger.debug('- Address: TKLnCNY5EsLNCvCXQTCn1dtqvc6vHhJUyJ') logger.debug('- Bandwidth:' + json.dumps(band_width, indent=2)) logger.debug('-----------') list_nodes = tron.trx.list_nodes() logger.debug('List of full nodes: ') logger.debug('- Node Count:' + str(len(list_nodes))) logger.debug('- Nodes:' + json.dumps(list_nodes, indent=2)) logger.debug('-----------') block_ids = tron.trx.get_block_range(30, 35) block = list(map(lambda x: {'id': x['block_header']['raw_data']['number'] or 0}, block_ids)) logger.debug('Block IDs between 30 and 35: ') logger.debug('- Block Range: [ 30, 35 ]') logger.debug('- Blocks IDs:' + json.dumps(block, indent=2)) logger.debug('-----------') # send = tron.send_trx('TGEJj8eus46QMHPgWQe1FJ2ymBXRm96fn1', 10) # logger.debug('Send TRX transaction: ') # logger.debug('- Result: ' + json.dumps(send, indent=2)) # logger.debug('-----------') event_result = tron.get_event_result('TGEJj8eus46QMHPgWQe1FJ2ymBXRm96fn1', 0, 'Notify') logger.debug('Event result:') logger.debug('Contract Address: TGEJj8eus46QMHPgWQe1FJ2ymBXRm96fn1') logger.debug('Event Name: Notify') logger.debug('Block Number: 32162') logger.debug('- Events: ' + json.dumps(event_result, indent=2)) event_by_transaction_id = tron.get_event_transaction_id('32d7efe5f70c044bcd831f21f911209a7abf4ed0d5934b2c1b804e108008cd43') logger.debug('Specific event result:') logger.debug('Transaction: 32d7efe5f70c044bcd831f21f911209a7abf4ed0d5934b2c1b804e108008cd43') logger.debug('- Events: ' + json.dumps(event_by_transaction_id, indent=2)) first_transaction = tron.trx.get_transaction_from_block(0, 0) logger.debug('First transaction from block 0') logger.debug('- Transaction: ' + json.dumps(first_transaction, indent=2)) ================================================ FILE: examples/account.py ================================================ import logging from tronapi import Tron logging.basicConfig(level=logging.DEBUG, format="%(asctime)s - %(levelname)s - %(message)s") logger = logging.getLogger() full_node = 'https://api.trongrid.io' solidity_node = 'https://api.trongrid.io' event_server = 'https://api.trongrid.io/' private_key = 'da146374a75310b9666e834ee4ad0866d6f4035967bfc76217c5a495fff9f0d0' tron = Tron(full_node=full_node, solidity_node=solidity_node, event_server=event_server) account = tron.create_account is_valid = bool(tron.isAddress(account.address.hex)) logger.debug('Generated account: ') logger.debug('- Private Key: ' + account.private_key) logger.debug('- Public Key: ' + account.public_key) logger.debug('- Address: ') logger.debug('-- Base58: ' + account.address.base58) logger.debug('-- Hex: ' + account.address.hex) logger.debug('-- isValid: ' + str(is_valid)) logger.debug('-----------') ================================================ FILE: examples/address-hex.py ================================================ from tronapi import Tron from tronapi import HttpProvider full_node = HttpProvider('https://api.trongrid.io') solidity_node = HttpProvider('https://api.trongrid.io') event_server = HttpProvider('https://api.trongrid.io') tron = Tron(full_node=full_node, solidity_node=solidity_node, event_server=event_server) tron.address.to_hex('TT67rPNwgmpeimvHUMVzFfKsjL9GZ1wGw8') # result: 41BBC8C05F1B09839E72DB044A6AA57E2A5D414A10 tron.address.from_hex('41BBC8C05F1B09839E72DB044A6AA57E2A5D414A10') # result: TT67rPNwgmpeimvHUMVzFfKsjL9GZ1wGw8 ================================================ FILE: examples/amount.py ================================================ from tronapi import Tron from tronapi import HttpProvider full_node = HttpProvider('https://api.trongrid.io') solidity_node = HttpProvider('https://api.trongrid.io') event_server = HttpProvider('https://api.trongrid.io') tron = Tron(full_node=full_node, solidity_node=solidity_node, event_server=event_server) tron.toSun(1) # result: 1000000 tron.fromSun(1000000) # result: 1 ================================================ FILE: examples/contract.py ================================================ from tronapi import Tron, HttpProvider from solc import compile_source full_node = HttpProvider('https://api.trongrid.io') solidity_node = HttpProvider('https://api.trongrid.io') event_server = HttpProvider('https://api.trongrid.io') tron = Tron(full_node=full_node, solidity_node=solidity_node, event_server=event_server) # Solidity source code contract_source_code = ''' pragma solidity ^0.4.25; contract Hello { string public message; function Hello(string initialMessage) public { message = initialMessage; } function setMessage(string newMessage) public { message = newMessage; } } ''' compiled_sol = compile_source(contract_source_code) contract_interface = compiled_sol[':Hello'] hello = tron.trx.contract( abi=contract_interface['abi'], bytecode=contract_interface['bin'] ) # Submit the transaction that deploys the contract tx_data = hello.deploy( fee_limit=10**9, call_value=0, consume_user_resource_percent=1 ) sign = tron.trx.sign(tx_data) result = tron.trx.broadcast(sign) ================================================ FILE: examples/custom-nodes.py ================================================ from tronapi import Tron from tronapi import HttpProvider full_node = HttpProvider('https://api.trongrid.io') solidity_node = HttpProvider('https://api.trongrid.io') event_server = HttpProvider('https://api.trongrid.io') # option 1 tron = Tron(full_node=full_node, solidity_node=solidity_node, event_server=event_server) # option 2 tron_v2 = Tron() # option 3 tron_v3 = Tron( default_address='TRWBqiqoFZysoAeyR1J35ibuyc8EvhUAoY', private_key='...' ) ================================================ FILE: examples/find-transaction.py ================================================ from tronapi import Tron from tronapi import HttpProvider full_node = HttpProvider('https://api.trongrid.io') solidity_node = HttpProvider('https://api.trongrid.io') event_server = HttpProvider('https://api.trongrid.io') tron = Tron(full_node=full_node, solidity_node=solidity_node, event_server=event_server) result = tron.trx.get_transaction('TxId') ================================================ FILE: examples/send-transaction.py ================================================ from tronapi import Tron from tronapi import HttpProvider full_node = HttpProvider('https://api.trongrid.io') solidity_node = HttpProvider('https://api.trongrid.io') event_server = HttpProvider('https://api.trongrid.io') tron = Tron(full_node=full_node, solidity_node=solidity_node, event_server=event_server) tron.private_key = 'private_key' tron.default_address = 'default address' # added message send = tron.trx.send_transaction('to', 1) print(send) ================================================ FILE: examples/sign.py ================================================ from tronapi import Tron from tronapi import HttpProvider full_node = HttpProvider('https://api.trongrid.io') solidity_node = HttpProvider('https://api.trongrid.io') event_server = HttpProvider('https://api.trongrid.io') tron = Tron(full_node=full_node, solidity_node=solidity_node, event_server=event_server) tron.private_key = 'private_key' tron.default_address = 'default address' # create transaction create_tx = tron.transaction_builder.send_transaction('to', 1, 'from') # offline sign offline_sign = tron.trx.sign(create_tx) # online sign (Not recommended) online_sign = tron.trx.online_sign(create_tx) ================================================ FILE: requirements-docs.txt ================================================ .[docs] ================================================ FILE: setup.py ================================================ #!/usr/bin/env python # -------------------------------------------------------------------- # Copyright (c) iEXBase. All rights reserved. # Licensed under the MIT License. # See License.txt in the project root for license information. # -------------------------------------------------------------------- """ setup ===== Tron: A Python API for interacting with Tron (TRX) :copyright: © 2018 by the iEXBase. :license: MIT License """ import os import platform from setuptools import ( find_packages, setup, ) py_version = platform.python_version() PACKAGE_VERSION = '3.1.5' EXTRAS_REQUIRE = { 'tester': [ 'coverage', 'pep8', 'pyflakes', 'pylint', 'pytest-cov' ], 'docs': [ "mock", "sphinx-better-theme>=0.1.4", "click>=5.1", "configparser==3.5.0", "contextlib2>=0.5.4", "py-solc>=0.4.0", "pytest>=2.7.2", "sphinx", "sphinx_rtd_theme>=0.1.9", "toposort>=1.4", "urllib3", "tronapi", "wheel >= 0.31.0" ], 'dev': [ "bumpversion", "flaky>=3.3.0", "hypothesis>=3.31.2", "pytest>=3.5.0,<4", "pytest-mock==1.*", "pytest-pythonpath>=0.3", "pytest-watch==4.*", "pytest-xdist==1.*", "setuptools>=38.6.0", "tox>=1.8.0", "twine >= 1.11.0", "tqdm", "when-changed" ] } EXTRAS_REQUIRE['dev'] = ( EXTRAS_REQUIRE['tester'] + EXTRAS_REQUIRE['docs'] + EXTRAS_REQUIRE['dev'] ) install_requires = [ "toolz>=0.9.0,<1.0.0;implementation_name=='pypy'", "cytoolz>=0.9.0,<1.0.0;implementation_name=='cpython'", "eth-abi>=2.0.0b6,<3.0.0", "eth-account==0.4.0", "eth-utils>=1.3.0,<2.0.0", "eth-hash[pycryptodome]>=0.2.0,<1.0.0", "trx-utils", "hexbytes>=0.1.0,<1.0.0", "requests>=2.16.0,<3.0.0", "base58", "ecdsa", 'attrdict', ] this_dir = os.path.dirname(__file__) readme_filename = os.path.join(this_dir, 'README.rst') with open(readme_filename) as f: PACKAGE_LONG_DESCRIPTION = f.read() setup( name='tronapi', version=PACKAGE_VERSION, description='A Python API for interacting with Tron (TRX)', long_description=PACKAGE_LONG_DESCRIPTION, long_description_content_type='text/x-rst', keywords='tron tron-api tron-api-python iexbase', url='https://github.com/iexbase/tron-api-python', author='Shamsudin Serderov', author_email='steein.shamsudin@gmail.com', license='MIT License', zip_safe=False, python_requires='>=3.6,<4', classifiers=[ 'Development Status :: 3 - Alpha', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Natural Language :: English', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', ], packages=find_packages(exclude=['examples']), include_package_data=True, install_requires=install_requires, tests_require=EXTRAS_REQUIRE['tester'], extras_require=EXTRAS_REQUIRE, ) ================================================ FILE: tronapi/__init__.py ================================================ # -------------------------------------------------------------------- # Copyright (c) iEXBase. All rights reserved. # Licensed under the MIT License. # See License.txt in the project root for license information. # -------------------------------------------------------------------- import sys import pkg_resources from eth_account import Account # noqa: E402 from tronapi.providers.http import HttpProvider # noqa: E402 from tronapi.main import Tron # noqa: E402 if sys.version_info < (3, 5): raise EnvironmentError("Python 3.5 or above is required") __version__ = pkg_resources.get_distribution("tronapi").version __all__ = [ '__version__', 'HttpProvider', 'Account', 'Tron', ] ================================================ FILE: tronapi/common/__init__.py ================================================ ================================================ FILE: tronapi/common/abi.py ================================================ # -------------------------------------------------------------------- # Copyright (c) iEXBase. All rights reserved. # Licensed under the MIT License. # See License.txt in the project root for license information. # -------------------------------------------------------------------- import binascii import itertools import re from collections import ( namedtuple, ) from eth_abi import ( encoding, decoding ) from eth_abi.codec import ABICodec from eth_abi.registry import ( BaseEquals, registry as default_registry, ) from eth_utils import to_tuple from trx_utils import ( decode_hex, is_bytes, is_text, to_text ) from tronapi.common.formatters import recursive_map from tronapi.exceptions import FallbackNotFound from tronapi.common.toolz import ( curry, partial, pipe, ) DYNAMIC_TYPES = ['bytes', 'string'] INT_SIZES = range(8, 257, 8) BYTES_SIZES = range(1, 33) UINT_TYPES = ['uint{0}'.format(i) for i in INT_SIZES] INT_TYPES = ['int{0}'.format(i) for i in INT_SIZES] BYTES_TYPES = ['bytes{0}'.format(i) for i in BYTES_SIZES] + ['bytes32.byte'] STATIC_TYPES = list(itertools.chain( ['address', 'bool'], UINT_TYPES, INT_TYPES, BYTES_TYPES, )) BASE_TYPE_REGEX = '|'.join(( _type + '(?![a-z0-9])' for _type in itertools.chain(STATIC_TYPES, DYNAMIC_TYPES) )) SUB_TYPE_REGEX = ( r'\[' '[0-9]*' r'\]' ) TYPE_REGEX = ( '^' '(?:{base_type})' '(?:(?:{sub_type})*)?' '$' ).format( base_type=BASE_TYPE_REGEX, sub_type=SUB_TYPE_REGEX, ) NAME_REGEX = ( '[a-zA-Z_]' '[a-zA-Z0-9_]*' ) ENUM_REGEX = ( '^' '{lib_name}' r'\.' '{enum_name}' '$' ).format(lib_name=NAME_REGEX, enum_name=NAME_REGEX) END_BRACKETS_OF_ARRAY_TYPE_REGEX = r"\[[^]]*\]$" NAME_REGEX = ( '[a-zA-Z_]' '[a-zA-Z0-9_]*' ) ARRAY_REGEX = ( "^" "[a-zA-Z0-9_]+" "({sub_type})+" "$" ).format(sub_type=SUB_TYPE_REGEX) def filter_by_argument_name(argument_names, contract_abi): return [ abi for abi in contract_abi if set(argument_names).intersection( get_abi_input_names(abi) ) == set(argument_names) ] try: from eth_abi.abi import ( process_type, collapse_type, ) except ImportError: from eth_abi.grammar import ( parse as parse_type_string, normalize as normalize_type_string, TupleType, ) def process_type(type_str): normalized_type_str = normalize_type_string(type_str) abi_type = parse_type_string(normalized_type_str) if isinstance(abi_type, TupleType): type_str_repr = repr(type_str) if type_str != normalized_type_str: type_str_repr = '{} (normalized to {})'.format( type_str_repr, repr(normalized_type_str), ) raise ValueError( "Cannot process type {}: tuple types not supported".format( type_str_repr, ) ) abi_type.validate() sub = abi_type.sub if isinstance(sub, tuple): sub = 'x'.join(map(str, sub)) elif isinstance(sub, int): sub = str(sub) else: sub = '' arrlist = abi_type.arrlist if isinstance(arrlist, tuple): arrlist = list(map(list, arrlist)) else: arrlist = [] return abi_type.base, sub, arrlist def collapse_type(base, sub, arrlist): return base + str(sub) + ''.join(map(repr, arrlist)) def filter_by_type(_type, contract_abi): return [abi for abi in contract_abi if abi['type'] == _type] def filter_by_name(name, contract_abi): return [ abi for abi in contract_abi if ( abi['type'] not in ('fallback', 'constructor') and abi['name'] == name ) ] def get_abi_input_types(abi): if 'inputs' not in abi and abi['type'] == 'fallback': return [] else: return [arg['type'] for arg in abi['inputs']] def get_abi_output_types(abi): if abi['type'] == 'fallback': return [] else: return [arg['type'] for arg in abi['outputs']] def get_abi_input_names(abi): if 'inputs' not in abi and abi['type'] == 'fallback': return [] else: return [arg['name'] for arg in abi['inputs']] def length_of_array_type(abi_type): if not is_array_type(abi_type): raise ValueError( "Cannot parse length of nonarray abi-type: {0}".format(abi_type) ) inner_brackets = re.search(END_BRACKETS_OF_ARRAY_TYPE_REGEX, abi_type).group(0).strip("[]") if not inner_brackets: return None else: return int(inner_brackets) def get_fallback_func_abi(contract_abi): fallback_abis = filter_by_type('fallback', contract_abi) if fallback_abis: return fallback_abis[0] else: raise FallbackNotFound("No fallback function was found in the contract ABI.") def fallback_func_abi_exists(contract_abi): return filter_by_type('fallback', contract_abi) def get_constructor_abi(contract_abi): candidates = [ abi for abi in contract_abi if abi['type'] == 'constructor' ] if len(candidates) == 1: return candidates[0] elif len(candidates) == 0: return None elif len(candidates) > 1: raise ValueError("Found multiple constructors.") def is_recognized_type(abi_type): return bool(re.match(TYPE_REGEX, abi_type)) def is_bool_type(abi_type): return abi_type == 'bool' def is_uint_type(abi_type): return abi_type in UINT_TYPES def is_int_type(abi_type): return abi_type in INT_TYPES def is_address_type(abi_type): return abi_type == 'address' def is_bytes_type(abi_type): return abi_type in BYTES_TYPES + ['bytes'] def is_string_type(abi_type): return abi_type == 'string' @curry def is_length(target_length, value): return len(value) == target_length def size_of_type(abi_type): """ Returns size in bits of abi_type """ if 'string' in abi_type: return None if 'byte' in abi_type: return None if '[' in abi_type: return None if abi_type == 'bool': return 8 if abi_type == 'address': return 160 return int(re.sub(r"\D", "", abi_type)) def is_array_type(abi_type): return bool(re.match(ARRAY_REGEX, abi_type)) def sub_type_of_array_type(abi_type): if not is_array_type(abi_type): raise ValueError( "Cannot parse subtype of nonarray abi-type: {0}".format(abi_type) ) return re.sub(END_BRACKETS_OF_ARRAY_TYPE_REGEX, '', abi_type, 1) def is_probably_enum(abi_type): return bool(re.match(ENUM_REGEX, abi_type)) @to_tuple def normalize_event_input_types(abi_args): for arg in abi_args: if is_recognized_type(arg['type']): yield arg elif is_probably_enum(arg['type']): yield {k: 'uint8' if k == 'type' else v for k, v in arg.items()} else: yield arg def abi_to_signature(abi): function_signature = "{fn_name}({fn_input_types})".format( fn_name=abi['name'], fn_input_types=','.join([ arg['type'] for arg in normalize_event_input_types(abi.get('inputs', [])) ]), ) return function_signature def filter_by_argument_count(num_arguments, contract_abi): return [ abi for abi in contract_abi if len(abi['inputs']) == num_arguments ] def filter_by_encodability(args, kwargs, contract_abi): return [ function_abi for function_abi in contract_abi if check_if_arguments_can_be_encoded(function_abi, args, kwargs) ] class AcceptsHexStrMixin: def validate_value(self, value): if is_text(value): try: value = decode_hex(value) except binascii.Error: self.invalidate_value( value, msg='invalid hex string', ) super().validate_value(value) class ByteStringEncoder(AcceptsHexStrMixin, encoding.ByteStringEncoder): pass class BytesEncoder(AcceptsHexStrMixin, encoding.BytesEncoder): pass class TextStringEncoder(encoding.TextStringEncoder): @classmethod def validate_value(cls, value): if is_bytes(value): try: value = to_text(value) except UnicodeDecodeError: cls.invalidate_value( value, msg='not decodable as unicode string', ) super().validate_value(value) # We make a copy here just to make sure that eth-abi's default registry is not # affected by our custom encoder subclasses registry = default_registry.copy() registry.unregister('address') registry.unregister('bytes') registry.unregister('bytes') registry.unregister('string') registry.register( BaseEquals('bytes', with_sub=True), BytesEncoder, decoding.BytesDecoder, label='bytes', ) registry.register( BaseEquals('bytes', with_sub=False), ByteStringEncoder, decoding.ByteStringDecoder, label='bytes', ) registry.register( BaseEquals('string'), TextStringEncoder, decoding.StringDecoder, label='string', ) codec = ABICodec(registry) is_encodable = codec.is_encodable def check_if_arguments_can_be_encoded(function_abi, args, kwargs): try: arguments = merge_args_and_kwargs(function_abi, args, kwargs) except TypeError: return False if len(function_abi.get('inputs', [])) != len(arguments): return False types = get_abi_input_types(function_abi) return all( is_encodable(_type, arg) for _type, arg in zip(types, arguments) ) def merge_args_and_kwargs(function_abi, args, kwargs): if len(args) + len(kwargs) != len(function_abi.get('inputs', [])): raise TypeError( "Incorrect argument count. Expected '{0}'. Got '{1}'".format( len(function_abi['inputs']), len(args) + len(kwargs), ) ) if not kwargs: return args args_as_kwargs = { arg_abi['name']: arg for arg_abi, arg in zip(function_abi['inputs'], args) } duplicate_keys = set(args_as_kwargs).intersection(kwargs.keys()) if duplicate_keys: raise TypeError( "{fn_name}() got multiple values for argument(s) '{dups}'".format( fn_name=function_abi['name'], dups=', '.join(duplicate_keys), ) ) sorted_arg_names = [arg_abi['name'] for arg_abi in function_abi['inputs']] unknown_kwargs = {key for key in kwargs.keys() if key not in sorted_arg_names} if unknown_kwargs: if function_abi.get('name'): raise TypeError( "{fn_name}() got unexpected keyword argument(s) '{dups}'".format( fn_name=function_abi.get('name'), dups=', '.join(unknown_kwargs), ) ) # show type instead of name in the error message incase key 'name' is missing. raise TypeError( "Type: '{_type}' got unexpected keyword argument(s) '{dups}'".format( _type=function_abi.get('type'), dups=', '.join(unknown_kwargs), ) ) sorted_args = list(zip( *sorted( itertools.chain(kwargs.items(), args_as_kwargs.items()), key=lambda kv: sorted_arg_names.index(kv[0]) ) )) if sorted_args: return sorted_args[1] else: return tuple() def abi_sub_tree(data_type, data_value): if data_type is None: return ABITypedData([None, data_value]) try: base, sub, arrlist = data_type except ValueError: base, sub, arrlist = process_type(data_type) collapsed = collapse_type(base, sub, arrlist) if arrlist: sub_type = (base, sub, arrlist[:-1]) return ABITypedData([ collapsed, [ abi_sub_tree(sub_type, sub_value) for sub_value in data_value ], ]) else: return ABITypedData([collapsed, data_value]) @curry def map_abi_data(normalizers, types, data): """ This function will apply normalizers to your data, in the context of the relevant types. Each normalizer is in the format: def normalizer(datatype, data): # Conditionally modify data return (datatype, data) Where datatype is a valid ABI type string, like "uint". In case of an array, like "bool[2]", normalizer will receive `data` as an iterable of typed data, like `[("bool", True), ("bool", False)]`. Internals --- This is accomplished by: 1. Decorating the data tree with types 2. Recursively mapping each of the normalizers to the data 3. Stripping the types back out of the tree """ pipeline = itertools.chain( [abi_data_tree(types)], map(data_tree_map, normalizers), [partial(recursive_map, strip_abi_type)], ) return pipe(data, *pipeline) @curry def abi_data_tree(types, data): """Decorate the data tree with pairs of (type, data). The pair tuple is actually an ABITypedData, but can be accessed as a tuple. Examples: >>> abi_data_tree(types=["bool[2]", "uint"], data=[[True, False], 0]) Returns: [("bool[2]", [("bool", True), ("bool", False)]), ("uint256", 0)] """ return [ abi_sub_tree(data_type, data_value) for data_type, data_value in zip(types, data) ] @curry def data_tree_map(func, data_tree): """ Map func to every ABITypedData element in the tree. func will receive two args: abi_type, and data """ def map_to_typed_data(elements): if isinstance(elements, ABITypedData) and elements.abi_type is not None: return ABITypedData(func(*elements)) else: return elements return recursive_map(map_to_typed_data, data_tree) class ABITypedData(namedtuple('ABITypedData', 'abi_type, data')): """ This class marks data as having a certain ABI-type. >>> a1 = ABITypedData(['address', addr1]) >>> a2 = ABITypedData(['address', addr2]) >>> addrs = ABITypedData(['address[]', [a1, a2]) You can access the fields using tuple() interface, or with attributes: >>> assert a1.abi_type == a1[0] >>> assert a1.data == a1[1] Unlike a typical `namedtuple`, you initialize with a single positional argument that is iterable, to match the init interface of all other relevant collections. """ def __new__(cls, iterable): return super().__new__(cls, *iterable) def strip_abi_type(elements): if isinstance(elements, ABITypedData): return elements.data else: return elements ================================================ FILE: tronapi/common/account.py ================================================ # -------------------------------------------------------------------- # Copyright (c) iEXBase. All rights reserved. # Licensed under the MIT License. # See License.txt in the project root for license information. # -------------------------------------------------------------------- import codecs from binascii import unhexlify import base58 import ecdsa from eth_keys import KeyAPI from eth_account import Account as ETHAccount from trx_utils import is_hex, is_bytes from tronapi.common.datastructures import AttributeDict class Account: @staticmethod def create(): generate_key = ecdsa.SigningKey.generate(curve=ecdsa.SECP256k1) return PrivateKey(generate_key.to_string().hex()) @staticmethod def sign_hash(message_hash, private_key): if not is_hex(message_hash): raise ValueError('Invalid message_hash provided') return ETHAccount.signHash(message_hash, private_key) @staticmethod def recover_hash(message_hash, signature): if not is_hex(message_hash): raise ValueError('Invalid message_hash provided') return ETHAccount.recoverHash(message_hash, signature=signature) class Address(object): @staticmethod def from_hex(address): """Helper function that will convert a generic value from hex""" if not is_hex(address): return address return base58.b58encode_check(bytes.fromhex(address)) @staticmethod def to_hex(address): """Helper function that will convert a generic value to hex""" if is_hex(address): return address.lower().replace('0x', '41', 2) return base58.b58decode_check(address).hex().upper() @staticmethod def from_private_key(private_key): return PrivateKey(private_key).address class PrivateKey(object): def __init__(self, private_key): """Work with private key. Getting: PublicKey, PublicToAddress Example::: PrivateKey("4d1bc37b069b9f2e975c37770b7c87185dc3a10454e3ea024ce1fce8f3eb78bf") """ _private = unhexlify(bytes(private_key, encoding='utf8')) self._key = KeyAPI.PrivateKey(_private) _length = len(self._key) # Key length must not exceed 64 length if _length < 64: raise ValueError('Key length must not exceed 64 length') @property def private_key(self): _raw_key = self._key.to_bytes() return codecs.decode(codecs.encode(_raw_key, 'hex'), 'ascii') @property def public_key(self) -> str: public_key = self._key.public_key return '04' + str(public_key)[2:] @property def address(self): public_key = self._key.public_key address = '41' + public_key.to_address()[2:] to_base58 = base58.b58encode_check(bytes.fromhex(address)) # If bytecode then convert to string if is_bytes(to_base58): to_base58 = to_base58.decode() return AttributeDict({ 'hex': address, 'base58': to_base58 }) def __str__(self): return self.private_key def __bytes__(self): return self._key.to_bytes() ================================================ FILE: tronapi/common/blocks.py ================================================ # -------------------------------------------------------------------------------------------- # Copyright (c) iEXBase. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- from trx_utils import ( is_string, remove_0x_prefix, is_hex, is_integer ) def is_hex_encoded_block_hash(value): if not is_string(value): return False return len(remove_0x_prefix(value)) == 64 and is_hex(value) def is_hex_encoded_block_number(value): if not is_string(value): return False elif is_hex_encoded_block_hash(value): return False try: value_as_int = int(value, 16) except ValueError: return False return 0 <= value_as_int < 2**256 def select_method_for_block(value, if_hash, if_number): if isinstance(value, bytes): return if_hash elif is_hex_encoded_block_hash(value): return if_hash elif is_integer(value) and (0 <= value < 2**256): return if_number elif is_hex_encoded_block_number(value): return if_number else: raise ValueError( "Value did not match any of the recognized block identifiers: {0}".format(value) ) ================================================ FILE: tronapi/common/contracts.py ================================================ # -------------------------------------------------------------------- # Copyright (c) iEXBase. All rights reserved. # Licensed under the MIT License. # See License.txt in the project root for license information. # -------------------------------------------------------------------- import functools from hexbytes import HexBytes from eth_utils import ( to_hex, function_abi_to_4byte_selector ) from trx_utils import is_text, encode_hex from tronapi.common.abi import ( filter_by_name, filter_by_encodability, filter_by_argument_count, get_fallback_func_abi, abi_to_signature, get_abi_input_types, check_if_arguments_can_be_encoded, map_abi_data, merge_args_and_kwargs ) from tronapi.common.normalizers import ( abi_address_to_hex, abi_bytes_to_bytes, abi_string_to_text ) from tronapi.common.toolz import ( pipe, valmap, ) from eth_abi import ( encode_abi as eth_abi_encode_abi, ) from eth_abi.exceptions import ( EncodingError, ) class FallbackFn: pass def find_matching_fn_abi(abi, fn_identifier=None, args=None, kwargs=None): args = args or tuple() kwargs = kwargs or dict() filters = [] num_arguments = len(args) + len(kwargs) diagnosis = None if fn_identifier is FallbackFn: return get_fallback_func_abi(abi) if not is_text(fn_identifier): raise TypeError("Unsupported function identifier") name_filter = functools.partial(filter_by_name, fn_identifier) arg_count_filter = functools.partial(filter_by_argument_count, num_arguments) encoding_filter = functools.partial(filter_by_encodability, args, kwargs) filters.extend([ name_filter, arg_count_filter, encoding_filter, ]) function_candidates = pipe(abi, *filters) if len(function_candidates) == 1: return function_candidates[0] else: matching_identifiers = name_filter(abi) matching_function_signatures = [abi_to_signature(func) for func in matching_identifiers] arg_count_matches = len(arg_count_filter(matching_identifiers)) encoding_matches = len(encoding_filter(matching_identifiers)) if arg_count_matches == 0: diagnosis = "\nFunction invocation failed due to improper number of arguments." elif encoding_matches == 0: diagnosis = "\nFunction invocation failed due to no matching argument types." elif encoding_matches > 1: diagnosis = ( "\nAmbiguous argument encoding. " "Provided arguments can be encoded to multiple functions matching this call." ) message = ( "\nCould not identify the intended function with name `{name}`, " "positional argument(s) of type `{arg_types}` and " "keyword argument(s) of type `{kwarg_types}`." "\nFound {num_candidates} function(s) with the name `{name}`: {candidates}" "{diagnosis}" ).format( name=fn_identifier, arg_types=tuple(map(type, args)), kwarg_types=valmap(type, kwargs), num_candidates=len(matching_identifiers), candidates=matching_function_signatures, diagnosis=diagnosis, ) raise ValueError(message) def encode_abi(tron, abi, arguments, data=None): argument_types = get_abi_input_types(abi) if not check_if_arguments_can_be_encoded(abi, arguments, {}): raise TypeError( "One or more arguments could not be encoded to the necessary " "ABI type. Expected types are: {0}".format( ', '.join(argument_types), ) ) try: normalizers = [ abi_address_to_hex, abi_bytes_to_bytes, abi_string_to_text, ] normalized_arguments = map_abi_data( normalizers, argument_types, arguments, ) encoded_arguments = eth_abi_encode_abi( argument_types, normalized_arguments, ) except EncodingError as e: raise TypeError( "One or more arguments could not be encoded to the necessary " "ABI type: {0}".format(str(e)) ) if data: return to_hex(HexBytes(data) + encoded_arguments) else: return encode_hex(encoded_arguments) def get_function_info(fn_name, contract_abi=None, fn_abi=None, args=None, kwargs=None): if args is None: args = tuple() if kwargs is None: kwargs = {} if fn_abi is None: fn_abi = find_matching_fn_abi(contract_abi, fn_name, args, kwargs) fn_selector = encode_hex(function_abi_to_4byte_selector(fn_abi)) fn_arguments = merge_args_and_kwargs(fn_abi, args, kwargs) return fn_abi, fn_selector, fn_arguments ================================================ FILE: tronapi/common/datastructures.py ================================================ from collections import ( Hashable, Mapping, MutableMapping, OrderedDict, Sequence, ) from trx_utils import ( is_integer, ) from tronapi.common.formatters import ( recursive_map, ) # Hashable must be immutable: # "the implementation of hashable collections requires that a key's hash value is immutable" # https://docs.python.org/3/reference/datamodel.html#object.__hash__ class ReadableAttributeDict(Mapping): """ The read attributes for the AttributeDict types """ def __init__(self, dictionary, *args, **kwargs): self.__dict__ = dict(dictionary) self.__dict__.update(dict(*args, **kwargs)) def __getitem__(self, key): return self.__dict__[key] def __iter__(self): return iter(self.__dict__) def __len__(self): return len(self.__dict__) def __repr__(self): return self.__class__.__name__ + "(%r)" % self.__dict__ def _repr_pretty_(self, builder, cycle): """ Custom pretty output for the IPython console """ builder.text(self.__class__.__name__ + "(") if cycle: builder.text("") else: builder.pretty(self.__dict__) builder.text(")") @classmethod def _apply_if_mapping(cls, value): if isinstance(value, Mapping): return cls(value) else: return value @classmethod def recursive(cls, value): return recursive_map(cls._apply_if_mapping, value) class MutableAttributeDict(MutableMapping, ReadableAttributeDict): def __setitem__(self, key, val): self.__dict__[key] = val def __delitem__(self, key): del self.__dict__[key] class AttributeDict(ReadableAttributeDict, Hashable): """ This provides superficial immutability, someone could hack around it """ def __setattr__(self, attr, val): if attr == '__dict__': super().__setattr__(attr, val) else: raise TypeError('This data is immutable -- create a copy instead of modifying') def __delattr__(self, key): raise TypeError('This data is immutable -- create a copy instead of modifying') def __hash__(self): return hash(tuple(sorted(self.items()))) def __eq__(self, other): if isinstance(other, Mapping): return self.__dict__ == dict(other) else: return False class NamedElementOnion(Mapping): """ Add layers to an onion-shaped structure. Optionally, inject to a specific layer. This structure is iterable, where the outermost layer is first, and innermost is last. """ def __init__(self, init_elements, valid_element=callable): self._queue = OrderedDict() for element in reversed(init_elements): if valid_element(element): self.add(element) else: self.add(*element) def add(self, element, name=None): if name is None: name = element if name in self._queue: if name is element: raise ValueError("You can't add the same un-named instance twice") else: raise ValueError("You can't add the same name again, use replace instead") self._queue[name] = element def inject(self, element, name=None, layer=None): """ Inject a named element to an arbitrary layer in the onion. The current implementation only supports insertion at the innermost layer, or at the outermost layer. Note that inserting to the outermost is equivalent to calling :meth:`add` . """ if not is_integer(layer): raise TypeError("The layer for insertion must be an int.") elif layer != 0 and layer != len(self._queue): raise NotImplementedError( "You can only insert to the beginning or end of a %s, currently. " "You tried to insert to %d, but only 0 and %d are permitted. " % ( type(self), layer, len(self._queue), ) ) self.add(element, name=name) if layer == 0: if name is None: name = element self._queue.move_to_end(name, last=False) elif layer == len(self._queue): return else: raise AssertionError("Impossible to reach: earlier validation raises an error") def clear(self): self._queue.clear() def replace(self, old, new): if old not in self._queue: raise ValueError("You can't replace unless one already exists, use add instead") to_be_replaced = self._queue[old] if to_be_replaced is old: # re-insert with new name in old slot self._replace_with_new_name(old, new) else: self._queue[old] = new return to_be_replaced def remove(self, old): if old not in self._queue: raise ValueError("You can only remove something that has been added") del self._queue[old] def _replace_with_new_name(self, old, new): self._queue[new] = new found_old = False for key in list(self._queue.keys()): if not found_old: if key == old: found_old = True continue elif key != new: self._queue.move_to_end(key) del self._queue[old] def __iter__(self): elements = self._queue.values() if not isinstance(elements, Sequence): elements = list(elements) return iter(reversed(elements)) def __add__(self, other): if not isinstance(other, NamedElementOnion): raise NotImplementedError("You can only combine with another NamedElementOnion") combined = self._queue.copy() combined.update(other._queue) return NamedElementOnion(combined.items()) def __contains__(self, element): return element in self._queue def __getitem__(self, element): return self._queue[element] def __len__(self): return len(self._queue) def __reversed__(self): elements = self._queue.values() if not isinstance(elements, Sequence): elements = list(elements) return iter(elements) ================================================ FILE: tronapi/common/datatypes.py ================================================ # -------------------------------------------------------------------- # Copyright (c) iEXBase. All rights reserved. # Licensed under the MIT License. # See License.txt in the project root for license information. # -------------------------------------------------------------------- from tronapi.common.formatters import apply_formatters_to_dict from tronapi.common.toolz import ( concat, curry, ) @curry def verify_attr(class_name, key, namespace): if key not in namespace: raise AttributeError( "Property {0} not found on {1} class. " "`{1}.factory` only accepts keyword arguments which are " "present on the {1} class".format(key, class_name) ) class PropertyCheckingFactory(type): def __init__(cls, name, bases, namespace, **kargs): # see PEP487. To accept kwargs in __new__, they need to be # filtered out here. super().__init__(name, bases, namespace) def __new__(mcs, name, bases, namespace, normalizers=None): all_bases = set(concat(base.__mro__ for base in bases)) for key in namespace: verify_key_attr = verify_attr(name, key) verify_key_attr(concat(base.__dict__.keys() for base in all_bases)) if normalizers: processed_namespace = apply_formatters_to_dict( normalizers, namespace) else: processed_namespace = namespace return super().__new__(mcs, name, bases, processed_namespace) ================================================ FILE: tronapi/common/encoding.py ================================================ import json import re from typing import Union from eth_account.datastructures import AttributeDict from hexbytes import HexBytes from eth_utils import ( hexstr_if_str, to_hex, big_endian_to_int, int_to_big_endian ) from trx_utils import ( remove_0x_prefix, encode_hex, add_0x_prefix, decode_hex, is_boolean, is_integer, is_list_like, is_bytes ) from tronapi.common.abi import ( size_of_type, sub_type_of_array_type, is_array_type, is_bool_type, is_uint_type, is_int_type, is_address_type, is_bytes_type, is_string_type ) from tronapi.common.validation import ( validate_abi_type, validate_abi_value ) from tronapi.common.toolz import ( curry ) from tronapi.common.validation import assert_one_val def hex_encode_abi_type(abi_type, value, force_size=None): """ Encodes value into a hex string in format of abi_type """ validate_abi_type(abi_type) validate_abi_value(abi_type, value) data_size = force_size or size_of_type(abi_type) if is_array_type(abi_type): sub_type = sub_type_of_array_type(abi_type) return "".join([remove_0x_prefix(hex_encode_abi_type(sub_type, v, 256)) for v in value]) elif is_bool_type(abi_type): return to_hex_with_size(value, data_size) elif is_uint_type(abi_type): return to_hex_with_size(value, data_size) elif is_int_type(abi_type): return to_hex_twos_compliment(value, data_size) elif is_address_type(abi_type): return pad_hex(value, data_size) elif is_bytes_type(abi_type): if is_bytes(value): return encode_hex(value) else: return value elif is_string_type(abi_type): return to_hex(text=value) else: raise ValueError( "Unsupported ABI type: {0}".format(abi_type) ) def to_hex_twos_compliment(value, bit_size): """ Converts integer value to twos compliment hex representation with given bit_size """ if value >= 0: return to_hex_with_size(value, bit_size) value = (1 << bit_size) + value hex_value = hex(value) hex_value = hex_value.rstrip("L") return hex_value def to_hex_with_size(value, bit_size): """Converts a value to hex with given bit_size:""" return pad_hex(to_hex(value), bit_size) def pad_hex(value, bit_size): """Pads a hex string up to the given bit_size""" value = remove_0x_prefix(value) return add_0x_prefix(value.zfill(int(bit_size / 4))) def trim_hex(hexstr): if hexstr.startswith('0x0'): hexstr = re.sub('^0x0+', '0x', hexstr) if hexstr == '0x': hexstr = '0x0' return hexstr def to_int(value=None, hexstr=None, text=None): """Converts value to it's integer representation. Values are converted this way: * value: * bytes: big-endian integer * bool: True => 1, False => 0 * hexstr: interpret hex as integer * text: interpret as string of digits, like '12' => 12 """ assert_one_val(value, hexstr=hexstr, text=text) if hexstr is not None: return int(hexstr, 16) elif text is not None: return int(text) elif isinstance(value, bytes): return big_endian_to_int(value) elif isinstance(value, str): raise TypeError("Pass in strings with keyword hexstr or text") else: return int(value) @curry def text_if_str(to_type, text_or_primitive): """Convert to a type, assuming that strings can be only unicode text (not a hexstr)""" if isinstance(text_or_primitive, str): (primitive, text) = (None, text_or_primitive) else: (primitive, text) = (text_or_primitive, None) return to_type(primitive, text=text) def to_text(primitive=None, hexstr=None, text=None): assert_one_val(primitive, hexstr=hexstr, text=text) if hexstr is not None: return to_bytes(hexstr=hexstr).decode('utf-8') elif text is not None: return text elif isinstance(primitive, str): return to_text(hexstr=primitive) elif isinstance(primitive, bytes): return primitive.decode('utf-8') elif is_integer(primitive): byte_encoding = int_to_big_endian(primitive) return to_text(byte_encoding) raise TypeError("Expected an int, bytes or hexstr.") def to_bytes(primitive=None, hexstr=None, text=None): assert_one_val(primitive, hexstr=hexstr, text=text) if is_boolean(primitive): return b'\x01' if primitive else b'\x00' elif isinstance(primitive, bytes): return primitive elif is_integer(primitive): return to_bytes(hexstr=to_hex(primitive)) elif hexstr is not None: if len(hexstr) % 2: hexstr = '0x0' + remove_0x_prefix(hexstr) return decode_hex(hexstr) elif text is not None: return text.encode('utf-8') raise TypeError("expected an int in first arg, or keyword of hexstr or text") def to_4byte_hex(hex_or_str_or_bytes: Union[int, str, bytes]) -> str: size_of_4bytes = 4 * 8 byte_str = hexstr_if_str(to_bytes, hex_or_str_or_bytes) if len(byte_str) > 4: raise ValueError( 'expected value of size 4 bytes. Got: %d bytes' % len(byte_str) ) hex_str = encode_hex(byte_str) return pad_hex(hex_str, size_of_4bytes) class FriendlyJsonSerialize: """ Friendly JSON serializer & deserializer When encoding or decoding fails, this class collects information on which fields failed, to show more helpful information in the raised error messages. """ def _json_mapping_errors(self, mapping): for key, val in mapping.items(): try: self._friendly_json_encode(val) except TypeError as exc: yield "%r: because (%s)" % (key, exc) def _json_list_errors(self, iterable): for index, element in enumerate(iterable): try: self._friendly_json_encode(element) except TypeError as exc: yield "%d: because (%s)" % (index, exc) def _friendly_json_encode(self, obj, cls=None): try: encoded = json.dumps(obj, cls=cls) return encoded except TypeError as full_exception: if hasattr(obj, 'items'): item_errors = '; '.join(self._json_mapping_errors(obj)) raise TypeError("dict had unencodable value at keys: {{{}}}".format(item_errors)) elif is_list_like(obj): element_errors = '; '.join(self._json_list_errors(obj)) raise TypeError("list had unencodable value at index: [{}]".format(element_errors)) else: raise full_exception @staticmethod def json_decode(json_str): try: decoded = json.loads(json_str) return decoded except json.decoder.JSONDecodeError as exc: err_msg = 'Could not decode {} because of {}.'.format(repr(json_str), exc) # Calling code may rely on catching JSONDecodeError to recognize bad json # so we have to re-raise the same type. raise json.decoder.JSONDecodeError(err_msg, exc.doc, exc.pos) def json_encode(self, obj, cls=None): try: return self._friendly_json_encode(obj, cls=cls) except TypeError as exc: raise TypeError("Could not encode to JSON: {}".format(exc)) class TronJsonEncoder(json.JSONEncoder): def default(self, obj): if isinstance(obj, AttributeDict): return {k: v for k, v in obj.items()} if isinstance(obj, HexBytes): return obj.hex() return json.JSONEncoder.default(self, obj) def to_json(obj: object) -> object: """Convert a complex object (like a transaction object) to a JSON string""" return FriendlyJsonSerialize().json_encode(obj, cls=TronJsonEncoder) ================================================ FILE: tronapi/common/formatters.py ================================================ # -------------------------------------------------------------------- # Copyright (c) iEXBase. All rights reserved. # Licensed under the MIT License. # See License.txt in the project root for license information. # -------------------------------------------------------------------- from collections import Mapping, Iterable from eth_utils import to_dict from trx_utils import ( reject_recursive_repeats, is_string ) from tronapi.common.toolz import ( curry ) @curry @to_dict def apply_formatters_to_dict(formatters, value): for key, item in value.items(): if key in formatters: try: yield key, formatters[key](item) except (TypeError, ValueError) as exc: raise type(exc)("Could not format value %r as field %r" % (item, key)) from exc else: yield key, item @reject_recursive_repeats def recursive_map(func, data): """ Apply func to data, and any collection items inside data (using map_collection). Define func so that it only applies to the type of value that you want it to apply to. """ def recurse(item): return recursive_map(func, item) items_mapped = map_collection(recurse, data) return func(items_mapped) def map_collection(func, collection): """ Apply func to each element of a collection, or value of a dictionary. If the value is not a collection, return it unmodified """ datatype = type(collection) if isinstance(collection, Mapping): return datatype((key, func(val)) for key, val in collection.items()) if is_string(collection): return collection elif isinstance(collection, Iterable): return datatype(map(func, collection)) else: return collection ================================================ FILE: tronapi/common/normalizers.py ================================================ import functools import json from eth_utils import ( is_binary_address, to_hex, hexstr_if_str ) from hexbytes import HexBytes from toolz import curry from tronapi.common.abi import process_type from tronapi.common.account import Address from tronapi.common.encoding import ( to_bytes, text_if_str, to_text ) from tronapi.common.validation import ( validate_abi, validate_address ) def implicitly_identity(to_wrap): @functools.wraps(to_wrap) def wrapper(abi_type, data): modified = to_wrap(abi_type, data) if modified is None: return abi_type, data else: return modified return wrapper def normalize_abi(abi): if isinstance(abi, str): abi = json.loads(abi) validate_abi(abi) return abi def normalize_bytecode(bytecode): if bytecode: bytecode = HexBytes(bytecode) return bytecode @implicitly_identity def abi_address_to_hex(abi_type, data): if abi_type == 'address': validate_address(data) if is_binary_address(data): return abi_type, to_hex(data) @implicitly_identity def abi_string_to_text(abi_type, data): if abi_type == 'string': return abi_type, text_if_str(to_text, data) @implicitly_identity def abi_bytes_to_bytes(abi_type, data): base, sub, arrlist = process_type(abi_type) if base == 'bytes' and not arrlist: return abi_type, hexstr_if_str(to_bytes, data) @implicitly_identity def addresses_checksummed(abi_type, data): if abi_type == 'address': return abi_type, to_checksum_address(data) def to_checksum_address(address: str): return Address().from_hex(address) @curry def abi_resolver(abi_type, val): return abi_type, val BASE_RETURN_NORMALIZERS = [ addresses_checksummed, ] ================================================ FILE: tronapi/common/threads.py ================================================ """ A minimal implementation of the various gevent APIs used within this codebase. """ import threading import time class Timeout(Exception): """ A limited subset of the `gevent.Timeout` context manager. """ seconds = None exception = None begun_at = None is_running = None def __init__(self, seconds=None, exception=None, *args, **kwargs): self.seconds = seconds self.exception = exception def __enter__(self): self.start() return self def __exit__(self, exc_type, exc_val, exc_tb): return False def __str__(self): if self.seconds is None: return '' return "{0} seconds".format(self.seconds) @property def expire_at(self): if self.seconds is None: raise ValueError("Timeouts with `seconds == None` do not have an expiration time") elif self.begun_at is None: raise ValueError("Timeout has not been started") return self.begun_at + self.seconds def start(self): if self.is_running is not None: raise ValueError("Timeout has already been started") self.begun_at = time.time() self.is_running = True def check(self): if self.is_running is None: raise ValueError("Timeout has not been started") elif self.is_running is False: raise ValueError("Timeout has already been cancelled") elif self.seconds is None: return elif time.time() > self.expire_at: self.is_running = False if isinstance(self.exception, type): raise self.exception(str(self)) elif isinstance(self.exception, Exception): raise self.exception else: raise self def cancel(self): self.is_running = False def sleep(self, seconds): time.sleep(seconds) self.check() class ThreadWithReturn(threading.Thread): def __init__(self, target=None, args=None, kwargs=None): super().__init__( target=target, args=args or tuple(), kwargs=kwargs or {}, ) self.target = target self.args = args self.kwargs = kwargs def run(self): self._return = self.target(*self.args, **self.kwargs) def get(self, timeout=None): self.join(timeout) try: return self._return except AttributeError: raise RuntimeError("Something went wrong. No `_return` property was set") class TimerClass(threading.Thread): def __init__(self, interval, callback, *args): threading.Thread.__init__(self) self.callback = callback self.terminate_event = threading.Event() self.interval = interval self.args = args def run(self): while not self.terminate_event.is_set(): self.callback(*self.args) self.terminate_event.wait(self.interval) def stop(self): self.terminate_event.set() def spawn(target, *args, thread_class=ThreadWithReturn, **kwargs): thread = thread_class( target=target, args=args, kwargs=kwargs, ) thread.daemon = True thread.start() return thread ================================================ FILE: tronapi/common/toolz/__init__.py ================================================ # -------------------------------------------------------------------- # Copyright (c) iEXBase. All rights reserved. # Licensed under the MIT License. # See License.txt in the project root for license information. # -------------------------------------------------------------------- try: from cytoolz import ( assoc, complement, compose, concat, curry, dicttoolz, dissoc, excepts, functoolz, groupby, identity, itertoolz, merge, partial, pipe, sliding_window, valfilter, valmap, ) except ImportError: from toolz import ( # noqa: F401 assoc, complement, compose, concat, curry, dicttoolz, dissoc, excepts, functoolz, groupby, identity, itertoolz, merge, partial, pipe, sliding_window, valfilter, valmap, ) ================================================ FILE: tronapi/common/transactions.py ================================================ from tronapi.common.threads import Timeout def wait_for_transaction_id(tron, tx_id, timeout=120, poll_latency=0.1): with Timeout(timeout) as _timeout: while True: tx_detail = tron.trx.get_transaction(tx_id) # FIXME: The check for a null `ref_block_hash` is due to parity's if tx_detail is not None and \ 'raw_data' in tx_detail and \ tx_detail['raw_data']['ref_block_hash'] is not None: break _timeout.sleep(poll_latency) return tx_detail ================================================ FILE: tronapi/common/validation.py ================================================ # -------------------------------------------------------------------------------------------- # Copyright (c) iEXBase. All rights reserved. # Licensed under the MIT License. # See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- import itertools import re from _sha256 import sha256 from typing import Any import base58 from eth_utils import ( function_abi_to_4byte_selector, apply_formatter_to_array ) from trx_utils import ( is_hex, encode_hex, is_0x_prefixed, is_text, is_list_like, is_dict, is_string, is_bytes, is_boolean, is_integer, is_binary_address, is_hex_address, is_checksum_address) from tronapi.common.toolz import ( compose, groupby, valfilter, valmap, ) from tronapi.common.abi import filter_by_type, abi_to_signature, is_recognized_type, is_string_type, is_bytes_type, \ is_address_type, is_int_type, is_uint_type, is_bool_type, sub_type_of_array_type, is_array_type, \ length_of_array_type from tronapi.exceptions import InvalidAddress def _prepare_selector_collision_msg(duplicates): dup_sel = valmap(apply_formatter_to_array(abi_to_signature), duplicates) joined_funcs = valmap(lambda f: ', '.join(f), dup_sel) func_sel_msg_list = [funcs + ' have selector ' + sel for sel, funcs in joined_funcs.items()] return ' and\n'.join(func_sel_msg_list) def is_valid_url(value): """Return whether or not given value is a valid URL. Args: value(str): URL address string to validate """ regex = re.compile( r'^(?:http|ftp)s?://' r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' r'localhost|' r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' r'(?::\d+)?' r'(?:/?|[/?]\S+)$', re.IGNORECASE) result = regex.match(value) return bool(result) def validate_abi(abi): """ Helper function for validating an ABI """ if not is_list_like(abi): raise ValueError("'abi' is not a list") if not all(is_dict(e) for e in abi): raise ValueError("'abi' is not a list of dictionaries") functions = filter_by_type('function', abi) selectors = groupby( compose(encode_hex, function_abi_to_4byte_selector), functions ) duplicates = valfilter(lambda funcs: len(funcs) > 1, selectors) if duplicates: raise ValueError( 'Abi contains functions with colliding selectors. ' 'Functions {0}'.format(_prepare_selector_collision_msg(duplicates)) ) def validate_abi_type(abi_type): """ Helper function for validating an abi_type """ if not is_recognized_type(abi_type): raise ValueError("Unrecognized abi_type: {abi_type}".format(abi_type=abi_type)) def validate_abi_value(abi_type, value): """ Helper function for validating a value against the expected abi_type Note: abi_type 'bytes' must either be python3 'bytes' object or '' """ if is_array_type(abi_type) and is_list_like(value): # validate length specified_length = length_of_array_type(abi_type) if specified_length is not None: if specified_length < 1: raise TypeError( "Invalid abi-type: {abi_type}. Length of fixed sized arrays" "must be greater than 0." .format(abi_type=abi_type) ) if specified_length != len(value): raise TypeError( "The following array length does not the length specified" "by the abi-type, {abi_type}: {value}" .format(abi_type=abi_type, value=value) ) # validate sub_types sub_type = sub_type_of_array_type(abi_type) for v in value: validate_abi_value(sub_type, v) return elif is_bool_type(abi_type) and is_boolean(value): return elif is_uint_type(abi_type) and is_integer(value) and value >= 0: return elif is_int_type(abi_type) and is_integer(value): return elif is_address_type(abi_type): validate_address(value) return elif is_bytes_type(abi_type): if is_bytes(value): return elif is_string(value): if is_0x_prefixed(value): return else: raise TypeError( "ABI values of abi-type 'bytes' must be either" "a python3 'bytes' object or an '0x' prefixed string." ) elif is_string_type(abi_type) and is_string(value): return raise TypeError( "The following abi value is not a '{abi_type}': {value}".format(abi_type=abi_type, value=value) ) def validate_address(value): """ Helper function for validating an address """ if is_bytes(value): if not is_binary_address(value): raise InvalidAddress("Address must be 20 bytes when input type is bytes", value) return if not isinstance(value, str): raise TypeError('Address {} must be provided as a string'.format(value)) if not is_hex_address(value): raise InvalidAddress("Address must be 20 bytes, as a hex string with a 0x prefix", value) if not is_checksum_address(value): if value == value.lower(): raise InvalidAddress( "Web3.py only accepts checksum addresses. " "The software that gave you this non-checksum address should be considered unsafe, " "please file it as a bug on their platform. " "Try using an ENS name instead. Or, if you must accept lower safety, " "use Web3.toChecksumAddress(lower_case_address).", value, ) else: raise InvalidAddress( "Address has an invalid EIP-55 checksum. " "After looking up the address from the original source, try again.", value, ) def has_one_val(*args, **kwargs): vals = itertools.chain(args, kwargs.values()) not_nones = list(filter(lambda val: val is not None, vals)) return len(not_nones) == 1 def assert_one_val(*args, **kwargs): if not has_one_val(*args, **kwargs): raise TypeError( "Exactly one of the passed values can be specified. " "Instead, values were: %r, %r" % (args, kwargs) ) ================================================ FILE: tronapi/constants.py ================================================ # -------------------------------------------------------------------- # Copyright (c) iEXBase. All rights reserved. # Licensed under the MIT License. # See License.txt in the project root for license information. # -------------------------------------------------------------------- # Here we specify default values for the different needed urls. # They are verified. Don't change this unless you know what you're doing. DEFAULT_NODES = { 'full_node': 'https://api.trongrid.io', 'solidity_node': 'https://api.trongrid.io', 'event_server': 'https://api.trongrid.io' } ================================================ FILE: tronapi/contract.py ================================================ # -------------------------------------------------------------------- # Copyright (c) iEXBase. All rights reserved. # Licensed under the MIT License. # See License.txt in the project root for license information. # -------------------------------------------------------------------- import copy from eth_abi import decode_abi from eth_utils import ( function_abi_to_4byte_selector, to_hex ) from hexbytes import HexBytes from trx_utils import ( encode_hex, is_text, deprecated_for, combomethod ) from tronapi.common.abi import ( filter_by_type, merge_args_and_kwargs, abi_to_signature, fallback_func_abi_exists, check_if_arguments_can_be_encoded, map_abi_data ) from tronapi.common.contracts import ( find_matching_fn_abi, encode_abi, get_function_info, FallbackFn ) from tronapi.common.datatypes import PropertyCheckingFactory from tronapi.common.encoding import to_4byte_hex from tronapi.common.normalizers import ( normalize_abi, normalize_bytecode, BASE_RETURN_NORMALIZERS ) from tronapi.exceptions import ( NoABIFunctionsFound, MismatchedABI, FallbackNotFound ) class NonExistentFallbackFunction: @staticmethod def _raise_exception(): raise FallbackNotFound("No fallback function was found in the contract ABI.") def __getattr__(self, attr): return NonExistentFallbackFunction._raise_exception class ContractFunction: """Base class for contract functions""" address = None function_identifier = None tron = None contract_abi = None abi = None transaction = None arguments = None def __init__(self, abi=None): self.abi = abi self.fn_name = type(self).__name__ def __call__(self, *args, **kwargs): clone = copy.copy(self) if args is None: clone.args = tuple() else: clone.args = args if kwargs is None: clone.kwargs = {} else: clone.kwargs = kwargs clone._set_function_info() return clone def _set_function_info(self): if not self.abi: self.abi = find_matching_fn_abi( self.contract_abi, self.function_identifier, self.args, self.kwargs ) if self.function_identifier is FallbackFn: self.selector = encode_hex(b'') elif is_text(self.function_identifier): self.selector = encode_hex(function_abi_to_4byte_selector(self.abi)) else: raise TypeError("Unsupported function identifier") self.arguments = merge_args_and_kwargs(self.abi, self.args, self.kwargs) @classmethod def factory(cls, class_name, **kwargs): return PropertyCheckingFactory(class_name, (cls,), kwargs)(kwargs.get('abi')) def __repr__(self): if self.abi: _repr = '' return '' % self.fn_name class ContractFunctions: """Class containing contract function objects """ def __init__(self, abi, tron, address=None): if abi: self.abi = abi self._functions = filter_by_type('function', self.abi) for func in self._functions: setattr( self, func['name'], ContractFunction.factory( func['name'], tron=tron, contract_abi=self.abi, address=address, function_identifier=func['name'])) def __iter__(self): if not hasattr(self, '_functions') or not self._functions: return for func in self._functions: yield func['name'] def __getattr__(self, function_name): if '_functions' not in self.__dict__: raise NoABIFunctionsFound( "The abi for this contract contains no function definitions. ", "Are you sure you provided the correct contract abi?" ) elif function_name not in self.__dict__['_functions']: raise MismatchedABI( "The function '{}' was not found in this contract's abi. ".format(function_name), "Are you sure you provided the correct contract abi?" ) else: return super().__getattribute__(function_name) def __getitem__(self, function_name): return getattr(self, function_name) class Contract: # set during class construction tron = None # instance level properties address = None # class properties (overridable at instance level) abi = None bytecode = None bytecode_runtime = None functions = None events = None def __init__(self, address=None): """Create a new smart contract proxy object. :param address: Contract address as 0x hex string """ if self.tron is None: raise AttributeError( 'The `Contract` class has not been initialized. Please use the ' '`tron.contract` interface to create your contract class.' ) if address: self.address = self.tron.address.to_hex(address) if not self.address: raise TypeError("The address argument is required to instantiate a contract.") self.functions = ContractFunctions(self.abi, self.tron, self.address) self.fallback = Contract.get_fallback_function(self.abi, self.tron, self.address) @classmethod def factory(cls, tron, class_name=None, **kwargs): kwargs['tron'] = tron normalizers = { 'abi': normalize_abi, 'bytecode': normalize_bytecode, 'bytecode_runtime': normalize_bytecode, } contract = PropertyCheckingFactory( class_name or cls.__name__, (cls,), kwargs, normalizers=normalizers ) setattr(contract, 'functions', ContractFunctions(contract.abi, contract.tron)) setattr(contract, 'fallback', Contract.get_fallback_function(contract.abi, contract.tron)) return contract @classmethod @deprecated_for("contract.constructor.transact") def deploy(cls, **kwargs): """Deploy Contract Deploys a contract. Returns TransactionExtention, which contains an unsigned transaction. Example: .. code-block:: python >>> MyContract.deploy( fee_limit=10**9, call_value=0, consume_user_resource_percent=10 ) Args: **kwargs: Transaction parameters for the deployment transaction as a dict """ return cls.tron.transaction_builder.create_smart_contract( **kwargs, abi=cls.abi, bytecode=to_hex(cls.bytecode) ) @classmethod def constructor(cls): if cls.bytecode is None: raise ValueError( "Cannot call constructor on a contract that does not have 'bytecode' associated " "with it" ) return ContractConstructor(cls.tron, cls.abi, cls.bytecode) @combomethod def encodeABI(cls, fn_name, args=None, kwargs=None, data=None): """Encodes the arguments using the Tron ABI for the contract function that matches the given name and arguments.. """ fn_abi, fn_selector, fn_arguments = get_function_info( fn_name, contract_abi=cls.abi, args=args, kwargs=kwargs, ) if data is None: data = fn_selector return encode_abi(cls.tron, fn_abi, fn_arguments, data) @staticmethod def get_fallback_function(abi, tron, address=None): if abi and fallback_func_abi_exists(abi): return ContractFunction.factory( 'fallback', tron=tron, contract_abi=abi, address=address, function_identifier=FallbackFn)() return NonExistentFallbackFunction() @combomethod def all_functions(self): return find_functions_by_identifier( self.abi, self.tron, self.address, lambda _: True ) @combomethod def get_function_by_signature(self, signature): if ' ' in signature: raise ValueError( 'Function signature should not contain any spaces. ' 'Found spaces in input: %s' % signature ) def callable_check(fn_abi): return abi_to_signature(fn_abi) == signature fns = find_functions_by_identifier(self.abi, self.tron, self.address, callable_check) return get_function_by_identifier(fns, 'signature') @combomethod def find_functions_by_name(self, fn_name): def callable_check(fn_abi): return fn_abi['name'] == fn_name return find_functions_by_identifier( self.abi, self.tron, self.address, callable_check ) @combomethod def get_function_by_name(self, fn_name): fns = self.find_functions_by_name(fn_name) return get_function_by_identifier(fns, 'name') @combomethod def get_function_by_selector(self, selector): def callable_check(fn_abi): return encode_hex(function_abi_to_4byte_selector(fn_abi)) == to_4byte_hex(selector) fns = find_functions_by_identifier(self.abi, self.tron, self.address, callable_check) return get_function_by_identifier(fns, 'selector') @combomethod def decode_function_input(self, data): data = HexBytes(data) selector, params = data[:4], data[4:] func = self.get_function_by_selector(selector) names = [x['name'] for x in func.abi['inputs']] types = [x['type'] for x in func.abi['inputs']] decoded = decode_abi(types, params) normalized = map_abi_data(BASE_RETURN_NORMALIZERS, types, decoded) return func, dict(zip(names, normalized)) @combomethod def find_functions_by_args(self, *args): def callable_check(fn_abi): return check_if_arguments_can_be_encoded(fn_abi, args=args, kwargs={}) return find_functions_by_identifier( self.abi, self.tron, self.address, callable_check ) @combomethod def get_function_by_args(self, *args): fns = self.find_functions_by_args(*args) return get_function_by_identifier(fns, 'args') class ContractConstructor: """ Class for contract constructor API. """ def __init__(self, tron, abi, bytecode): self.tron = tron self.abi = abi self.bytecode = bytecode @staticmethod def check_forbidden_keys_in_transaction(transaction, forbidden_keys=None): keys_found = set(transaction.keys()) & set(forbidden_keys) if keys_found: raise ValueError("Cannot set {} in transaction".format(', '.join(keys_found))) @combomethod def transact(self, **kwargs): """Deploy Contract Deploys a contract. Returns TransactionExtention, which contains an unsigned transaction. Args: **kwargs: Additional options to send """ return self.tron.transaction_builder.create_smart_contract( **kwargs, abi=self.abi, bytecode=to_hex(self.bytecode) ) def find_functions_by_identifier(contract_abi, tron, address, callable_check): fns_abi = filter_by_type('function', contract_abi) return [ ContractFunction.factory( fn_abi['name'], tron=tron, contract_abi=contract_abi, address=address, function_identifier=fn_abi['name'], abi=fn_abi ) for fn_abi in fns_abi if callable_check(fn_abi) ] def get_function_by_identifier(fns, identifier): if len(fns) > 1: raise ValueError( 'Found multiple functions with matching {0}. ' 'Found: {1!r}'.format(identifier, fns) ) elif len(fns) == 0: raise ValueError( 'Could not find any function with matching {0}'.format(identifier) ) return fns[0] ================================================ FILE: tronapi/exceptions.py ================================================ # -------------------------------------------------------------------- # Copyright (c) iEXBase. All rights reserved. # Licensed under the MIT License. # See License.txt in the project root for license information. # -------------------------------------------------------------------- class TronError(Exception): """Base class for TronAPI exceptions.""" class InvalidTronError(TronError): """Raised Tron Error""" class FallbackNotFound(Exception): """ Raised when fallback function doesn't exist in contract. """ pass class MismatchedABI(Exception): """ Raised when an ABI does not match with supplied parameters, or when an attempt is made to access a function/event that does not exist in the ABI. """ pass class InvalidAddress(ValueError): """ The supplied address does not have a valid checksum, as defined in EIP-55 """ pass class NoABIFunctionsFound(AttributeError): """ Raised when an ABI doesn't contain any functions. """ pass class ValidationError(Exception): """ Raised when a supplied value is invalid. """ pass class TransportError(TronError): """Base exception for transport related errors. This is mainly for cases where the status code denotes an HTTP error, and for cases in which there was a connection error. """ @property def status_code(self): return self.args[0] @property def error(self): return self.args[1] @property def info(self): return self.args[2] @property def url(self): return self.args[3] class HttpError(TransportError): """Exception for errors occurring when connecting, and/or making a request""" class BadRequest(TransportError): """Exception for HTTP 400 errors.""" class NotFoundError(TransportError): """Exception for HTTP 404 errors.""" class ServiceUnavailable(TransportError): """Exception for HTTP 503 errors.""" class GatewayTimeout(TransportError): """Exception for HTTP 503 errors.""" class TimeExhausted(Exception): """ Raised when a method has not retrieved the desired result within a specified timeout. """ pass HTTP_EXCEPTIONS = { 400: BadRequest, 404: NotFoundError, 503: ServiceUnavailable, 504: GatewayTimeout, } ================================================ FILE: tronapi/main.py ================================================ # -*- coding: utf-8 -*- # -------------------------------------------------------------------- # Copyright (c) iEXBase. All rights reserved. # Licensed under the MIT License. # See License.txt in the project root for license information. # -------------------------------------------------------------------- """ tronapi.main =============== Connect to the Tron network. :copyright: © 2019 by the iEXBase. :license: MIT License """ from eth_account.datastructures import AttributeDict from urllib.parse import urlencode from eth_utils import ( apply_to_return_value, to_hex, keccak as tron_keccak, ) from hexbytes import HexBytes from trx_utils import ( to_sun, from_sun, is_integer, add_0x_prefix, remove_0x_prefix, is_address ) from tronapi.common.abi import map_abi_data from tronapi.common.account import Address, PrivateKey, Account from tronapi.common.normalizers import abi_resolver from tronapi.common.encoding import ( to_bytes, to_int, to_text, to_json, hex_encode_abi_type ) from tronapi.exceptions import ( InvalidTronError, TronError ) from tronapi.manager import TronManager from tronapi import HttpProvider, constants from tronapi.transactionbuilder import TransactionBuilder from tronapi.trx import Trx DEFAULT_MODULES = { 'trx': Trx } class Tron: # Providers HTTPProvider = HttpProvider _default_block = None _private_key = None _default_address = AttributeDict({}) # Encoding and Decoding toBytes = staticmethod(to_bytes) toInt = staticmethod(to_int) toHex = staticmethod(to_hex) toText = staticmethod(to_text) toJSON = staticmethod(to_json) # Currency Utility toSun = staticmethod(to_sun) fromSun = staticmethod(from_sun) # Validate address isAddress = staticmethod(is_address) def __init__(self, **kwargs): """Connect to the Tron network. Args: kwargs (Any): We fill the most necessary parameters for working with blockchain Tron """ # We check the obtained nodes, if the necessary parameters # are not specified, then we take the default kwargs.setdefault('full_node', constants.DEFAULT_NODES['full_node']) kwargs.setdefault('solidity_node', constants.DEFAULT_NODES['solidity_node']) kwargs.setdefault('event_server', constants.DEFAULT_NODES['event_server']) # The node manager allows you to automatically determine the node # on the router or manually refer to a specific node. # solidity_node, full_node or event_server self.manager = TronManager(self, dict( full_node=kwargs.get('full_node'), solidity_node=kwargs.get('solidity_node'), event_server=kwargs.get('event_server') )) # If the parameter of the private key is not empty, # then write to the variable if 'private_key' in kwargs: self.private_key = kwargs.get('private_key') # We check whether the default wallet address is set when # defining the class, and then written to the variable if 'default_address' in kwargs: self.default_address = kwargs.get('default_address') # If custom methods are not declared, # we take the default from the list modules = kwargs.setdefault('modules', DEFAULT_MODULES) for module_name, module_class in modules.items(): module_class.attach(self, module_name) self.transaction_builder = TransactionBuilder(self) @property def default_block(self): return self._default_block @default_block.setter def default_block(self, block_id): """Sets the default block used as a reference for all future calls.""" if block_id in ('latest', 'earliest', 0): self._default_block = block_id return if not is_integer(block_id) or not block_id: raise ValueError('Invalid block ID provided') self._default_block = abs(block_id) @property def providers(self): """List providers""" return self.manager.providers @property def private_key(self): """Get a private key""" return self._private_key @private_key.setter def private_key(self, value: str) -> None: """Set a private key used with the TronAPI instance, used for obtaining the address, signing transactions etc... Args: value (str): Private key """ try: private_key = PrivateKey(value) except ValueError: raise TronError('Invalid private key provided') self._private_key = str(private_key).lower() @property def default_address(self) -> AttributeDict: """Get a TRON Address""" return self._default_address @default_address.setter def default_address(self, address: str) -> None: """Sets the address used with all Tron API. Will not sign any transactions. Args: address (str) Tron Address """ if not self.isAddress(address): raise InvalidTronError('Invalid address provided') _hex = self.address.to_hex(address) _base58 = self.address.from_hex(address) _private_base58 = self.address.from_private_key(self._private_key).base58 # check the addresses if self._private_key and _private_base58 != _base58: self._private_key = None self._default_address = AttributeDict({ 'hex': _hex, 'base58': _base58 }) def get_event_result(self, **kwargs): """Will return all events matching the filters. Args: kwargs (any): List parameters """ # Check the most necessary parameters since_timestamp = kwargs.setdefault('since_timestamp', 0) event_name = kwargs.setdefault('event_name', 'Notify') block_number = kwargs.setdefault('block_number', '') size = kwargs.setdefault('size', 20) page = kwargs.setdefault('page', 1) only_confirmed = kwargs.setdefault('only_confirmed', None) only_unconfirmed = kwargs.setdefault('only_unconfirmed', None) previous_last = kwargs.setdefault('previous_last_event_fingerprint', None) contract_address = kwargs.setdefault('contract_address', self.default_address.hex) if not self.isAddress(contract_address): raise InvalidTronError('Invalid contract address provided') if event_name and not contract_address: raise TronError('Usage of event name filtering requires a contract address') if block_number and event_name is None: raise TronError('Usage of block number filtering requires an event name') if not is_integer(page): raise ValueError('Invalid size provided') if not is_integer(since_timestamp): raise ValueError('Invalid sinceTimestamp provided') # If the size exceeds 200, displays an error if size > 200: raise ValueError('Defaulting to maximum accepted size: 200') # We collect all parameters in one array route_params = [] if contract_address: route_params.append(contract_address) if event_name: route_params.append(event_name) if block_number: route_params.append(block_number) route = '/'.join(route_params) qs = { 'since': since_timestamp, 'page': page, 'size': size } if only_confirmed is not None: qs.update({'onlyConfirmed': only_confirmed}) if only_unconfirmed is not None and not only_confirmed: qs.update({'onlyUnconfirmed': only_unconfirmed}) if previous_last is not None: qs.update({'previousLastEventFingerprint': previous_last}) return self.manager.request("/event/contract/{0}?{1}" .format(route, urlencode(qs)), method='get') def get_event_transaction_id(self, tx_id): """Will return all events within a transactionID. Args: tx_id (str): TransactionID to query for events. """ response = self.manager.request('/event/transaction/' + tx_id, method='get') return response @property def address(self) -> Address: """Helper object that allows you to convert between hex/base58 and private key representations of a TRON address. Note: If you wish to convert generic data to hexadecimal strings, please use the function tron.to_hex. """ return Address() @property def create_account(self) -> PrivateKey: """Create account Warning: Please control risks when using this API. To ensure environmental security, please do not invoke APIs provided by other or invoke this very API on a public network. """ return Account.create() @staticmethod def is_valid_provider(provider) -> bool: """Check connected provider Args: provider(HttpProvider): Provider """ return isinstance(provider, HttpProvider) def solidity_sha3(self, abi_types, values): """ Executes keccak256 exactly as Solidity does. Takes list of abi_types as inputs -- `[uint24, int8[], bool]` and list of corresponding values -- `[20, [-1, 5, 0], True]` Args: abi_types (any): types abi values (any): values Examples: >>> tron = Tron() >>> sol = tron.solidity_sha3(['uint8[]'], [[1, 2, 3, 4, 5]]) >>> assert sol.hex() == '0x5917e5a395fb9b454434de59651d36822a9e29c5ec57474df3e67937b969460c' """ if len(abi_types) != len(values): raise ValueError( "Length mismatch between provided abi types and values. Got " "{0} types and {1} values.".format(len(abi_types), len(values)) ) normalized_values = map_abi_data([abi_resolver()], abi_types, values) hex_string = add_0x_prefix(''.join( remove_0x_prefix(hex_encode_abi_type(abi_type, value)) for abi_type, value in zip(abi_types, normalized_values) )) return self.keccak(hexstr=hex_string) @staticmethod @apply_to_return_value(HexBytes) def keccak(primitive=None, text=None, hexstr=None): if isinstance(primitive, (bytes, int, type(None))): input_bytes = to_bytes(primitive, hexstr=hexstr, text=text) return tron_keccak(input_bytes) raise TypeError( "You called keccak with first arg %r and keywords %r. You must call it with one of " "these approaches: keccak(text='txt'), keccak(hexstr='0x747874'), " "keccak(b'\\x74\\x78\\x74'), or keccak(0x747874)." % ( primitive, {'text': text, 'hexstr': hexstr} ) ) def is_connected(self): """List of available providers""" return self.manager.is_connected() ================================================ FILE: tronapi/manager.py ================================================ # -------------------------------------------------------------------- # Copyright (c) iEXBase. All rights reserved. # Licensed under the MIT License. # See License.txt in the project root for license information. # -------------------------------------------------------------------- """ tronapi.manager =============== This class is designed to configure and define nodes for different types. :copyright: © 2019 by the iEXBase. :license: MIT License """ from trx_utils import is_string from tronapi import HttpProvider from tronapi.constants import DEFAULT_NODES # In this variable, you can specify the base paths # to test the connection with the nodes. # It is advisable to leave the settings unchanged. STATUS_PAGE = { 'full_node': '/wallet/getnowblock', 'solidity_node': '/walletsolidity/getnowblock', 'event_server': '/healthcheck' } class TronManager(object): """This class is designed to configure and define nodes for different types. """ _providers = None def __init__(self, tron, providers): """Create new manager tron instance Args: tron: The tron implementation providers: List of providers """ self.tron = tron self.providers = providers self.preferred_node = None for key, value in self.providers.items(): # This condition checks the nodes, # if the link to the node is not specified, # we insert the default value to avoid an error. if not providers[key]: self.providers[key] = HttpProvider(DEFAULT_NODES[key]) # If the type of the accepted provider is lower-case, # then we transform it to “HttpProvider”, if is_string(value): self.providers[key] = HttpProvider(value) self.providers[key].status_page = STATUS_PAGE[key] @property def providers(self): """Getting a list of all providers """ return self._providers or tuple() @providers.setter def providers(self, value) -> None: """Add a new provider """ self._providers = value @property def full_node(self) -> HttpProvider: """Getting and managing paths to a full node """ if 'full_node' not in self.providers: raise ValueError('Full node is not activated.') return self.providers.get('full_node') @property def solidity_node(self) -> HttpProvider: """Getting and managing paths to a solidity node """ if 'solidity_node' not in self.providers: raise ValueError('Solidity node is not activated.') return self.providers.get('solidity_node') @property def event_server(self) -> HttpProvider: """Getting and managing paths to a event server """ if 'event_server' not in self.providers: raise ValueError('Event server is not activated.') return self.providers.get('event_server') def request(self, url, params=None, method=None): """Prepare and route the request object according to the manager's configuration. Args: url (str): Path to send params (dict): Options method (str): Request method """ method = 'post' if method is None else method # In this variable, we divide the resulting reference # into 2 parts to determine the type of node split = url[1:].split('/', 2) if split[0] in ('walletsolidity', 'walletextension',): return self.solidity_node.request(url, json=params, method=method) elif split[0] in ('wallet',): return self.full_node.request(url, json=params, method=method) elif split[0] in ('event', 'healthcheck',): return self.event_server.request(url, json=params, method=method) raise ValueError('Could not determine the type of node') def is_connected(self): """Check connection with providers""" is_node = dict() for key, value in self.providers.items(): is_node.update({key: value.is_connected()}) return is_node ================================================ FILE: tronapi/module.py ================================================ # -------------------------------------------------------------------- # Copyright (c) iEXBase. All rights reserved. # Licensed under the MIT License. # See License.txt in the project root for license information. # -------------------------------------------------------------------- class Module: """Module Class""" def __init__(self, tron) -> None: self.tron = tron @classmethod def attach(cls, target, module_name: str = None) -> None: if not module_name: module_name = cls.__name__.lower() if hasattr(target, module_name): raise AttributeError( "Cannot set {0} module named '{1}'. The Tron object " "already has an attribute with that name".format( target, module_name, ) ) if isinstance(target, Module): tron = target.tron else: tron = target setattr(target, module_name, cls(tron)) ================================================ FILE: tronapi/providers/__init__.py ================================================ ================================================ FILE: tronapi/providers/base.py ================================================ # -------------------------------------------------------------------- # Copyright (c) iEXBase. All rights reserved. # Licensed under the MIT License. # See License.txt in the project root for license information. # -------------------------------------------------------------------- import platform import tronapi class BaseProvider(object): _status_page = None @property def status_page(self): """Get the page to check the connection""" return self._status_page @status_page.setter def status_page(self, page): self._status_page = page @staticmethod def _http_default_headers(): """Add default headers""" return { 'Content-Type': 'application/json', 'User-Agent': BaseProvider.format_user_agent() } @staticmethod def format_user_agent(name=None): """Construct a User-Agent suitable for use in client code. This will identify use by the provided ``name`` (which should be on the format ``dist_name/version``), TronAPI version and Python version. .. versionadded:: 1.1 """ parts = ['TronAPI/%s' % tronapi.__version__, '%s/%s' % (platform.python_implementation(), platform.python_version())] if name: parts.insert(0, name) return ' '.join(parts) ================================================ FILE: tronapi/providers/http.py ================================================ # -------------------------------------------------------------------- # Copyright (c) iEXBase. All rights reserved. # Licensed under the MIT License. # See License.txt in the project root for license information. # -------------------------------------------------------------------- """ tronapi.providers.http ====================== Class for configuring http providers :copyright: © 2018 by the iEXBase. :license: MIT License """ import logging from collections import namedtuple from urllib.parse import urlparse from eth_utils import to_dict from requests import Session from requests.exceptions import ( ConnectionError as TrxConnectionError ) from tronapi.common.encoding import to_text from tronapi.providers.base import BaseProvider from tronapi.exceptions import HTTP_EXCEPTIONS, TransportError HTTP_SCHEMES = {'http', 'https'} HttpResponse = namedtuple('HttpResponse', ('status_code', 'headers', 'data')) log = logging.getLogger(__name__) class HttpProvider(BaseProvider): """A Connection object to make HTTP requests to a particular node.""" def __init__(self, node_url, request_kwargs=None): """Initializes a :class:`~tronapi.providers.http.HttpProvider` instance. Args: node_url (str): Url of the node to connect to. request_kwargs (dict): Optional params to send with each request. """ self.node_url = node_url.rstrip('/') uri = urlparse(node_url) # This condition checks the node that will connect # to work with methods. if uri.scheme not in HTTP_SCHEMES: raise NotImplementedError( 'TronAPI does not know how to connect to scheme %r in %r' % ( uri.scheme, self.node_url, ) ) self._request_kwargs = request_kwargs or {} self.session = Session() @to_dict def get_request_kwargs(self): """Header settings""" if 'headers' not in self._request_kwargs: yield 'headers', self._http_default_headers() for key, value in self._request_kwargs.items(): yield key, value def request(self, path, json=None, params=None, method=None): """Performs an HTTP request with the given parameters. Args: path (str): API endpoint path (e.g.: ``'/transactions'``). json (dict): JSON data to send along with the request. params (dict): Dictionary of URL (query) parameters. method (str): HTTP method (e.g.: ``'GET'``). """ try: response = self._request( method=method, url=self.node_url + path if path else self.node_url, json=json, params=params, **self.get_request_kwargs(), ) except TrxConnectionError as err: raise err return response.data def is_connected(self) -> bool: """Connection check This method sends a test request to the connected node to determine its health. Returns: bool: True if successful, False otherwise. """ response = self.request(path=self.status_page, method='get') if 'blockID' in response or response == 'OK': return True return False def _request(self, **kwargs): kwargs.setdefault('timeout', 60) response = self.session.request(**kwargs) text = response.text try: json = response.json() except ValueError: json = None if not (200 <= response.status_code < 300): exc_cls = HTTP_EXCEPTIONS.get(response.status_code, TransportError) raise exc_cls(response.status_code, text, json, kwargs.get('url')) data = json if json is not None else text log.debug(data) # Additional error interceptor that will occur in case of failed requests if 'Error' in data: raise ValueError(data['Error']) self.__error_manager(data) return HttpResponse(response.status_code, response.headers, data) @staticmethod def __error_manager(data): """Manager error Args: data (any): response data """ # Additional error interceptor that will occur in case of failed requests if 'Error' in data: raise ValueError(data['Error']) # Convert hash errors if 'code' in data and 'message' in data: data['message'] = to_text(hexstr=data['message']) ================================================ FILE: tronapi/transactionbuilder.py ================================================ # -------------------------------------------------------------------- # Copyright (c) iEXBase. All rights reserved. # Licensed under the MIT License. # See License.txt in the project root for license information. # -------------------------------------------------------------------- from datetime import datetime from typing import ( Any, Tuple, List ) from eth_abi import encode_abi from trx_utils import ( is_string, is_integer, is_boolean, is_hex, encode_hex ) from tronapi.exceptions import ( InvalidTronError, TronError, InvalidAddress ) from tronapi.common.validation import is_valid_url DEFAULT_TIME = datetime.now() START_DATE = int(DEFAULT_TIME.timestamp() * 1000) class TransactionBuilder(object): def __init__(self, tron): self.tron = tron def send_transaction(self, to, amount, account=None): """Creates a transaction of transfer. If the recipient address does not exist, a corresponding account will be created. Args: to (str): to address amount (float): amount account (str): from address Returns: Transaction contract data """ # If the address of the sender is not specified, we prescribe the default if account is None: account = self.tron.default_address.hex if not self.tron.isAddress(to): raise InvalidTronError('Invalid recipient address provided') if not isinstance(amount, float) or amount <= 0: raise InvalidTronError('Invalid amount provided') _to = self.tron.address.to_hex(to) _from = self.tron.address.to_hex(account) if _to == _from: raise TronError('Cannot transfer TRX to the same account') response = self.tron.manager.request('/wallet/createtransaction', { 'to_address': _to, 'owner_address': _from, 'amount': self.tron.toSun(amount) }) return response def send_token(self, to, amount, token_id, account=None): """Transfer Token Args: to (str): is the recipient address amount (int): is the amount of token to transfer. must be integer instead of float token_id (any): Token Name and id account: (str): is the address of the withdrawal account Returns: Token transfer Transaction raw data """ # If the address of the sender is not specified, we prescribe the default if account is None: account = self.tron.default_address.hex if not self.tron.isAddress(to): raise InvalidTronError('Invalid recipient address provided') if not isinstance(amount, int) or amount <= 0: raise InvalidTronError('Invalid amount provided') if not token_id: raise InvalidTronError('Invalid token ID provided') if not self.tron.isAddress(account): raise InvalidTronError('Invalid origin address provided') _to = self.tron.address.to_hex(to) _from = self.tron.address.to_hex(account) _token_id = self.tron.toHex(text=str(token_id)) if _to == _from: raise TronError('Cannot transfer TRX to the same account') # In case if "TRX" is specified, we redirect to another method. if is_string(token_id) and token_id.upper() == 'TRX': return self.send_transaction(_to, amount, _from) return self.tron.manager.request('/wallet/transferasset', { 'to_address': _to, 'owner_address': _from, 'asset_name': _token_id, 'amount': amount }) def freeze_balance(self, amount, duration, resource, account=None): """ Freezes an amount of TRX. Will give bandwidth OR Energy and TRON Power(voting rights) to the owner of the frozen tokens. Args: amount (int): number of frozen trx duration (int): duration in days to be frozen resource (str): type of resource, must be either "ENERGY" or "BANDWIDTH" account (str): address that is freezing trx account """ # If the address of the sender is not specified, we prescribe the default if account is None: account = self.tron.default_address.hex if resource not in ('BANDWIDTH', 'ENERGY',): raise InvalidTronError('Invalid resource provided: Expected "BANDWIDTH" or "ENERGY"') if not is_integer(amount) or amount <= 0: raise InvalidTronError('Invalid amount provided') if not is_integer(duration) or duration < 3: raise InvalidTronError('Invalid duration provided, minimum of 3 days') if not self.tron.isAddress(account): raise InvalidTronError('Invalid address provided') response = self.tron.manager.request('/wallet/freezebalance', { 'owner_address': self.tron.address.to_hex(account), 'frozen_balance': self.tron.toSun(amount), 'frozen_duration': int(duration), 'resource': resource }) if 'Error' in response: raise TronError(response['Error']) return response def unfreeze_balance(self, resource='BANDWIDTH', account=None): """ Unfreeze TRX that has passed the minimum freeze duration. Unfreezing will remove bandwidth and TRON Power. Args: resource (str): type of resource, must be either "ENERGY" or "BANDWIDTH" account (str): address that is freezing trx account """ # If the address of the sender is not specified, we prescribe the default if account is None: account = self.tron.default_address.hex if resource not in ('BANDWIDTH', 'ENERGY',): raise InvalidTronError('Invalid resource provided: Expected "BANDWIDTH" or "ENERGY"') if not self.tron.isAddress(account): raise InvalidTronError('Invalid address provided') response = self.tron.manager.request('/wallet/unfreezebalance', { 'owner_address': self.tron.address.to_hex(account), 'resource': resource }) if 'Error' in response: raise ValueError(response['Error']) return response def purchase_token(self, to: str, token_id: str, amount: int, buyer=None): """Purchase a Token Creates an unsigned ICO token purchase transaction. Args: to (str): is the address of the Token issuer token_id (str): is the name of the token amount (int): is the number of tokens created buyer (str): is the address of the Token owner """ if buyer is None: buyer = self.tron.default_address.hex if not self.tron.isAddress(to): raise InvalidAddress('Invalid to address provided') if not len(token_id): raise ValueError('Invalid token ID provided') if amount <= 0: raise ValueError('Invalid amount provided') _to = self.tron.address.to_hex(to) _from = self.tron.address.to_hex(buyer) return self.tron.manager.request('/wallet/participateassetissue', { 'to_address': _to, 'owner_address': _from, 'asset_name': self.tron.toHex(text=token_id), 'amount': int(amount) }) def withdraw_block_rewards(self, address: str = None): """Withdraw block rewards Creates an unsigned Super Representative award balance withdraw transaction. Args: address (str): Optional address to withdraw from. """ if not address: address = self.tron.default_address.hex if not self.tron.isAddress(address): raise InvalidAddress('Invalid address provided') return self.tron.manager.request('/wallet/withdrawbalance', { 'owner_address': self.tron.address.to_hex(address) }) def apply_for_sr(self, url, address=None): """Apply to become a super representative Args: url (str): official website address address (str): address """ # If the address of the sender is not specified, we prescribe the default if address is None: address = self.tron.default_address.hex if not self.tron.isAddress(address): raise TronError('Invalid address provided') if not is_valid_url(url): raise TronError('Invalid url provided') return self.tron.manager.request('/wallet/createwitness', { 'owner_address': self.tron.address.to_hex(address), 'url': self.tron.toHex(text=url) }) def vote(self, votes: List[Tuple[str, int]], voter_address: str = None): """Vote Vote on the super representative Args: votes (dict): dictionary of SR address : vote count key-value pair voter_address: voter address Examples: >>> from tronapi import Tron >>> data = [ >>> ('TRJpw2uqohP7FUmAEJgt57wakRn6aGQU6Z', 1) >>> ] >>> tron = Tron() >>> tron.transaction.vote(data) """ if voter_address is None: voter_address = self.tron.default_address.hex _view_vote = [] # We create a cycle to check all the received data for voting. for sr_address, vote_count in votes: if not self.tron.isAddress(sr_address): raise InvalidAddress( 'Invalid SR address provided: ' + sr_address ) if not is_integer(vote_count) or vote_count <= 0: raise ValueError( 'Invalid vote count provided for SR: ' + sr_address ) _view_vote.append({ 'vote_address': self.tron.address.to_hex(sr_address), 'vote_count': int(vote_count) }) return self.tron.manager.request('/wallet/votewitnessaccount', { 'owner_address': self.tron.address.to_hex(voter_address), 'votes': _view_vote }) def create_proposal(self, parameters: Any, issuer_address=None): """Creates a proposal to modify the network. Can only be created by a current Super Representative. Args: parameters (Any): proposal parameters issuer_address: owner address Examples: >>> from tronapi import Tron >>> data = [ >>> {'key': 1, 'value': 2}, >>> {'key': 1, 'value': 2} >>> ] >>> tron = Tron() >>> tron.transaction.create_proposal(data) """ if issuer_address is None: issuer_address = self.tron.default_address.hex if not self.tron.isAddress(issuer_address): raise InvalidAddress('Invalid issuerAddress provided') return self.tron.manager.request('/wallet/proposalcreate', { 'owner_address': self.tron.address.to_hex(issuer_address), 'parameters': parameters }) def vote_proposal(self, proposal_id, has_approval, voter_address=None): """Proposal approval Args: proposal_id (int): proposal id has_approval (bool): Approved voter_address (str): Approve address """ # If the address of the sender is not specified, we prescribe the default if voter_address is None: voter_address = self.tron.default_address.hex if not self.tron.isAddress(voter_address): raise TronError('Invalid voter_address address provided') if not is_integer(proposal_id) or proposal_id < 0: raise TronError('Invalid proposal_id provided') if not is_boolean(has_approval): raise TronError('Invalid has_approval provided') return self.tron.manager.request('/wallet/proposalapprove', { 'owner_address': self.tron.address.to_hex(voter_address), 'proposal_id': int(proposal_id), 'is_add_approval': bool(has_approval) }) def delete_proposal(self, proposal_id: int, issuer_address: str = None): """Delete proposal Args: proposal_id (int): proposal id issuer_address (str): delete the person's address Results: Delete the proposal's transaction """ # If the address of the sender is not specified, we prescribe the default if issuer_address is None: issuer_address = self.tron.default_address.hex if not self.tron.isAddress(issuer_address): raise InvalidTronError('Invalid issuer_address provided') if not isinstance(proposal_id, int) or proposal_id < 0: raise InvalidTronError('Invalid proposal_id provided') return self.tron.manager.request('/wallet/proposaldelete', { 'owner_address': self.tron.address.to_hex(issuer_address), 'proposal_id': int(proposal_id) }) def update_account(self, account_name, account: str = None): """Modify account name Note: Username is allowed to edit only once. Args: account_name (str): name of the account account (str): address Returns: modified Transaction Object """ # If the address of the sender is not specified, we prescribe the default if account is None: account = self.tron.default_address.hex if not is_string(account_name): raise ValueError('Name must be a string') if not self.tron.isAddress(account): raise TronError('Invalid origin address provided') response = self.tron.manager.request('/wallet/updateaccount', { 'account_name': self.tron.toHex(text=account_name), 'owner_address': self.tron.address.to_hex(account) }) return response def create_smart_contract(self, **kwargs): """Deploy Contract Deploys a contract. Returns TransactionExtention, which contains an unsigned transaction. Example: .. code-block:: python >>> from tronapi import Tron >>> >>> tron = Tron() >>> tron.transaction_builder.create_smart_contract( >>> fee_limit=10**9, >>> call_value=0, >>> consume_user_resource_percent=10 >>> ) Args: **kwargs: Transaction parameters for the deployment transaction as a dict """ if 'bytecode' not in kwargs: raise ValueError( "Cannot deploy a contract that does not have 'bytecode' associated " "with it" ) # Maximum TRX consumption, measured in SUN (1 TRX = 1,000,000 SUN). fee_limit = kwargs.setdefault('fee_limit', 0) # The same as User Pay Ratio. # The percentage of resources specified for users who use this contract. # This field accepts integers between [0, 100]. user_fee_percentage = kwargs.setdefault('consume_user_resource_percent', 0) # Amount of TRX transferred with this transaction, measured in SUN (1TRX = 1,000,000 SUN) call_value = kwargs.setdefault('call_value', 0) # Contract owner address, converted to a hex string owner_address = kwargs.setdefault('owner_address', self.tron.default_address.hex) # The max energy which will be consumed by the owner # in the process of excution or creation of the contract, # is an integer which should be greater than 0. origin_energy_limit = kwargs.setdefault('origin_energy_limit', 10000000) if not is_integer(user_fee_percentage) and not user_fee_percentage: user_fee_percentage = 100 if not is_hex(kwargs.get('bytecode')): raise ValueError('Invalid bytecode provided') if not is_integer(fee_limit) or fee_limit <= 0 or \ fee_limit > 1000000000: raise ValueError('Invalid fee limit provided') if not is_integer(call_value) or call_value < 0: raise ValueError('Invalid call value provided') if not is_integer(user_fee_percentage) or user_fee_percentage < 0 or \ user_fee_percentage > 100: raise ValueError('Invalid user fee percentage provided') if not is_integer(origin_energy_limit) or origin_energy_limit < 0: return ValueError('Invalid origin_energy_limit provided') if not self.tron.isAddress(owner_address): raise InvalidAddress('Invalid issuer address provided') # We write all the results in one object transaction = dict(**kwargs) transaction.setdefault('owner_address', self.tron.address.to_hex(owner_address)) return self.tron.manager.request('/wallet/deploycontract', transaction) def trigger_smart_contract(self, **kwargs): """Trigger Smart Contract Calls a function on a contract Args: **kwargs: Fill in the required parameters Examples: >>> tron = Tron() >>> tron.transaction_builder.trigger_smart_contract( >>> contract_address='413c8143e98b3e2fe1b1a8fb82b34557505a752390', >>> function_selector='set(uint256,uint256)', >>> fee_limit=30000, >>> call_value=0, >>> parameters=[ >>> {'type': 'int256', 'value': 1}, >>> {'type': 'int256', 'value': 1} ] ) Returns: TransactionExtention, TransactionExtention contains unsigned Transaction """ contract_address = kwargs.setdefault('contract_address', None) function_selector = kwargs.setdefault('function_selector', None) parameters = kwargs.setdefault('parameters', []) issuer_address = kwargs.setdefault('issuer_address', self.tron.default_address.hex) call_value = kwargs.setdefault('call_value', 0) fee_limit = kwargs.setdefault('fee_limit', 1000000000) token_value = kwargs.setdefault('token_value', 0) token_id = kwargs.setdefault('token_id', 0) if not is_integer(token_value) or token_value < 0: raise ValueError('Invalid options.tokenValue provided') if not is_integer(token_id) or token_id < 0: raise ValueError('Invalid options.tokenId provided') if not self.tron.isAddress(contract_address): raise InvalidAddress('Invalid contract address provided') if not is_string(function_selector): raise ValueError('Invalid function selector provided') if not is_integer(call_value) or call_value < 0: raise ValueError('Invalid call value provided') if not is_integer(fee_limit) or fee_limit <= 0 or fee_limit > 1000000000: raise ValueError('Invalid fee limit provided') function_selector = function_selector.replace('/\s*/g', '') if len(parameters) > 0: types = [] values = [] for abi in parameters: if 'type' not in abi or not is_string(abi['type']): raise ValueError('Invalid parameter type provided: ' + abi['type']) if abi['type'] == 'address': abi['value'] = self.tron.address.to_hex(abi['value']).replace('41', '0x', 1) types.append(abi['type']) values.append(abi['value']) try: parameters = encode_hex(encode_abi(types, values)).replace('0x', '', 2) except ValueError as ex: print(ex) else: parameters = '' data = { 'contract_address': self.tron.address.to_hex(contract_address), 'owner_address': self.tron.address.to_hex(issuer_address), 'function_selector': function_selector, 'fee_limit': int(fee_limit), 'call_value': int(call_value), 'parameter': parameters } if token_value: data['call_token_value'] = int(token_value) if token_id: data['token_id'] = int(token_id) return self.tron.manager.request('/wallet/triggersmartcontract', data) def create_trx_exchange(self, token_name: str, token_balance: int, trx_balance: int, account: str = None): """Create an exchange between a token and TRX. Token Name should be a CASE SENSITIVE string. Note: PLEASE VERIFY THIS ON TRONSCAN. Args: token_name (str): Token Name token_balance (int): balance of the first token trx_balance (int): balance of the second token account (str): Owner Address """ # If the address of the sender is not specified, we prescribe the default if account is None: account = self.tron.default_address.hex if not self.tron.isAddress(account): raise TronError('Invalid address provided') if token_balance <= 0 or trx_balance <= 0: raise TronError('Invalid amount provided') return self.tron.manager.request('/wallet/exchangecreate', { 'owner_address': self.tron.address.to_hex(account), 'first_token_id': self.tron.toHex(text=token_name), 'first_token_balance': token_balance, 'second_token_id': '5f', 'second_token_balance': trx_balance }) def create_token_exchange(self, first_token_name: str, first_token_balance: int, second_token_name: str, second_token_balance: int, owner_address: str = None): """Create an exchange between a token and another token. DO NOT USE THIS FOR TRX. Token Names should be a CASE SENSITIVE string. Args: first_token_name (str): the id of the first token first_token_balance (int): balance of the first token second_token_name (str): the id of the second token second_token_balance (int): balance of the second token owner_address: owner address """ if owner_address is None: owner_address = self.tron.default_address.hex if not self.tron.isAddress(owner_address): raise InvalidAddress('Invalid address provided') if second_token_balance <= 0 or first_token_balance <= 0: raise ValueError('Invalid amount provided') return self.tron.manager.request('/wallet/exchangecreate', { 'owner_address': self.tron.address.to_hex(owner_address), 'first_token_id': self.tron.toHex(text=first_token_name), 'first_token_balance': first_token_balance, 'second_token_id': self.tron.toHex(text=second_token_name), 'second_token_balance': second_token_balance }) def inject_exchange_tokens(self, exchange_id: int, token_name: str, token_amount: int = 0, owner_address: str = None): """Adds tokens into a bancor style exchange. Will add both tokens at market rate. Args: exchange_id (int): non-negative integer exchange id token_name (str): token name token_amount (int): amount of token owner_address (str): token owner address in hex """ if owner_address is None: owner_address = self.tron.default_address.hex if not self.tron.isAddress(owner_address): raise InvalidAddress('Invalid owner_address provided') if exchange_id < 0: raise ValueError('Invalid exchange_id provided') if token_amount < 1: raise ValueError('Invalid token_amount provided') return self.tron.manager.request('/wallet/exchangeinject', { 'owner_address': self.tron.address.to_hex(owner_address), 'exchange_id': exchange_id, 'token_id': self.tron.toHex(text=token_name), 'quant': token_amount }) def create_token(self, **kwargs): """Issue Token Issuing a token on the TRON Protocol can be done by anyone who has at least 1024 TRX in their account. When a token is issued it will be shown on the token overview page. Users can then participate within the issuing time and exchange their TRX for tokens.After issuing the token your account will receive the amount of tokens equal to the total supply. When other users exchange their TRX for tokens then the tokens will be withdrawn from your account and you will receive TRX equal to the specified exchange rate. Args: **kwargs: Fill in the required parameters Examples: >>> start_func = datetime.now() >>> start = int(start_func.timestamp() * 1000) >>> >>> end_func = datetime.now() + timedelta(days=2) >>> end = int(end_func.timestamp() * 1000) >>> >>> opt = { >>> 'name': 'Tron', >>> 'abbreviation': 'TRX', >>> 'description': 'Hello World', >>> 'url': 'https://github.com', >>> 'totalSupply': 25000000, >>> 'frozenAmount': 1, >>> 'frozenDuration': 2, >>> 'freeBandwidth': 10000, >>> 'freeBandwidthLimit': 10000, >>> 'saleStart': start, >>> 'saleEnd': end, >>> 'voteScore': 1 >>> } """ issuer_address = kwargs.setdefault( 'issuer_address', self.tron.default_address.hex ) if not self.tron.isAddress(issuer_address): raise TronError('Invalid issuer address provided') total_supply = kwargs.setdefault('totalSupply', 0) trx_ratio = kwargs.setdefault('trxRatio', 1) token_ratio = kwargs.setdefault('tokenRatio', 1) sale_start = kwargs.setdefault( 'saleStart', START_DATE ) free_bandwidth = kwargs.setdefault('freeBandwidth', 0) free_bandwidth_limit = kwargs.setdefault('freeBandwidthLimit', 0) frozen_amount = kwargs.setdefault('frozenAmount', 0) frozen_duration = kwargs.setdefault('frozenDuration', 0) vote_score = kwargs.setdefault('voteScore', 0) precision = kwargs.setdefault('precision', 0) if not is_string(kwargs.get('name')): raise ValueError('Invalid token name provided') if not is_string(kwargs.get('abbreviation')): raise ValueError('Invalid token abbreviation provided') if not is_integer(total_supply) or total_supply <= 0: raise ValueError('Invalid supply amount provided') if not is_integer(trx_ratio) or trx_ratio <= 0: raise ValueError('TRX ratio must be a positive integer') if not is_integer(token_ratio) or token_ratio <= 0: raise ValueError('Token ratio must be a positive integer') if not is_integer(vote_score) or vote_score <= 0: raise ValueError('voteScore must be a positive integer greater than 0') if not is_integer(precision) or precision <= 0 or precision > 6: raise ValueError('precision must be a positive integer > 0 and <= 6') if not is_integer(sale_start) or sale_start < START_DATE: raise ValueError('Invalid sale start timestamp provided') if not is_integer(kwargs.get('saleEnd')) or \ kwargs.get('saleEnd') <= sale_start: raise ValueError('Invalid sale end timestamp provided') if not is_string(kwargs.get('description')): raise ValueError('Invalid token description provided') if not is_valid_url(kwargs.get('url')): raise ValueError('Invalid token url provided') if not is_integer(free_bandwidth) or free_bandwidth < 0: raise ValueError('Invalid free bandwidth amount provided') if not is_integer(free_bandwidth_limit) or free_bandwidth_limit < 0 \ or (free_bandwidth and not free_bandwidth_limit): raise ValueError('Invalid free bandwidth limit provided') if not is_integer(frozen_amount) or frozen_amount < 0 \ or (not frozen_duration and frozen_amount): raise ValueError('Invalid frozen supply provided') if not is_integer(frozen_duration) or frozen_duration < 0 \ or (frozen_duration and not frozen_amount): raise ValueError('Invalid frozen duration provided') frozen_supply = { 'frozen_amount': int(frozen_amount), 'frozen_days': int(frozen_duration) } response = self.tron.manager.request('/wallet/createassetissue', { 'owner_address': self.tron.address.to_hex(issuer_address), 'name': self.tron.toHex(text=kwargs.get('name')), 'abbr': self.tron.toHex(text=kwargs.get('abbreviation')), 'description': self.tron.toHex(text=kwargs.get('description')), 'url': self.tron.toHex(text=kwargs.get('url')), 'total_supply': int(total_supply), 'trx_num': int(trx_ratio), 'num': int(token_ratio), 'start_time': int(sale_start), 'end_time': int(kwargs.get('saleEnd')), 'free_asset_net_limit': int(free_bandwidth), 'public_free_asset_net_limit': int(free_bandwidth_limit), 'frozen_supply': frozen_supply, 'vote_score': vote_score, 'precision': precision }) return response def withdraw_exchange_tokens(self, exchange_id: int, token_name: str, token_amount: int = 0, owner_address: str = None): """Withdraws tokens from a bancor style exchange. Will withdraw at market rate both tokens. Args: exchange_id (int): non-negative integer exchange id token_name (str): token name token_amount (int): number of tokens withdraw owner_address (str): owner address in hex """ if owner_address is None: owner_address = self.tron.default_address.hex if not self.tron.isAddress(owner_address): raise InvalidAddress('Invalid owner_address provided') if exchange_id < 0: raise ValueError('Invalid exchange_id provided') if token_amount < 1: raise ValueError('Invalid token_amount provided') return self.tron.manager.request('/wallet/exchangewithdraw', { 'owner_address': self.tron.address.to_hex(owner_address), 'exchange_id': exchange_id, 'token_id': self.tron.toHex(text=token_name), 'quant': token_amount }) def trade_exchange_tokens(self, exchange_id: int, token_name: str, token_amount_sold: int = 0, token_amount_expected: int = 0, owner_address: str = None): """Trade tokens on a bancor style exchange. Expected value is a validation and used to cap the total amt of token 2 spent. Args: exchange_id (int): non-negative integer exchange id token_name (str): token name token_amount_sold (int): amount f token actually sold token_amount_expected (int): amount of token expected owner_address (str): token owner address in hex """ if owner_address is None: owner_address = self.tron.default_address.hex if not self.tron.isAddress(owner_address): raise InvalidAddress('Invalid owner_address provided') if exchange_id < 0: raise ValueError('Invalid exchange_id provided') if token_amount_sold < 1: raise ValueError('Invalid token_amount_sold provided') if token_amount_expected < 1: raise ValueError('Invalid token_amount_expected provided') return self.tron.manager.request('/wallet/exchangewithdraw', { 'owner_address': self.tron.address.to_hex(owner_address), 'exchange_id': exchange_id, 'token_id': self.tron.toHex(text=token_name), 'quant': token_amount_sold, 'expected': token_amount_expected }) def update_setting(self, contract_address, user_fee_percentage, owner_address: str = None): """Update userFeePercentage. Args: contract_address (str): the address of the contract to be modified user_fee_percentage (int): the percentage of resources specified for users using this contract owner_address (str): is the address of the creator Returns: Contains unsigned transaction Transaction """ if owner_address is None: owner_address = self.tron.default_address.hex if not self.tron.isAddress(owner_address): raise InvalidAddress('Invalid owner_address provided') if not self.tron.isAddress(contract_address): raise InvalidAddress('Invalid contract_address provided') if not is_integer(user_fee_percentage) or user_fee_percentage < 0 or \ user_fee_percentage > 100: raise ValueError('Invalid user_fee_percentage provided') return self.tron.manager.request('wallet/updatesetting', { 'owner_address': self.tron.address.to_hex(owner_address), 'contract_address': self.tron.address.to_hex(contract_address), 'consume_user_resource_percent': user_fee_percentage }) def update_energy_limit(self, contract_address, origin_energy_limit, owner_address: str = None): """Update energy limit. Args: contract_address (str): The address of the contract to be modified origin_energy_limit (int): The maximum energy set by the creator that is created owner_address (str): Is the address of the creator Returns: Contains unsigned transaction Transaction """ if owner_address is None: owner_address = self.tron.default_address.hex if not self.tron.isAddress(owner_address): raise InvalidAddress('Invalid owner_address provided') if not self.tron.isAddress(contract_address): raise InvalidAddress('Invalid contractAddress provided') if not is_integer(origin_energy_limit) or origin_energy_limit < 0 or \ origin_energy_limit > 10000000: raise ValueError('Invalid originEnergyLimit provided') return self.tron.manager.request('wallet/updateenergylimit', { 'owner_address': self.tron.address.to_hex(owner_address), 'contract_address': self.tron.address.to_hex(contract_address), 'origin_energy_limit': origin_energy_limit }) def check_permissions(self, permissions, _type): if permissions is not None: if permissions['type'] != _type or \ not permissions['permission_name'] or \ not is_string(permissions['permission_name']) or \ not is_integer(permissions['threshold']) or \ permissions['threshold'] < 1 or not permissions['keys']: return False for key in permissions['key']: if not self.tron.isAddress(key['address']) or \ not is_integer(key['weight']) or \ key['weight'] > permissions['threshold'] or \ key['weight'] < 1 or _type == 2 and not permissions['operations']: return False return True def update_account_permissions(self, owner_address=None, owner_permissions=None, witness_permissions=None, actives_permissions=None ): """Role: update user permissions (for multi-signature) Args: owner_address (str): The address of the account whose permissions are to be modified owner_permissions: Modified owner permission witness_permissions: Modified witness permission (if it is a witness) actives_permissions: Modified actives permission """ if owner_address is None: owner_address = self.tron.default_address.hex if not self.check_permissions(owner_permissions, 0): raise InvalidTronError('Invalid ownerPermissions provided') if not self.check_permissions(witness_permissions, 1): raise InvalidTronError('Invalid witnessPermissions provided') for actives_permission in actives_permissions: if not self.check_permissions(actives_permission, 2): raise InvalidTronError('Invalid activesPermissions provided') data = { owner_address: owner_address } if owner_permissions: data['owner'] = owner_permissions if witness_permissions: data['witness'] = witness_permissions if actives_permissions: if len(actives_permissions) == 1: data['actives'] = actives_permissions[0] else: data['actives'] = actives_permissions return self.tron.manager.request('wallet/accountpermissionupdate', data) ================================================ FILE: tronapi/trx.py ================================================ # -------------------------------------------------------------------- # Copyright (c) iEXBase. All rights reserved. # Licensed under the MIT License. # See License.txt in the project root for license information. # -------------------------------------------------------------------- """ tronapi.trx =============== Work with basic methods :copyright: © 2018 by the iEXBase. :license: MIT License """ import math from typing import Any from trx_utils import is_integer, is_hex from trx_utils.types import is_object, is_string, is_list from tronapi.common.transactions import wait_for_transaction_id from tronapi.contract import Contract from tronapi.exceptions import InvalidTronError, TronError, TimeExhausted from tronapi.module import Module from tronapi.common.blocks import select_method_for_block from tronapi.common.toolz import ( assoc ) from tronapi.common.account import Account TRX_MESSAGE_HEADER = '\x19TRON Signed Message:\n' ETH_MESSAGE_HEADER = '\x19Ethereum Signed Message:\n' class Trx(Module): default_contract_factory = Contract def get_current_block(self): """Query the latest block""" return self.tron.manager.request(url='/wallet/getnowblock') def get_confirmed_current_block(self): """Query the confirmed latest block""" return self.tron.manager.request('/walletsolidity/getnowblock') def get_block(self, block: Any = None): """Get block details using HashString or blockNumber Args: block (Any): ID or height for the block """ # If the block identifier is not specified, # we take the default if block is None: block = self.tron.default_block if block == 'latest': return self.get_current_block() elif block == 'earliest': return self.get_block(0) method = select_method_for_block( block, if_hash={'url': '/wallet/getblockbyid', 'field': 'value'}, if_number={'url': '/wallet/getblockbynum', 'field': 'num'}, ) result = self.tron.manager.request(method['url'], { method['field']: block }) if result: return result raise ValueError("The call to {method['url']} did not return a value.") def get_transaction_count_by_blocknum(self, num: int): """Query transaction's count on a specified block by height Args: num (int): block number """ if not is_integer(num) or num < 0: raise ValueError('Invalid num provided') return self.tron.manager.request('/wallet/gettransactioncountbyblocknum', { 'num': num }) def get_block_transaction_count(self, block: Any): """Total number of transactions in a block Args: block (Any): Number or Hash Block """ transaction = self.get_block(block) if 'transactions' not in transaction: raise TronError('Parameter "transactions" not found') return len(transaction) def get_transaction_from_block(self, block: Any, index: int = 0): """Get transaction details from Block Args: block (Any): Number or Hash Block index (int) Position """ if not is_integer(index) or index < 0: raise InvalidTronError('Invalid transaction index provided') transactions = self.get_block(block).get('transactions') if not transactions or len(transactions) < index: raise TronError('Transaction not found in block') return transactions[index] def wait_for_transaction_id(self, transaction_hash: str, timeout=120, poll_latency=0.2): """ Waits for the transaction specified by transaction_hash to be included in a block, then returns its transaction receipt. Optionally, specify a timeout in seconds. If timeout elapses before the transaction is added to a block, then wait_for_transaction_id() raises a Timeout exception. Args: transaction_hash (str): Transaction Hash timeout (int): TimeOut poll_latency (any): between subsequent requests """ try: if poll_latency > timeout: poll_latency = timeout return wait_for_transaction_id(self.tron, transaction_hash, timeout, poll_latency) except TimeoutError: raise TimeExhausted( "Transaction {} is not in the chain, after {} seconds".format( transaction_hash, timeout, ) ) def get_transaction(self, transaction_id: str, is_confirm: bool = False): """Query transaction based on id Args: transaction_id (str): transaction id is_confirm (bool): """ method = 'walletsolidity' if is_confirm else 'wallet' response = self.tron.manager.request('/{}/gettransactionbyid'.format(method), { 'value': transaction_id }) if 'txID' not in response: raise ValueError('Transaction not found') return response def get_account_by_id(self, account_id: str, options: object): return self.get_account_info_by_id(account_id, options) def get_account_info_by_id(self, account_id: str, options: object): if account_id.startswith('0x'): account_id = id[2:] if 'confirmed' in options: return self.tron.manager.request('/walletsolidity/getaccountbyid', { 'account_id': self.tron.toHex(text=account_id) }) return self.tron.manager.request('/wallet/getaccountbyid', { 'account_id': self.tron.toHex(text=account_id) }) def get_unconfirmed_account_by_id(self, account_id: str): return self.get_account_info_by_id(account_id, { 'confirmed': True }) def get_account_resource(self, address=None): """Query the resource information of the account Args: address (str): Address Results: Resource information of the account """ if address is None: address = self.tron.default_address.hex if not self.tron.isAddress(address): raise InvalidTronError('Invalid address provided') return self.tron.manager.request('/wallet/getaccountresource', { 'address': self.tron.address.to_hex(address) }) def get_account(self, address=None): """Query information about an account Args: address (str): Address """ if address is None: address = self.tron.default_address.hex if not self.tron.isAddress(address): raise InvalidTronError('Invalid address provided') return self.tron.manager.request('/walletsolidity/getaccount', { 'address': self.tron.address.to_hex(address) }) def get_balance(self, address=None, is_float=False): """Getting a balance Args: address (str): Address is_float (bool): Convert to float format """ response = self.get_account(address) if 'balance' not in response: return 0 if is_float: return self.tron.fromSun(response['balance']) return response['balance'] def get_transactions_related(self, address, direction='all', limit=30, offset=0): """Getting data in the "from", "to" and "all" directions Args: address (str): Address direction (str): Type direction address (str): address limit (int): number of transactions expected to be returned offset (int): index of the starting transaction """ if direction not in ['from', 'to', 'all']: raise InvalidTronError('Invalid direction provided: Expected "to", "from" or "all"') if direction == 'all': _from = self.get_transactions_related(address, 'from', limit, offset) _to = self.get_transactions_related(address, 'to', limit, offset) filter_from = [{**i, 'direction': 'from'} for i in _from] filter_to = [{**i, 'direction': 'to'} for i in _to] callback = filter_from callback.extend(filter_to) return callback if address is None: address = self.tron.default_address.hex if not self.tron.isAddress(address): raise InvalidTronError('Invalid address provided') if not isinstance(limit, int) or limit < 0 or (offset and limit < 1): raise InvalidTronError('Invalid limit provided') if not isinstance(offset, int) or offset < 0: raise InvalidTronError('Invalid offset provided') path = '/walletextension/gettransactions{0}this'.format(direction) response = self.tron.manager.request(path, { 'account': { 'address': self.tron.address.to_hex(address) }, 'limit': limit, 'offset': offset }, 'get') if 'transaction' in response: return response['transaction'] return response def get_transactions_to_address(self, address=None, limit=30, offset=0): """Query the list of transactions received by an address Args: address (str): address limit (int): number of transactions expected to be returned offset (int): index of the starting transaction Returns: Transactions list """ return self.get_transactions_related(address, 'to', limit, offset) def get_transactions_from_address(self, address=None, limit=30, offset=0): """Query the list of transactions sent by an address Args: address (str): address limit (int): number of transactions expected to be returned offset (int): index of the starting transaction Returns: Transactions list """ return self.get_transactions_related(address, 'from', limit, offset) def get_transaction_info(self, tx_id): """Query transaction fee based on id Args: tx_id (str): Transaction Id Returns: Transaction fee,block height and block creation time """ response = self.tron.manager.request('/walletsolidity/gettransactioninfobyid', { 'value': tx_id }) return response def get_band_width(self, address=None): """Query bandwidth information. Args: address (str): address Returns: Bandwidth information for the account. If a field doesn't appear, then the corresponding value is 0. { "freeNetUsed": 557, "freeNetLimit": 5000, "NetUsed": 353, "NetLimit": 5239157853, "TotalNetLimit": 43200000000, "TotalNetWeight": 41228 } """ if address is None: address = self.tron.default_address.hex if not self.tron.isAddress(address): raise InvalidTronError('Invalid address provided') response = self.tron.manager.request('/wallet/getaccountnet', { 'address': self.tron.address.to_hex(address) }) free_net_limit = 0 if 'freeNetLimit' not in response else response['freeNetLimit'] free_net_used = 0 if 'freeNetUsed' not in response else response['freeNetUsed'] net_limit = 0 if 'NetLimit' not in response else response['NetLimit'] net_used = 0 if 'NetUsed' not in response else response['NetUsed'] return (free_net_limit - free_net_used) + (net_limit - net_used) def get_transaction_count(self): """Count all transactions on the network Note: Possible delays Returns: Total number of transactions. """ response = self.tron.manager.request('/wallet/totaltransaction') return response.get('num') def send(self, to, amount, options=None): """Send funds to the Tron account (option 2)""" return self.send_transaction(to, amount, options) def send_trx(self, to, amount, options=None): """Send funds to the Tron account (option 3)""" return self.send_transaction(to, amount, options) def send_transaction(self, to, amount, options=None): """Send an asset to another account. Will create and broadcast the transaction if a private key is provided. Args: to (str): Address to send TRX to. amount (float): Amount of TRX to send. options (Any, optional): Options """ if options is None: options = {} if 'from' not in options: options = assoc(options, 'from', self.tron.default_address.hex) tx = self.tron.transaction_builder.send_transaction( to, amount, options['from'] ) # If a comment is attached to the transaction, # in this case adding to the object if 'message' in options: tx['raw_data']['data'] = self.tron.toHex(text=str(options['message'])) sign = self.sign(tx) result = self.broadcast(sign) return result def send_token(self, to, amount, token_id=None, account=None): """Transfer Token Args: to (str): is the recipient address amount (float): is the amount of token to transfer token_id (str): Token Name(NOT SYMBOL) account: (str): is the address of the withdrawal account Returns: Token transfer Transaction raw data """ if account is None: account = self.tron.default_address.hex tx = self.tron.transaction_builder.send_token( to, amount, token_id, account ) sign = self.sign(tx) result = self.broadcast(sign) return result def freeze_balance(self, amount=0, duration=3, resource='BANDWIDTH', account=None): """ Freezes an amount of TRX. Will give bandwidth OR Energy and TRON Power(voting rights) to the owner of the frozen tokens. Args: amount (int): number of frozen trx duration (int): duration in days to be frozen resource (str): type of resource, must be either "ENERGY" or "BANDWIDTH" account (str): address that is freezing trx account """ if account is None: account = self.tron.default_address.hex transaction = self.tron.transaction_builder.freeze_balance( amount, duration, resource, account ) sign = self.sign(transaction) response = self.broadcast(sign) return response def unfreeze_balance(self, resource='BANDWIDTH', account=None): """ Unfreeze TRX that has passed the minimum freeze duration. Unfreezing will remove bandwidth and TRON Power. Args: resource (str): type of resource, must be either "ENERGY" or "BANDWIDTH" account (str): address that is freezing trx account """ if account is None: account = self.tron.default_address.hex transaction = self.tron.transaction_builder.unfreeze_balance( resource, account ) sign = self.sign(transaction) response = self.broadcast(sign) return response def online_sign(self, transaction: dict): """Online transaction signature Sign the transaction, the api has the risk of leaking the private key, please make sure to call the api in a secure environment Warnings: Do not use this in any web / user-facing applications. This will expose the private key. Args: transaction (dict): transaction details """ if 'signature' in transaction: raise TronError('Transaction is already signed') address = self.tron.address.from_private_key(self.tron.private_key).hex.lower() owner_address = transaction['raw_data']['contract'][0]['parameter']['value']['owner_address'] if address != owner_address: raise ValueError('Private key does not match address in transaction') return self.tron.manager.request('/wallet/gettransactionsign', { 'transaction': transaction, 'privateKey': self.tron.private_key }) def sign(self, transaction: Any, use_tron: bool = True, multisig: bool = False): """Safe method for signing your transaction Warnings: method: online_sign() - Use only in extreme cases. Args: transaction (Any): transaction details use_tron (bool): is Tron header multisig (bool): multi sign """ if is_string(transaction): if not is_hex(transaction): raise TronError('Expected hex message input') # Determine which header to attach to the message # before encrypting or decrypting header = TRX_MESSAGE_HEADER if use_tron else ETH_MESSAGE_HEADER header += str(len(transaction)) message_hash = self.tron.keccak(text=header+transaction) signed_message = Account.sign_hash(self.tron.toHex(message_hash), self.tron.private_key) return signed_message if not multisig and 'signature' in transaction: raise TronError('Transaction is already signed') try: if not multisig: address = self.tron.address.from_private_key(self.tron.private_key).hex.lower() owner_address = transaction['raw_data']['contract'][0]['parameter']['value']['owner_address'] if address != owner_address: raise ValueError('Private key does not match address in transaction') # This option deals with signing of transactions, and writing to the array signed_tx = Account.sign_hash( transaction['txID'], self.tron.private_key ) signature = signed_tx['signature'].hex()[2:] # support multi sign if 'signature' in transaction and is_list(transaction['signature']): if not transaction['signature'].index(signature): transaction['signature'].append(signature) else: transaction['signature'] = [signature] return transaction except ValueError as err: raise InvalidTronError(err) def broadcast(self, signed_transaction): """Broadcast the signed transaction Args: signed_transaction (object): signed transaction contract data """ if not is_object(signed_transaction): raise InvalidTronError('Invalid transaction provided') if 'signature' not in signed_transaction: raise TronError('Transaction is not signed') response = self.tron.manager.request('/wallet/broadcasttransaction', signed_transaction) if 'result' in response: response.update({ 'transaction': signed_transaction }) return response def sign_and_broadcast(self, transaction: Any): """Sign and send to the network Args: transaction (Any): transaction details """ if not is_object(transaction): raise TronError('Invalid transaction provided') signed_tx = self.sign(transaction) return self.broadcast(signed_tx) def verify_message(self, message, signed_message=None, address=None, use_tron: bool = True): """ Get the address of the account that signed the message with the given hash. You must specify exactly one of: vrs or signature Args: message (str): The message in the format "hex" signed_message (AttributeDict): Signature address (str): is Address use_tron (bool): is Tron header """ if address is None: address = self.tron.default_address.base58 if not is_hex(message): raise TronError('Expected hex message input') # Determine which header to attach to the message # before encrypting or decrypting header = TRX_MESSAGE_HEADER if use_tron else ETH_MESSAGE_HEADER header += str(len(message)) message_hash = self.tron.keccak(text=header+message) recovered = Account.recover_hash(self.tron.toHex(message_hash), signed_message.signature) tron_address = '41' + recovered[2:] base58address = self.tron.address.from_hex(tron_address).decode() if base58address == address: return True raise ValueError('Signature does not match') def update_account(self, account_name, address=None): """Modify account name Note: Username is allowed to edit only once. Args: account_name (str): name of the account address (str): address """ if address is None: address = self.tron.default_address.hex transaction = self.tron.transaction_builder.update_account( account_name, address ) sign = self.sign(transaction) response = self.broadcast(sign) return response def apply_for_sr(self, url, address): """Apply to become a super representative Note: Applied to become a super representative. Cost 9999 TRX. Args: url (str): official website address address (str): address """ if address is None: address = self.tron.default_address.hex transaction = self.tron.transaction_builder.apply_for_sr( url, address ) sign = self.sign(transaction) response = self.broadcast(sign) return response def list_nodes(self): """List the nodes which the api fullnode is connecting on the network""" response = self.tron.manager.request('/wallet/listnodes') callback = map(lambda x: { 'address': '{}:{}'.format(self.tron.toText(x['address']['host']), str(x['address']['port'])) }, response['nodes']) return list(callback) def get_tokens_issued_by_address(self, address): """List the tokens issued by an account. Args: address (str): address Returns: The token issued by the account. An account can issue only one token. """ if not self.tron.isAddress(address): raise InvalidTronError('Invalid address provided') address = self.tron.address.to_hex(address) return self.tron.manager.request('/wallet/getassetissuebyaccount', { 'address': address }) def get_token_from_id(self, token_id: str): """Query token by name. Args: token_id (str): The name of the token """ if not isinstance(token_id, str) or not len(token_id): raise InvalidTronError('Invalid token ID provided') return self.tron.manager.request('/wallet/getassetissuebyname', { 'value': self.tron.toHex(text=token_id) }) def get_block_range(self, start, end): """Query a range of blocks by block height Args: start (int): starting block height, including this block end (int): ending block height, excluding that block """ if not is_integer(start) or start < 0: raise InvalidTronError('Invalid start of range provided') if not is_integer(end) or end <= start: raise InvalidTronError('Invalid end of range provided') response = self.tron.manager.request('/wallet/getblockbylimitnext', { 'startNum': int(start), 'endNum': int(end) + 1 }, 'post') return response.get('block') def get_latest_blocks(self, num=1): """Query the latest blocks Args: num (int): the number of blocks to query """ if not is_integer(num) or num <= 0: raise InvalidTronError('Invalid limit provided') response = self.tron.manager.request('/wallet/getblockbylatestnum', { 'num': num }) return response.get('block') def list_super_representatives(self): """Query the list of Super Representatives""" response = self.tron.manager.request('/wallet/listwitnesses') return response.get('witnesses') def list_tokens(self, limit=0, offset=0): """Query the list of Tokens with pagination Args: limit (int): index of the starting Token offset (int): number of Tokens expected to be returned Returns: List of Tokens """ if not is_integer(limit) or (limit and offset < 1): raise InvalidTronError('Invalid limit provided') if not is_integer(offset) or offset < 0: raise InvalidTronError('Invalid offset provided') if not limit: return self.tron.manager.request('/wallet/getassetissuelist').get('assetIssue') return self.tron.manager.request('/wallet/getpaginatedassetissuelist', { 'limit': int(limit), 'offset': int(offset) }) def time_until_next_vote_cycle(self): """Get the time of the next Super Representative vote Returns: Number of milliseconds until the next voting time. """ num = self.tron.manager.request('/wallet/getnextmaintenancetime').get('num') if num == -1: raise Exception('Failed to get time until next vote cycle') return math.floor(num / 1000) def get_contract(self, contract_address): """Queries a contract's information from the blockchain. Args: contract_address (str): contract address Returns: SmartContract object. """ if not self.tron.isAddress(contract_address): raise InvalidTronError('Invalid contract address provided') return self.tron.manager.request('/wallet/getcontract', { 'value': self.tron.address.to_hex(contract_address) }) def contract(self, address=None, **kwargs): """Work with a contract Args: address (str): TRON Address **kwargs (any): details (bytecode, abi) """ factory_class = kwargs.pop('contract_factory_class', self.default_contract_factory) contract_factory = factory_class.factory(self.tron, **kwargs) if address: return contract_factory(address) return contract_factory def validate_address(self, address, _is_hex=False): """Validate address Args: address (str): The address, should be in base58checksum _is_hex (bool): hexString or base64 format """ if _is_hex: address = self.tron.address.to_hex(address) return self.tron.manager.request('/wallet/validateaddress', { 'address': address }) def get_chain_parameters(self): """Getting chain parameters""" return self.tron.manager.request('/wallet/getchainparameters') def get_exchange_by_id(self, exchange_id): """Find exchange by id Args: exchange_id (str): ID Exchange """ if not isinstance(exchange_id, int) or exchange_id < 0: raise InvalidTronError('Invalid exchangeID provided') return self.tron.manager.request('/wallet/getexchangebyid', { 'id': exchange_id }) def get_list_exchangers(self): """Get list exchangers""" return self.tron.manager.request('/wallet/listexchanges') def get_proposal(self, proposal_id): """Query proposal based on id Args: proposal_id (int): ID """ if not isinstance(proposal_id, int) or proposal_id < 0: raise InvalidTronError('Invalid proposalID provided') return self.tron.manager.request('/wallet/getproposalbyid', { 'id': int(proposal_id) }) def list_proposals(self): """Query all proposals Returns: Proposal list information """ return self.tron.manager.request('/wallet/listproposals') def vote_proposal(self, proposal_id, has_approval, voter_address): """Proposal approval Args: proposal_id (int): proposal id has_approval (bool): Approved voter_address (str): Approve address Returns: Approval of the proposed transaction """ if voter_address is None: voter_address = self.tron.default_address.hex transaction = self.tron.transaction_builder.vote_proposal( proposal_id, has_approval, voter_address ) sign = self.sign(transaction) response = self.broadcast(sign) return response def proposal_delete(self, proposal_id: int, issuer_address: str): """Delete proposal Args: proposal_id (int): proposal id issuer_address (str): delete the person's address Results: Delete the proposal's transaction """ if issuer_address is None: issuer_address = self.tron.default_address.hex transaction = self.tron.transaction_builder.delete_proposal( proposal_id, issuer_address ) sign = self.sign(transaction) response = self.broadcast(sign) return response def list_exchanges_paginated(self, limit=10, offset=0): """Paged query transaction pair list Args: limit (int): number of trading pairs expected to be returned. offset (int): index of the starting trading pair """ return self.tron.manager.request('/wallet/listexchangespaginated', { 'limit': limit, 'offset': offset }) def get_node_info(self): """Get info about thre node""" return self.tron.manager.request('/wallet/getnodeinfo') def get_token_list_name(self, token_id: str) -> any: """Query token list by name. Args: token_id (str): The name of the token """ if not is_string(token_id): raise ValueError('Invalid token ID provided') return self.tron.manager.request('/wallet/getassetissuelistbyname', { 'value': self.tron.toHex(text=token_id) }) def get_token_by_id(self, token_id: str) -> any: """Query token by id. Args: token_id (str): The id of the token, it's a string """ if not is_string(token_id): raise ValueError('Invalid token ID provided') return self.tron.manager.request('/wallet/getassetissuebyid', { 'value': token_id })