def __init__(self, provider: HTTPProvider = None, *, network: str = "mainnet", conf: dict = None): self.conf = DEFAULT_CONF """The config dict.""" if conf is not None: self.conf = dict(DEFAULT_CONF, **conf) if provider is None: self.provider = HTTPProvider(conf_for_name(network), self.conf['timeout']) elif isinstance(provider, (HTTPProvider,)): self.provider = provider else: raise TypeError("provider is not a HTTPProvider") self._trx = Trx(self)
class Tron(object): """The TRON API Client. :param provider: An :class:`~tronpy.providers.HTTPProvider` object, can be configured to use private node :param network: Which network to connect, one of ``"mainnet"``, ``"shasta"``, ``"nile"``, or ``"tronex"`` """ # Address API is_address = staticmethod(keys.is_address) """Is object a TRON address, both hex format and base58check format.""" is_base58check_address = staticmethod(keys.is_base58check_address) """Is object an address in base58check format.""" is_hex_address = staticmethod(keys.is_hex_address) """Is object an address in hex str format.""" to_base58check_address = staticmethod(keys.to_base58check_address) """Convert address of any format to a base58check format.""" to_hex_address = staticmethod(keys.to_hex_address) """Convert address of any format to a hex format.""" to_canonical_address = staticmethod(keys.to_base58check_address) def __init__(self, provider: HTTPProvider = None, *, network: str = "mainnet", conf: dict = None): self.conf = DEFAULT_CONF """The config dict.""" if conf is not None: self.conf = dict(DEFAULT_CONF, **conf) if provider is None: self.provider = HTTPProvider(conf_for_name(network), self.conf['timeout']) elif isinstance(provider, (HTTPProvider, )): self.provider = provider else: raise TypeError("provider is not a HTTPProvider") self._trx = Trx(self) @property def trx(self) -> Trx: """ Helper object to send various transactions. :type: Trx """ return self._trx def _handle_api_error(self, payload: dict): if payload.get("result", None) is True: return if "Error" in payload: # class java.lang.NullPointerException : null raise ApiError(payload["Error"]) if "code" in payload: try: msg = bytes.fromhex(payload["message"]).decode() except Exception: msg = payload["message"] if payload["code"] == "SIGERROR": raise BadSignature(msg) elif payload["code"] == "TAPOS_ERROR": raise TaposError(msg) elif payload["code"] in [ "TRANSACTION_EXPIRATION_ERROR", "TOO_BIG_TRANSACTION_ERROR" ]: raise TransactionError(msg) elif payload["code"] == "CONTRACT_VALIDATE_ERROR": raise ValidationError(msg) raise UnknownError(msg, payload["code"]) if "result" in payload and isinstance(payload["result"], (dict, )): return self._handle_api_error(payload["result"]) # Address utilities def generate_address(self, priv_key=None) -> dict: """Generate a random address.""" if priv_key is None: priv_key = PrivateKey.random() return { "base58check_address": priv_key.public_key.to_base58check_address(), "hex_address": priv_key.public_key.to_hex_address(), "private_key": priv_key.hex(), "public_key": priv_key.public_key.hex(), } def get_address_from_passphrase(self, passphrase: str) -> dict: """Get an address from a passphrase, compatiable with `wallet/createaddress`.""" priv_key = PrivateKey.from_passphrase(passphrase.encode()) return self.generate_address(priv_key) def generate_zkey(self) -> dict: """Generate a random shielded address.""" return self.provider.make_request("wallet/getnewshieldedaddress") def get_zkey_from_sk(self, sk: str, d: str = None) -> dict: """Get the shielded address from sk(spending key) and d(diversifier).""" if len(sk) != 64: raise BadKey("32 byte sk required") if d and len(d) != 22: raise BadKey("11 byte d required") esk = self.provider.make_request("wallet/getexpandedspendingkey", {"value": sk}) ask = esk["ask"] nsk = esk["nsk"] ovk = esk["ovk"] ak = self.provider.make_request("wallet/getakfromask", {"value": ask})["value"] nk = self.provider.make_request("wallet/getnkfromnsk", {"value": nsk})["value"] ivk = self.provider.make_request("wallet/getincomingviewingkey", { "ak": ak, "nk": nk })["ivk"] if d is None: d = self.provider.make_request("wallet/getdiversifier")["d"] ret = self.provider.make_request("wallet/getzenpaymentaddress", { "ivk": ivk, "d": d }) pkD = ret["pkD"] payment_address = ret["payment_address"] return dict( sk=sk, ask=ask, nsk=nsk, ovk=ovk, ak=ak, nk=nk, ivk=ivk, d=d, pkD=pkD, payment_address=payment_address, ) # Account query def get_account(self, addr: TAddress) -> dict: """Get account info from an address.""" ret = self.provider.make_request( "wallet/getaccount", { "address": keys.to_base58check_address(addr), "visible": True }) if ret: return ret else: raise AddressNotFound("account not found on-chain") def get_account_resource(self, addr: TAddress) -> dict: """Get resource info of an account.""" ret = self.provider.make_request( "wallet/getaccountresource", { "address": keys.to_base58check_address(addr), "visible": True }, ) if ret: return ret else: raise AddressNotFound("account not found on-chain") def get_account_balance(self, addr: TAddress) -> Decimal: """Get TRX balance of an account. Result in `TRX`.""" info = self.get_account(addr) return Decimal(info.get("balance", 0)) / 1_000_000 def get_account_asset_balances(self, addr: TAddress) -> dict: """Get all TRC10 token balances of an account.""" info = self.get_account(addr) return { p['key']: p['value'] for p in info.get("assetV2", {}) if p['value'] > 0 } def get_account_asset_balance(self, addr: TAddress, token_id: Union[int, str]) -> int: """Get TRC10 token balance of an account. Result is in raw amount.""" if int(token_id) < 1000000 or int(token_id) > 1999999: raise ValueError("invalid token_id range") balances = self.get_account_asset_balances(addr) return balances.get(str(token_id), 0) def get_account_permission(self, addr: TAddress) -> dict: """Get account's permission info from an address. Can be used in `account_permission_update`.""" addr = keys.to_base58check_address(addr) # will check account existence info = self.get_account(addr) # For old accounts prior to AccountPermissionUpdate, these fields are not set. # So default permission is for backward compatibility. default_witness = None if info.get("is_witness", None): default_witness = { "type": "Witness", "id": 1, "permission_name": "witness", "threshold": 1, "keys": [{ "address": addr, "weight": 1 }], } return { "owner": info.get( "owner_permission", { "permission_name": "owner", "threshold": 1, "keys": [{ "address": addr, "weight": 1 }] }, ), "actives": info.get( "active_permission", [{ "type": "Active", "id": 2, "permission_name": "active", "threshold": 1, "operations": "7fff1fc0033e0100000000000000000000000000000000000000000000000000", "keys": [{ "address": addr, "weight": 1 }], }], ), "witness": info.get("witness_permission", default_witness), } # Block query def get_latest_solid_block(self) -> dict: return self.provider.make_request("walletsolidity/getnowblock") def get_latest_solid_block_id(self) -> str: """Get latest solid block id in hex.""" info = self.provider.make_request("wallet/getnodeinfo") return info["solidityBlock"].split(",ID:", 1)[-1] def get_latest_block(self) -> dict: """Get latest block.""" return self.provider.make_request("wallet/getnowblock", {"visible": True}) def get_latest_block_id(self) -> str: """Get latest block id in hex.""" info = self.provider.make_request("wallet/getnodeinfo") return info["block"].split(",ID:", 1)[-1] def get_latest_block_number(self) -> int: """Get latest block number. Implemented via `wallet/getnodeinfo`, which is faster than `wallet/getnowblock`.""" info = self.provider.make_request("wallet/getnodeinfo") return int(info["block"].split(",ID:", 1)[0].replace("Num:", "", 1)) def get_block(self, id_or_num: Union[None, str, int] = None) -> dict: """Get block from a block id or block number.""" if isinstance(id_or_num, (int, )): block = self.provider.make_request("wallet/getblockbynum", { "num": id_or_num, "visible": True }) elif isinstance(id_or_num, (str, )): block = self.provider.make_request("wallet/getblockbyid", { "value": id_or_num, "visible": True }) elif id_or_num is None: block = self.provider.make_request("wallet/getnowblock", {"visible": True}) else: raise TypeError("can not infer type of {}".format(id_or_num)) if block: return block else: raise BlockNotFound def get_transaction(self, txn_id: str) -> dict: """Get transaction from a transaction id.""" if len(txn_id) != 64: raise BadHash("wrong transaction hash length") ret = self.provider.make_request("wallet/gettransactionbyid", { "value": txn_id, "visible": True }) self._handle_api_error(ret) if ret: return ret raise TransactionNotFound def get_transaction_info(self, txn_id: str) -> dict: """Get transaction receipt info from a transaction id.""" if len(txn_id) != 64: raise BadHash("wrong transaction hash length") ret = self.provider.make_request("wallet/gettransactioninfobyid", { "value": txn_id, "visible": True }) self._handle_api_error(ret) if ret: return ret raise TransactionNotFound def get_solid_transaction_info(self, txn_id: str) -> dict: """Get transaction receipt info from a transaction id, must be in solid block.""" if len(txn_id) != 64: raise BadHash("wrong transaction hash length") ret = self.provider.make_request( "walletsolidity/gettransactioninfobyid", { "value": txn_id, "visible": True }) self._handle_api_error(ret) if ret: return ret raise TransactionNotFound # Chain parameters def list_witnesses(self) -> list: """List all witnesses, including SR, SRP, and SRC.""" # NOTE: visible parameter is ignored ret = self.provider.make_request("wallet/listwitnesses", {"visible": True}) witnesses = ret.get("witnesses", []) for witness in witnesses: witness["address"] = keys.to_base58check_address( witness["address"]) return ret def list_nodes(self) -> list: """List all nodes that current API node is connected to.""" # NOTE: visible parameter is ignored ret = self.provider.make_request("wallet/listnodes", {"visible": True}) nodes = ret.get("nodes", []) for node in nodes: node["address"]["host"] = bytes.fromhex( node["address"]["host"]).decode() return nodes def get_node_info(self) -> dict: """Get current API node' info.""" return self.provider.make_request("wallet/getnodeinfo", {"visible": True}) def get_chain_parameters(self) -> dict: """List all chain parameters, values that can be changed via proposal.""" return self.provider.make_request("wallet/getchainparameters", { "visible": True }).get("chainParameter", []) # Asset (TRC10) def get_asset(self, id: int = None, issuer: TAddress = None) -> dict: """Get TRC10(asset) info by asset's id or issuer.""" if id and issuer: return ValueError("either query by id or issuer") if id: return self.provider.make_request("wallet/getassetissuebyid", { "value": id, "visible": True }) else: return self.provider.make_request( "wallet/getassetissuebyaccount", { "address": keys.to_base58check_address(issuer), "visible": True }, ) def get_asset_from_name(self, name: str) -> dict: """Get asset info from its abbr name, might fail if there're duplicates.""" assets = [ asset for asset in self.list_assets() if asset['abbr'] == name ] if assets: if len(assets) == 1: return assets[0] raise ValueError("duplicated assets with the same name", [asset['id'] for asset in assets]) raise AssetNotFound def list_assets(self) -> list: """List all TRC10 tokens(assets).""" ret = self.provider.make_request("wallet/getassetissuelist", {"visible": True}) assets = ret["assetIssue"] for asset in assets: asset["id"] = int(asset["id"]) asset["owner_address"] = keys.to_base58check_address( asset["owner_address"]) asset["name"] = bytes.fromhex(asset["name"]).decode() if "abbr" in asset: asset["abbr"] = bytes.fromhex(asset["abbr"]).decode() else: asset["abbr"] = "" asset["description"] = bytes.fromhex(asset["description"]).decode( "utf8", "replace") asset["url"] = bytes.fromhex(asset["url"]).decode() return assets # Smart contract def get_contract(self, addr: TAddress) -> Contract: """Get a contract object.""" addr = keys.to_base58check_address(addr) info = self.provider.make_request("wallet/getcontract", { "value": addr, "visible": True }) try: self._handle_api_error(info) except ApiError: # your java's null pointer exception sucks raise AddressNotFound("contract address not found") cntr = Contract( addr=addr, bytecode=info.get("bytecode", ''), name=info.get("name", ""), abi=info.get("abi", {}).get("entrys", []), origin_energy_limit=info.get("origin_energy_limit", 0), user_resource_percent=info["consume_user_resource_percent"], client=self, ) return cntr def get_contract_as_shielded_trc20(self, addr: TAddress) -> ShieldedTRC20: """Get a Shielded TRC20 Contract object.""" contract = self.get_contract(addr) return ShieldedTRC20(contract) def trigger_const_smart_contract_function( self, owner_address: TAddress, contract_address: TAddress, function_selector: str, parameter: str, ) -> str: ret = self.provider.make_request( "wallet/triggerconstantcontract", { "owner_address": keys.to_base58check_address(owner_address), "contract_address": keys.to_base58check_address(contract_address), "function_selector": function_selector, "parameter": parameter, "visible": True, }, ) self._handle_api_error(ret) if 'message' in ret['result']: raise TransactionError(ret['result']['message']) return ret["constant_result"][0] # Transaction handling def broadcast(self, txn: Transaction) -> dict: payload = self.provider.make_request("/wallet/broadcasttransaction", txn.to_json()) self._handle_api_error(payload) return payload def get_sign_weight(self, txn: TransactionBuilder) -> str: return self.provider.make_request("wallet/getsignweight", txn.to_json())