Showing preview only (203K chars total). Download the full file or copy to clipboard to get everything.
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<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(-(?P<stage>[^.]*)\.(?P<devnum>\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 <https://github.com/pypa/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['<stdin>: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 <https://github.com/iexbase/tron-api-python/tree/master/examples>`__.
Documentation
=============
Documentation is available at `docs <https://tronapi-for-python.readthedocs.io/en/latest/>`__.
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 <https://pypi.org/project/ipython/>`_.
Installation
------------
TronAPI can be installed (preferably in a :ref:`virtualenv <setup_environment>`)
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['<stdin>: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<M>')
registry.unregister('bytes')
registry.unregister('string')
registry.register(
BaseEquals('bytes', with_sub=True),
BytesEncoder, decoding.BytesDecoder,
label='bytes<M>',
)
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("<cycle>")
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 = '<Function %s' % abi_to_signature(self.abi)
if self.arguments is not None:
_repr += ' bound to %r' % (self.arguments,)
return _repr + '>'
return '<Function %s>' % 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.manage
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
SYMBOL INDEX (335 symbols across 21 files)
FILE: tronapi/common/abi.py
function filter_by_argument_name (line 108) | def filter_by_argument_name(argument_names, contract_abi):
function process_type (line 131) | def process_type(type_str):
function collapse_type (line 168) | def collapse_type(base, sub, arrlist):
function filter_by_type (line 172) | def filter_by_type(_type, contract_abi):
function filter_by_name (line 176) | def filter_by_name(name, contract_abi):
function get_abi_input_types (line 188) | def get_abi_input_types(abi):
function get_abi_output_types (line 195) | def get_abi_output_types(abi):
function get_abi_input_names (line 202) | def get_abi_input_names(abi):
function length_of_array_type (line 209) | def length_of_array_type(abi_type):
function get_fallback_func_abi (line 222) | def get_fallback_func_abi(contract_abi):
function fallback_func_abi_exists (line 230) | def fallback_func_abi_exists(contract_abi):
function get_constructor_abi (line 234) | def get_constructor_abi(contract_abi):
function is_recognized_type (line 246) | def is_recognized_type(abi_type):
function is_bool_type (line 250) | def is_bool_type(abi_type):
function is_uint_type (line 254) | def is_uint_type(abi_type):
function is_int_type (line 258) | def is_int_type(abi_type):
function is_address_type (line 262) | def is_address_type(abi_type):
function is_bytes_type (line 266) | def is_bytes_type(abi_type):
function is_string_type (line 270) | def is_string_type(abi_type):
function is_length (line 275) | def is_length(target_length, value):
function size_of_type (line 279) | def size_of_type(abi_type):
function is_array_type (line 296) | def is_array_type(abi_type):
function sub_type_of_array_type (line 300) | def sub_type_of_array_type(abi_type):
function is_probably_enum (line 309) | def is_probably_enum(abi_type):
function normalize_event_input_types (line 314) | def normalize_event_input_types(abi_args):
function abi_to_signature (line 324) | def abi_to_signature(abi):
function filter_by_argument_count (line 334) | def filter_by_argument_count(num_arguments, contract_abi):
function filter_by_encodability (line 343) | def filter_by_encodability(args, kwargs, contract_abi):
class AcceptsHexStrMixin (line 351) | class AcceptsHexStrMixin:
method validate_value (line 352) | def validate_value(self, value):
class ByteStringEncoder (line 365) | class ByteStringEncoder(AcceptsHexStrMixin, encoding.ByteStringEncoder):
class BytesEncoder (line 369) | class BytesEncoder(AcceptsHexStrMixin, encoding.BytesEncoder):
class TextStringEncoder (line 373) | class TextStringEncoder(encoding.TextStringEncoder):
method validate_value (line 375) | def validate_value(cls, value):
function check_if_arguments_can_be_encoded (line 418) | def check_if_arguments_can_be_encoded(function_abi, args, kwargs):
function merge_args_and_kwargs (line 435) | def merge_args_and_kwargs(function_abi, args, kwargs):
function abi_sub_tree (line 491) | def abi_sub_tree(data_type, data_value):
function map_abi_data (line 516) | def map_abi_data(normalizers, types, data):
function abi_data_tree (line 550) | def abi_data_tree(types, data):
function data_tree_map (line 569) | def data_tree_map(func, data_tree):
class ABITypedData (line 584) | class ABITypedData(namedtuple('ABITypedData', 'abi_type, data')):
method __new__ (line 603) | def __new__(cls, iterable):
function strip_abi_type (line 607) | def strip_abi_type(elements):
FILE: tronapi/common/account.py
class Account (line 19) | class Account:
method create (line 21) | def create():
method sign_hash (line 26) | def sign_hash(message_hash, private_key):
method recover_hash (line 33) | def recover_hash(message_hash, signature):
class Address (line 40) | class Address(object):
method from_hex (line 42) | def from_hex(address):
method to_hex (line 50) | def to_hex(address):
method from_private_key (line 58) | def from_private_key(private_key):
class PrivateKey (line 62) | class PrivateKey(object):
method __init__ (line 63) | def __init__(self, private_key):
method private_key (line 79) | def private_key(self):
method public_key (line 84) | def public_key(self) -> str:
method address (line 89) | def address(self):
method __str__ (line 103) | def __str__(self):
method __bytes__ (line 106) | def __bytes__(self):
FILE: tronapi/common/blocks.py
function is_hex_encoded_block_hash (line 13) | def is_hex_encoded_block_hash(value):
function is_hex_encoded_block_number (line 19) | def is_hex_encoded_block_number(value):
function select_method_for_block (line 31) | def select_method_for_block(value, if_hash, if_number):
FILE: tronapi/common/contracts.py
class FallbackFn (line 47) | class FallbackFn:
function find_matching_fn_abi (line 51) | def find_matching_fn_abi(abi, fn_identifier=None, args=None, kwargs=None):
function encode_abi (line 109) | def encode_abi(tron, abi, arguments, data=None):
function get_function_info (line 149) | def get_function_info(fn_name, contract_abi=None, fn_abi=None, args=None...
FILE: tronapi/common/datastructures.py
class ReadableAttributeDict (line 22) | class ReadableAttributeDict(Mapping):
method __init__ (line 27) | def __init__(self, dictionary, *args, **kwargs):
method __getitem__ (line 31) | def __getitem__(self, key):
method __iter__ (line 34) | def __iter__(self):
method __len__ (line 37) | def __len__(self):
method __repr__ (line 40) | def __repr__(self):
method _repr_pretty_ (line 43) | def _repr_pretty_(self, builder, cycle):
method _apply_if_mapping (line 55) | def _apply_if_mapping(cls, value):
method recursive (line 62) | def recursive(cls, value):
class MutableAttributeDict (line 66) | class MutableAttributeDict(MutableMapping, ReadableAttributeDict):
method __setitem__ (line 68) | def __setitem__(self, key, val):
method __delitem__ (line 71) | def __delitem__(self, key):
class AttributeDict (line 75) | class AttributeDict(ReadableAttributeDict, Hashable):
method __setattr__ (line 80) | def __setattr__(self, attr, val):
method __delattr__ (line 86) | def __delattr__(self, key):
method __hash__ (line 89) | def __hash__(self):
method __eq__ (line 92) | def __eq__(self, other):
class NamedElementOnion (line 99) | class NamedElementOnion(Mapping):
method __init__ (line 104) | def __init__(self, init_elements, valid_element=callable):
method add (line 112) | def add(self, element, name=None):
method inject (line 124) | def inject(self, element, name=None, layer=None):
method clear (line 155) | def clear(self):
method replace (line 158) | def replace(self, old, new):
method remove (line 169) | def remove(self, old):
method _replace_with_new_name (line 174) | def _replace_with_new_name(self, old, new):
method __iter__ (line 186) | def __iter__(self):
method __add__ (line 192) | def __add__(self, other):
method __contains__ (line 199) | def __contains__(self, element):
method __getitem__ (line 202) | def __getitem__(self, element):
method __len__ (line 205) | def __len__(self):
method __reversed__ (line 208) | def __reversed__(self):
FILE: tronapi/common/datatypes.py
function verify_attr (line 15) | def verify_attr(class_name, key, namespace):
class PropertyCheckingFactory (line 24) | class PropertyCheckingFactory(type):
method __init__ (line 25) | def __init__(cls, name, bases, namespace, **kargs):
method __new__ (line 30) | def __new__(mcs, name, bases, namespace, normalizers=None):
FILE: tronapi/common/encoding.py
function hex_encode_abi_type (line 50) | def hex_encode_abi_type(abi_type, value, force_size=None):
function to_hex_twos_compliment (line 82) | def to_hex_twos_compliment(value, bit_size):
function to_hex_with_size (line 95) | def to_hex_with_size(value, bit_size):
function pad_hex (line 100) | def pad_hex(value, bit_size):
function trim_hex (line 106) | def trim_hex(hexstr):
function to_int (line 114) | def to_int(value=None, hexstr=None, text=None):
function text_if_str (line 140) | def text_if_str(to_type, text_or_primitive):
function to_text (line 149) | def to_text(primitive=None, hexstr=None, text=None):
function to_bytes (line 166) | def to_bytes(primitive=None, hexstr=None, text=None):
function to_4byte_hex (line 184) | def to_4byte_hex(hex_or_str_or_bytes: Union[int, str, bytes]) -> str:
class FriendlyJsonSerialize (line 195) | class FriendlyJsonSerialize:
method _json_mapping_errors (line 203) | def _json_mapping_errors(self, mapping):
method _json_list_errors (line 210) | def _json_list_errors(self, iterable):
method _friendly_json_encode (line 217) | def _friendly_json_encode(self, obj, cls=None):
method json_decode (line 232) | def json_decode(json_str):
method json_encode (line 242) | def json_encode(self, obj, cls=None):
class TronJsonEncoder (line 249) | class TronJsonEncoder(json.JSONEncoder):
method default (line 250) | def default(self, obj):
function to_json (line 258) | def to_json(obj: object) -> object:
FILE: tronapi/common/formatters.py
function apply_formatters_to_dict (line 22) | def apply_formatters_to_dict(formatters, value):
function recursive_map (line 34) | def recursive_map(func, data):
function map_collection (line 47) | def map_collection(func, collection):
FILE: tronapi/common/normalizers.py
function implicitly_identity (line 25) | def implicitly_identity(to_wrap):
function normalize_abi (line 37) | def normalize_abi(abi):
function normalize_bytecode (line 44) | def normalize_bytecode(bytecode):
function abi_address_to_hex (line 51) | def abi_address_to_hex(abi_type, data):
function abi_string_to_text (line 59) | def abi_string_to_text(abi_type, data):
function abi_bytes_to_bytes (line 65) | def abi_bytes_to_bytes(abi_type, data):
function addresses_checksummed (line 72) | def addresses_checksummed(abi_type, data):
function to_checksum_address (line 77) | def to_checksum_address(address: str):
function abi_resolver (line 82) | def abi_resolver(abi_type, val):
FILE: tronapi/common/threads.py
class Timeout (line 8) | class Timeout(Exception):
method __init__ (line 17) | def __init__(self, seconds=None, exception=None, *args, **kwargs):
method __enter__ (line 21) | def __enter__(self):
method __exit__ (line 25) | def __exit__(self, exc_type, exc_val, exc_tb):
method __str__ (line 28) | def __str__(self):
method expire_at (line 34) | def expire_at(self):
method start (line 41) | def start(self):
method check (line 47) | def check(self):
method cancel (line 63) | def cancel(self):
method sleep (line 66) | def sleep(self, seconds):
class ThreadWithReturn (line 71) | class ThreadWithReturn(threading.Thread):
method __init__ (line 72) | def __init__(self, target=None, args=None, kwargs=None):
method run (line 82) | def run(self):
method get (line 85) | def get(self, timeout=None):
class TimerClass (line 93) | class TimerClass(threading.Thread):
method __init__ (line 94) | def __init__(self, interval, callback, *args):
method run (line 101) | def run(self):
method stop (line 106) | def stop(self):
function spawn (line 110) | def spawn(target, *args, thread_class=ThreadWithReturn, **kwargs):
FILE: tronapi/common/transactions.py
function wait_for_transaction_id (line 4) | def wait_for_transaction_id(tron, tx_id, timeout=120, poll_latency=0.1):
FILE: tronapi/common/validation.py
function _prepare_selector_collision_msg (line 43) | def _prepare_selector_collision_msg(duplicates):
function is_valid_url (line 50) | def is_valid_url(value):
function validate_abi (line 69) | def validate_abi(abi):
function validate_abi_type (line 92) | def validate_abi_type(abi_type):
function validate_abi_value (line 100) | def validate_abi_value(abi_type, value):
function validate_address (line 155) | def validate_address(value):
function has_one_val (line 186) | def has_one_val(*args, **kwargs):
function assert_one_val (line 192) | def assert_one_val(*args, **kwargs):
FILE: tronapi/contract.py
class NonExistentFallbackFunction (line 51) | class NonExistentFallbackFunction:
method _raise_exception (line 53) | def _raise_exception():
method __getattr__ (line 56) | def __getattr__(self, attr):
class ContractFunction (line 60) | class ContractFunction:
method __init__ (line 70) | def __init__(self, abi=None):
method __call__ (line 74) | def __call__(self, *args, **kwargs):
method _set_function_info (line 88) | def _set_function_info(self):
method factory (line 106) | def factory(cls, class_name, **kwargs):
method __repr__ (line 109) | def __repr__(self):
class ContractFunctions (line 118) | class ContractFunctions:
method __init__ (line 122) | def __init__(self, abi, tron, address=None):
method __iter__ (line 137) | def __iter__(self):
method __getattr__ (line 144) | def __getattr__(self, function_name):
method __getitem__ (line 158) | def __getitem__(self, function_name):
class Contract (line 162) | class Contract:
method __init__ (line 178) | def __init__(self, address=None):
method factory (line 198) | def factory(cls, tron, class_name=None, **kwargs):
method deploy (line 221) | def deploy(cls, **kwargs):
method constructor (line 247) | def constructor(cls):
method encodeABI (line 258) | def encodeABI(cls, fn_name, args=None, kwargs=None, data=None):
method get_fallback_function (line 272) | def get_fallback_function(abi, tron, address=None):
method all_functions (line 284) | def all_functions(self):
method get_function_by_signature (line 290) | def get_function_by_signature(self, signature):
method find_functions_by_name (line 304) | def find_functions_by_name(self, fn_name):
method get_function_by_name (line 313) | def get_function_by_name(self, fn_name):
method get_function_by_selector (line 318) | def get_function_by_selector(self, selector):
method decode_function_input (line 326) | def decode_function_input(self, data):
method find_functions_by_args (line 337) | def find_functions_by_args(self, *args):
method get_function_by_args (line 346) | def get_function_by_args(self, *args):
class ContractConstructor (line 351) | class ContractConstructor:
method __init__ (line 356) | def __init__(self, tron, abi, bytecode):
method check_forbidden_keys_in_transaction (line 362) | def check_forbidden_keys_in_transaction(transaction, forbidden_keys=No...
method transact (line 368) | def transact(self, **kwargs):
function find_functions_by_identifier (line 385) | def find_functions_by_identifier(contract_abi, tron, address, callable_c...
function get_function_by_identifier (line 401) | def get_function_by_identifier(fns, identifier):
FILE: tronapi/exceptions.py
class TronError (line 8) | class TronError(Exception):
class InvalidTronError (line 12) | class InvalidTronError(TronError):
class FallbackNotFound (line 16) | class FallbackNotFound(Exception):
class MismatchedABI (line 23) | class MismatchedABI(Exception):
class InvalidAddress (line 31) | class InvalidAddress(ValueError):
class NoABIFunctionsFound (line 38) | class NoABIFunctionsFound(AttributeError):
class ValidationError (line 45) | class ValidationError(Exception):
class TransportError (line 52) | class TransportError(TronError):
method status_code (line 61) | def status_code(self):
method error (line 65) | def error(self):
method info (line 69) | def info(self):
method url (line 73) | def url(self):
class HttpError (line 77) | class HttpError(TransportError):
class BadRequest (line 81) | class BadRequest(TransportError):
class NotFoundError (line 85) | class NotFoundError(TransportError):
class ServiceUnavailable (line 89) | class ServiceUnavailable(TransportError):
class GatewayTimeout (line 93) | class GatewayTimeout(TransportError):
class TimeExhausted (line 97) | class TimeExhausted(Exception):
FILE: tronapi/main.py
class Tron (line 63) | class Tron:
method __init__ (line 85) | def __init__(self, **kwargs):
method default_block (line 128) | def default_block(self):
method default_block (line 132) | def default_block(self, block_id):
method providers (line 144) | def providers(self):
method private_key (line 149) | def private_key(self):
method private_key (line 154) | def private_key(self, value: str) -> None:
method default_address (line 169) | def default_address(self) -> AttributeDict:
method default_address (line 174) | def default_address(self, address: str) -> None:
method get_event_result (line 199) | def get_event_result(self, **kwargs):
method get_event_transaction_id (line 265) | def get_event_transaction_id(self, tx_id):
method address (line 275) | def address(self) -> Address:
method create_account (line 287) | def create_account(self) -> PrivateKey:
method is_valid_provider (line 298) | def is_valid_provider(provider) -> bool:
method solidity_sha3 (line 306) | def solidity_sha3(self, abi_types, values):
method keccak (line 339) | def keccak(primitive=None, text=None, hexstr=None):
method is_connected (line 353) | def is_connected(self):
FILE: tronapi/manager.py
class TronManager (line 32) | class TronManager(object):
method __init__ (line 40) | def __init__(self, tron, providers):
method providers (line 66) | def providers(self):
method providers (line 73) | def providers(self, value) -> None:
method full_node (line 80) | def full_node(self) -> HttpProvider:
method solidity_node (line 89) | def solidity_node(self) -> HttpProvider:
method event_server (line 98) | def event_server(self) -> HttpProvider:
method request (line 106) | def request(self, url, params=None, method=None):
method is_connected (line 130) | def is_connected(self):
FILE: tronapi/module.py
class Module (line 8) | class Module:
method __init__ (line 11) | def __init__(self, tron) -> None:
method attach (line 15) | def attach(cls, target, module_name: str = None) -> None:
FILE: tronapi/providers/base.py
class BaseProvider (line 11) | class BaseProvider(object):
method status_page (line 15) | def status_page(self):
method status_page (line 20) | def status_page(self, page):
method _http_default_headers (line 24) | def _http_default_headers():
method format_user_agent (line 32) | def format_user_agent(name=None):
FILE: tronapi/providers/http.py
class HttpProvider (line 37) | class HttpProvider(BaseProvider):
method __init__ (line 40) | def __init__(self, node_url, request_kwargs=None):
method get_request_kwargs (line 66) | def get_request_kwargs(self):
method request (line 73) | def request(self, path, json=None, params=None, method=None):
method is_connected (line 96) | def is_connected(self) -> bool:
method _request (line 112) | def _request(self, **kwargs):
method __error_manager (line 140) | def __error_manager(data):
FILE: tronapi/transactionbuilder.py
class TransactionBuilder (line 34) | class TransactionBuilder(object):
method __init__ (line 35) | def __init__(self, tron):
method send_transaction (line 38) | def send_transaction(self, to, amount, account=None):
method send_token (line 76) | def send_token(self, to, amount, token_id, account=None):
method freeze_balance (line 124) | def freeze_balance(self, amount, duration, resource, account=None):
method unfreeze_balance (line 166) | def unfreeze_balance(self, resource='BANDWIDTH', account=None):
method purchase_token (line 197) | def purchase_token(self, to: str, token_id: str, amount: int, buyer=No...
method withdraw_block_rewards (line 231) | def withdraw_block_rewards(self, address: str = None):
method apply_for_sr (line 249) | def apply_for_sr(self, url, address=None):
method vote (line 273) | def vote(self, votes: List[Tuple[str, int]], voter_address: str = None):
method create_proposal (line 317) | def create_proposal(self, parameters: Any, issuer_address=None):
method vote_proposal (line 347) | def vote_proposal(self, proposal_id, has_approval, voter_address=None):
method delete_proposal (line 376) | def delete_proposal(self, proposal_id: int, issuer_address: str = None):
method update_account (line 403) | def update_account(self, account_name, account: str = None):
method create_smart_contract (line 434) | def create_smart_contract(self, **kwargs):
method trigger_smart_contract (line 508) | def trigger_smart_contract(self, **kwargs):
method create_trx_exchange (line 599) | def create_trx_exchange(self,
method create_token_exchange (line 633) | def create_token_exchange(self,
method inject_exchange_tokens (line 668) | def inject_exchange_tokens(self,
method create_token (line 702) | def create_token(self, **kwargs):
method withdraw_exchange_tokens (line 837) | def withdraw_exchange_tokens(self,
method trade_exchange_tokens (line 871) | def trade_exchange_tokens(self,
method update_setting (line 912) | def update_setting(self,
method update_energy_limit (line 946) | def update_energy_limit(self,
method check_permissions (line 980) | def check_permissions(self, permissions, _type):
method update_account_permissions (line 998) | def update_account_permissions(self, owner_address=None,
FILE: tronapi/trx.py
class Trx (line 37) | class Trx(Module):
method get_current_block (line 40) | def get_current_block(self):
method get_confirmed_current_block (line 44) | def get_confirmed_current_block(self):
method get_block (line 48) | def get_block(self, block: Any = None):
method get_transaction_count_by_blocknum (line 80) | def get_transaction_count_by_blocknum(self, num: int):
method get_block_transaction_count (line 93) | def get_block_transaction_count(self, block: Any):
method get_transaction_from_block (line 106) | def get_transaction_from_block(self, block: Any, index: int = 0):
method wait_for_transaction_id (line 123) | def wait_for_transaction_id(self,
method get_transaction (line 155) | def get_transaction(self, transaction_id: str,
method get_account_by_id (line 174) | def get_account_by_id(self, account_id: str, options: object):
method get_account_info_by_id (line 177) | def get_account_info_by_id(self, account_id: str, options: object):
method get_unconfirmed_account_by_id (line 191) | def get_unconfirmed_account_by_id(self, account_id: str):
method get_account_resource (line 197) | def get_account_resource(self, address=None):
method get_account (line 218) | def get_account(self, address=None):
method get_balance (line 236) | def get_balance(self, address=None, is_float=False):
method get_transactions_related (line 253) | def get_transactions_related(self, address, direction='all', limit=30,...
method get_transactions_to_address (line 304) | def get_transactions_to_address(self, address=None, limit=30, offset=0):
method get_transactions_from_address (line 318) | def get_transactions_from_address(self, address=None, limit=30, offset...
method get_transaction_info (line 332) | def get_transaction_info(self, tx_id):
method get_band_width (line 348) | def get_band_width(self, address=None):
method get_transaction_count (line 385) | def get_transaction_count(self):
method send (line 396) | def send(self, to, amount, options=None):
method send_trx (line 400) | def send_trx(self, to, amount, options=None):
method send_transaction (line 404) | def send_transaction(self, to, amount, options=None):
method send_token (line 436) | def send_token(self, to, amount, token_id=None, account=None):
method freeze_balance (line 463) | def freeze_balance(self, amount=0, duration=3, resource='BANDWIDTH', a...
method unfreeze_balance (line 491) | def unfreeze_balance(self, resource='BANDWIDTH', account=None):
method online_sign (line 514) | def online_sign(self, transaction: dict):
method sign (line 542) | def sign(self, transaction: Any, use_tron: bool = True, multisig: bool...
method broadcast (line 597) | def broadcast(self, signed_transaction):
method sign_and_broadcast (line 619) | def sign_and_broadcast(self, transaction: Any):
method verify_message (line 631) | def verify_message(self, message, signed_message=None, address=None, u...
method update_account (line 664) | def update_account(self, account_name, address=None):
method apply_for_sr (line 685) | def apply_for_sr(self, url, address):
method list_nodes (line 707) | def list_nodes(self):
method get_tokens_issued_by_address (line 717) | def get_tokens_issued_by_address(self, address):
method get_token_from_id (line 738) | def get_token_from_id(self, token_id: str):
method get_block_range (line 752) | def get_block_range(self, start, end):
method get_latest_blocks (line 773) | def get_latest_blocks(self, num=1):
method list_super_representatives (line 789) | def list_super_representatives(self):
method list_tokens (line 794) | def list_tokens(self, limit=0, offset=0):
method time_until_next_vote_cycle (line 819) | def time_until_next_vote_cycle(self):
method get_contract (line 833) | def get_contract(self, contract_address):
method contract (line 851) | def contract(self, address=None, **kwargs):
method validate_address (line 865) | def validate_address(self, address, _is_hex=False):
method get_chain_parameters (line 880) | def get_chain_parameters(self):
method get_exchange_by_id (line 884) | def get_exchange_by_id(self, exchange_id):
method get_list_exchangers (line 899) | def get_list_exchangers(self):
method get_proposal (line 903) | def get_proposal(self, proposal_id):
method list_proposals (line 917) | def list_proposals(self):
method vote_proposal (line 926) | def vote_proposal(self, proposal_id, has_approval, voter_address):
method proposal_delete (line 952) | def proposal_delete(self, proposal_id: int, issuer_address: str):
method list_exchanges_paginated (line 975) | def list_exchanges_paginated(self, limit=10, offset=0):
method get_node_info (line 988) | def get_node_info(self):
method get_token_list_name (line 992) | def get_token_list_name(self, token_id: str) -> any:
method get_token_by_id (line 1005) | def get_token_by_id(self, token_id: str) -> any:
Condensed preview — 50 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (205K chars).
[
{
"path": ".bumpversion.cfg",
"chars": 464,
"preview": "[bumpversion]\ncurrent_version = 3.1.5\ncommit = True\ntag = True\nparse = (?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)(-("
},
{
"path": ".coveragerc",
"chars": 41,
"preview": "[run]\nbranch = True\ninclude = */tronapi/*"
},
{
"path": ".gitignore",
"chars": 1210,
"preview": ".idea\n\n# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / "
},
{
"path": ".pyup.yml",
"chars": 228,
"preview": "# autogenerated pyup.io config file\n# see https://pyup.io/docs/configuration/ for all available options\n\nupdate: all\n\n# "
},
{
"path": ".travis.yml",
"chars": 360,
"preview": "sudo: required\nlanguage: python\nos: linux\ncache: pip\ndist: trusty\npython:\n - \"3.6\"\n - \"3.7-dev\"\n - \"nightly\" # curren"
},
{
"path": "LICENSE",
"chars": 1064,
"preview": "MIT License\n\nCopyright (c) 2018 iEXBase\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof"
},
{
"path": "README.rst",
"chars": 4586,
"preview": "===================\nTRON API for Python\n===================\n\nA Python API for interacting with the Tron (TRX)\n\n.. image:"
},
{
"path": "codecov.yml",
"chars": 942,
"preview": "# --------------------------------------------------------------------------------------------\n# Copyright (c) iEXBase. "
},
{
"path": "docs/index.rst",
"chars": 198,
"preview": "TronAPI\n=======\n\nTronAPI is a python library for interacting with Tron Protocol.\n\nContents\n--------\n\n.. toctree::\n :m"
},
{
"path": "docs/overview.rst",
"chars": 5158,
"preview": "Overview\n========\n\n.. contents:: :local:\n\nThe common entrypoint for interacting with the Tron library is the ``Tron``\nob"
},
{
"path": "docs/quickstart.rst",
"chars": 2122,
"preview": "Quickstart\n==========\n\n.. contents:: :local:\n\n.. NOTE:: All code starting with a ``$`` is meant to run on your terminal."
},
{
"path": "docs/releases.rst",
"chars": 0,
"preview": ""
},
{
"path": "docs/v2_migration.rst",
"chars": 1456,
"preview": "Migrating your code from v1 to v2\n=======================================\n\nChanges to base API convenience methods\n-----"
},
{
"path": "example.py",
"chars": 4095,
"preview": "import json\nimport logging\n\nfrom tronapi import Tron\n\nlogging.basicConfig(level=logging.DEBUG, format=\"%(asctime)s - %(l"
},
{
"path": "examples/account.py",
"chars": 913,
"preview": "\nimport logging\nfrom tronapi import Tron\n\nlogging.basicConfig(level=logging.DEBUG, format=\"%(asctime)s - %(levelname)s -"
},
{
"path": "examples/address-hex.py",
"chars": 562,
"preview": "from tronapi import Tron\nfrom tronapi import HttpProvider\n\nfull_node = HttpProvider('https://api.trongrid.io')\nsolidity_"
},
{
"path": "examples/amount.py",
"chars": 407,
"preview": "from tronapi import Tron\nfrom tronapi import HttpProvider\n\nfull_node = HttpProvider('https://api.trongrid.io')\nsolidity_"
},
{
"path": "examples/contract.py",
"chars": 1085,
"preview": "from tronapi import Tron, HttpProvider\nfrom solc import compile_source\n\nfull_node = HttpProvider('https://api.trongrid.i"
},
{
"path": "examples/custom-nodes.py",
"chars": 486,
"preview": "from tronapi import Tron\nfrom tronapi import HttpProvider\n\nfull_node = HttpProvider('https://api.trongrid.io')\nsolidity_"
},
{
"path": "examples/find-transaction.py",
"chars": 380,
"preview": "from tronapi import Tron\nfrom tronapi import HttpProvider\n\nfull_node = HttpProvider('https://api.trongrid.io')\nsolidity_"
},
{
"path": "examples/send-transaction.py",
"chars": 484,
"preview": "from tronapi import Tron\nfrom tronapi import HttpProvider\n\nfull_node = HttpProvider('https://api.trongrid.io')\nsolidity_"
},
{
"path": "examples/sign.py",
"chars": 642,
"preview": "from tronapi import Tron\nfrom tronapi import HttpProvider\n\nfull_node = HttpProvider('https://api.trongrid.io')\nsolidity_"
},
{
"path": "requirements-docs.txt",
"chars": 8,
"preview": ".[docs]\n"
},
{
"path": "setup.py",
"chars": 3185,
"preview": "#!/usr/bin/env python\n# --------------------------------------------------------------------\n# Copyright (c) iEXBase. Al"
},
{
"path": "tronapi/__init__.py",
"chars": 712,
"preview": "# --------------------------------------------------------------------\n# Copyright (c) iEXBase. All rights reserved.\n# L"
},
{
"path": "tronapi/common/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tronapi/common/abi.py",
"chars": 15063,
"preview": "# --------------------------------------------------------------------\n# Copyright (c) iEXBase. All rights reserved.\n# L"
},
{
"path": "tronapi/common/account.py",
"chars": 3192,
"preview": "# --------------------------------------------------------------------\n# Copyright (c) iEXBase. All rights reserved.\n# L"
},
{
"path": "tronapi/common/blocks.py",
"chars": 1328,
"preview": "# --------------------------------------------------------------------------------------------\n# Copyright (c) iEXBase. "
},
{
"path": "tronapi/common/contracts.py",
"chars": 4862,
"preview": "# --------------------------------------------------------------------\n# Copyright (c) iEXBase. All rights reserved.\n# L"
},
{
"path": "tronapi/common/datastructures.py",
"chars": 6408,
"preview": "from collections import (\n Hashable,\n Mapping,\n MutableMapping,\n OrderedDict,\n Sequence,\n)\n\nfrom trx_util"
},
{
"path": "tronapi/common/datatypes.py",
"chars": 1520,
"preview": "# --------------------------------------------------------------------\n# Copyright (c) iEXBase. All rights reserved.\n# L"
},
{
"path": "tronapi/common/encoding.py",
"chars": 7948,
"preview": "import json\nimport re\nfrom typing import Union\n\nfrom eth_account.datastructures import AttributeDict\nfrom hexbytes impor"
},
{
"path": "tronapi/common/formatters.py",
"chars": 1781,
"preview": "# --------------------------------------------------------------------\n# Copyright (c) iEXBase. All rights reserved.\n# L"
},
{
"path": "tronapi/common/normalizers.py",
"chars": 1820,
"preview": "import functools\nimport json\n\nfrom eth_utils import (\n is_binary_address,\n to_hex,\n hexstr_if_str\n)\nfrom hexbyt"
},
{
"path": "tronapi/common/threads.py",
"chars": 3280,
"preview": "\"\"\"\nA minimal implementation of the various gevent APIs used within this codebase.\n\"\"\"\nimport threading\nimport time\n\n\ncl"
},
{
"path": "tronapi/common/toolz/__init__.py",
"chars": 1013,
"preview": "# --------------------------------------------------------------------\n# Copyright (c) iEXBase. All rights reserved.\n# L"
},
{
"path": "tronapi/common/transactions.py",
"chars": 561,
"preview": "from tronapi.common.threads import Timeout\n\n\ndef wait_for_transaction_id(tron, tx_id, timeout=120, poll_latency=0.1):\n "
},
{
"path": "tronapi/common/validation.py",
"chars": 6614,
"preview": "# --------------------------------------------------------------------------------------------\n# Copyright (c) iEXBase. "
},
{
"path": "tronapi/constants.py",
"chars": 582,
"preview": "# --------------------------------------------------------------------\n# Copyright (c) iEXBase. All rights reserved.\n# L"
},
{
"path": "tronapi/contract.py",
"chars": 12561,
"preview": "# --------------------------------------------------------------------\n# Copyright (c) iEXBase. All rights reserved.\n# L"
},
{
"path": "tronapi/exceptions.py",
"chars": 2329,
"preview": "# --------------------------------------------------------------------\n# Copyright (c) iEXBase. All rights reserved.\n# L"
},
{
"path": "tronapi/main.py",
"chars": 11310,
"preview": "# -*- coding: utf-8 -*-\n# --------------------------------------------------------------------\n# Copyright (c) iEXBase. "
},
{
"path": "tronapi/manager.py",
"chars": 4226,
"preview": "# --------------------------------------------------------------------\n# Copyright (c) iEXBase. All rights reserved.\n# L"
},
{
"path": "tronapi/module.py",
"chars": 1009,
"preview": "# --------------------------------------------------------------------\n# Copyright (c) iEXBase. All rights reserved.\n# L"
},
{
"path": "tronapi/providers/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "tronapi/providers/base.py",
"chars": 1382,
"preview": "# --------------------------------------------------------------------\n# Copyright (c) iEXBase. All rights reserved.\n# L"
},
{
"path": "tronapi/providers/http.py",
"chars": 4660,
"preview": "# --------------------------------------------------------------------\n# Copyright (c) iEXBase. All rights reserved.\n# L"
},
{
"path": "tronapi/transactionbuilder.py",
"chars": 38618,
"preview": "# --------------------------------------------------------------------\n# Copyright (c) iEXBase. All rights reserved.\n# L"
},
{
"path": "tronapi/trx.py",
"chars": 31831,
"preview": "# --------------------------------------------------------------------\n# Copyright (c) iEXBase. All rights reserved.\n# L"
}
]
About this extraction
This page contains the full source code of the iexbase/tron-api-python GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 50 files (190.1 KB), approximately 44.1k tokens, and a symbol index with 335 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.