├── .env ├── .github └── ISSUE_TEMPLATE │ ├── bug-report.md │ └── idea-suggestion.md ├── .gitignore ├── LICENSE ├── README.md ├── images └── icons │ └── library_icon.png ├── py_eth_async ├── __init__.py ├── blockscan_api.py ├── client.py ├── contracts.py ├── data │ ├── __init__.py │ ├── config.py │ ├── models.py │ └── types.py ├── exceptions.py ├── nfts.py ├── transactions.py ├── utils.py └── wallet.py ├── requirements.txt ├── setup.py └── test.py /.env: -------------------------------------------------------------------------------- 1 | PRIVATE_KEY=0x0000000000000000000000000000000000000000000000000000000000000000 2 | 3 | ETHEREUM_API_KEY= 4 | ARBITRUM_API_KEY= 5 | OPTIMISM_API_KEY= 6 | BSC_API_KEY= 7 | POLYGON_API_KEY= 8 | AVALANCHE_API_KEY= 9 | ZKSYNC_ERA_API_KEY= 10 | MOONBEAM_API_KEY= 11 | FANTOM_API_KEY= 12 | CELO_API_KEY= 13 | GNOSIS_API_KEY= 14 | HECO_API_KEY= 15 | GOERLI_API_KEY= 16 | SEPOLIA_API_KEY= 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: Bug report 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 |
Description
11 | 12 | A clear and concise description of what the bug is. 13 | 14 |To reproduce
15 | 16 | Steps to reproduce the behavior: 17 | 1. Send ... 18 | 2. Select ... 19 | 3. See error 20 | 21 |Screenshots
22 | 23 | If applicable, add screenshots to help explain your problem. 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/idea-suggestion.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Idea suggestion 3 | about: Help us to make better 4 | title: Idea suggestion 5 | labels: idea 6 | assignees: '' 7 | 8 | --- 9 | 10 |Description
11 | 12 | A clear and detailed description of the idea. 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2023 SecorD 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |py-eth-async
2 | 3 |Content
8 | 9 | - [Description](#Description) 10 | - [Useful links](#Useful-links) 11 | - [Installation](#Installation) 12 | - [Report a bug or suggest an idea](#Report-a-bug-or-suggest-an-idea) 13 | - [Express your gratitude](#Express-your-gratitude) 14 | 15 | 16 | 17 |Description
18 | 19 | 20 | ⠀This library is an asynchronous add-on to the `Web3` library, designed to simplify interaction with it. 21 | 22 | 23 | 24 |Useful links
25 | 26 | 27 | ⠀[web3py](https://github.com/ethereum/web3.py) 28 | 29 | ⠀[py-eth-async](https://github.com/SecorD0/py-eth-async) 30 | 31 | ⠀[py-eth](https://github.com/SecorD0/py-eth) 32 | 33 | 34 | 35 |Installation
36 | 37 | 38 | ⠀You need execute the command below to install or update the library: 39 | ```sh 40 | pip install --force-reinstall git+https://github.com/SecorD0/py-eth-async 41 | ``` 42 | 43 | 44 | 45 |Report a bug or suggest an idea
46 | 47 | 48 | ⠀If you found a bug or have an idea, go to [the link](https://github.com/SecorD0/py-eth-async/issues/new/choose), select the template, fill it out and submit it. 49 | 50 | 51 | 52 |Express your gratitude
53 | 54 | 55 | ⠀You can express your gratitude to the developer by sending fund to crypto wallets! 56 | - Address of EVM networks (Ethereum, Polygon, BSC, etc.): `0x900649087b8D7b9f799F880427DacCF2286D8F20` 57 | - USDT TRC-20: `TNpBdjcmR5KzMVCBJTRYMJp16gCkQHu84K` 58 | - SOL: `DoZpXzGj5rEZVhEVzYdtwpzbXR8ifk5bajHybAmZvR4H` 59 | - BTC: `bc1qs4a0c3fntlhzn9j297qdsh3splcju54xscjstc` 60 | -------------------------------------------------------------------------------- /images/icons/library_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SecorD0/py-eth-async/3b00a55e3b678c0c6e458ee93dd67c6690c7826d/images/icons/library_icon.png -------------------------------------------------------------------------------- /py_eth_async/__init__.py: -------------------------------------------------------------------------------- 1 | from .data import config 2 | -------------------------------------------------------------------------------- /py_eth_async/blockscan_api.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Union, List, Dict, Any 2 | 3 | from fake_useragent import UserAgent 4 | from pretty_utils.miscellaneous.http import aiohttp_params 5 | 6 | from py_eth_async import exceptions 7 | from py_eth_async.utils import async_get 8 | 9 | 10 | class Tag: 11 | """ 12 | An instance with tag values. 13 | """ 14 | Earliest: str = 'earliest' 15 | Pending: str = 'pending' 16 | Latest: str = 'latest' 17 | 18 | 19 | class Sort: 20 | """ 21 | An instance with sort values. 22 | """ 23 | Asc: str = 'asc' 24 | Desc: str = 'desc' 25 | 26 | 27 | class BlockType: 28 | """ 29 | An instance with block type values. 30 | """ 31 | Blocks: str = 'blocks' 32 | Uncles: str = 'uncles' 33 | 34 | 35 | class Closest: 36 | """ 37 | An instance with closest values. 38 | """ 39 | Before: str = 'before' 40 | After: str = 'after' 41 | 42 | 43 | class ClientType: 44 | """ 45 | An instance with client type values. 46 | """ 47 | Geth: str = 'geth' 48 | Parity: str = 'parity' 49 | 50 | 51 | class SyncMode: 52 | """ 53 | An instance with sync mode values. 54 | """ 55 | Default: str = 'default' 56 | Archive: str = 'archive' 57 | 58 | 59 | class APIFunctions: 60 | """ 61 | Class with functions related to Blockscan API. 62 | 63 | Attributes: 64 | key (str): an API key. 65 | url (str): an API entrypoint URL. 66 | headers (Dict[str, Any]): a headers for requests. 67 | account (Account): functions related to 'account' API module. 68 | contract (Contract): functions related to 'contract' API module. 69 | transaction (Transaction): functions related to 'transaction' API module. 70 | block (Block): functions related to 'block' API module. 71 | logs (Logs): functions related to 'logs' API module. 72 | token (Token): functions related to 'token' API module. 73 | gastracker (Gastracker): functions related to 'gastracker' API module. 74 | stats (Stats): functions related to 'stats' API module. 75 | 76 | """ 77 | 78 | def __init__(self, key: str, url: str) -> None: 79 | """ 80 | Initialize the class. 81 | 82 | Args: 83 | key (str): an API key. 84 | url (str): an API entrypoint URL. 85 | 86 | """ 87 | self.key = key 88 | self.url = url 89 | self.headers = {'content-type': 'application/json', 'user-agent': UserAgent().chrome} 90 | self.account = Account(self.key, self.url, self.headers) 91 | self.contract = Contract(self.key, self.url, self.headers) 92 | self.transaction = Transaction(self.key, self.url, self.headers) 93 | self.block = Block(self.key, self.url, self.headers) 94 | self.logs = Logs(self.key, self.url, self.headers) 95 | self.token = Token(self.key, self.url, self.headers) 96 | self.gastracker = Gastracker(self.key, self.url, self.headers) 97 | self.stats = Stats(self.key, self.url, self.headers) 98 | 99 | 100 | class Module: 101 | """ 102 | Class with functions related to some API module. 103 | 104 | Attributes: 105 | key (str): an API key. 106 | url (str): an API entrypoint URL. 107 | headers (Dict[str, Any]): a headers for requests. 108 | module (str): a module name. 109 | 110 | """ 111 | key: str 112 | url: str 113 | headers: Dict[str, Any] 114 | module: str 115 | 116 | def __init__(self, key: str, url: str, headers: Dict[str, Any]) -> None: 117 | """ 118 | Initialize the class. 119 | 120 | Args: 121 | key (str): an API key. 122 | url (str): an API entrypoint URL. 123 | headers (Dict[str, Any]): a headers for requests. 124 | 125 | """ 126 | self.key = key 127 | self.url = url 128 | self.headers = headers 129 | 130 | 131 | class Account(Module): 132 | """ 133 | Class with functions related to 'account' API module. 134 | """ 135 | module: str = 'account' 136 | 137 | async def balance(self, address: str, tag: Union[str, Tag] = Tag.Latest) -> Dict[str, Any]: 138 | """ 139 | Return the Ether balance of a given address. 140 | 141 | https://docs.etherscan.io/api-endpoints/accounts#get-ether-balance-for-a-single-address 142 | 143 | Args: 144 | address (str): the address to check for balance 145 | tag (Union[str, Tag]): the pre-defined block parameter, either "earliest", "pending" or "latest". ("latest") 146 | 147 | Returns: 148 | Dict[str, Any]: the dictionary with the Ether balance of the address in wei. 149 | 150 | """ 151 | action = 'balance' 152 | if tag not in ('earliest', 'pending', 'latest'): 153 | raise exceptions.APIException('"tag" parameter have to be either "earliest", "pending" or "latest"') 154 | 155 | params = { 156 | 'module': self.module, 157 | 'action': action, 158 | 'apikey': self.key, 159 | 'address': address, 160 | 'tag': tag 161 | } 162 | return await async_get(self.url, params=aiohttp_params(params), headers=self.headers) 163 | 164 | async def balancemulti(self, addresses: List[str], tag: Union[str, Tag] = Tag.Latest) -> Dict[str, Any]: 165 | """ 166 | Return the balance of the accounts from a list of addresses. 167 | 168 | https://docs.etherscan.io/api-endpoints/accounts#get-ether-balance-for-multiple-addresses-in-a-single-call 169 | 170 | Args: 171 | addresses (List[str]): the list of up to 20 addresses to check for balance. 172 | tag (Union[str, Tag]): the pre-defined block parameter, either "earliest", "pending" or "latest". ("latest") 173 | 174 | Returns: 175 | Dict[str, Any]: the dictionary with the Ether balances for the addresses in wei. 176 | 177 | """ 178 | action = 'balancemulti' 179 | if tag not in ('earliest', 'pending', 'latest'): 180 | raise exceptions.APIException('"tag" parameter have to be either "earliest", "pending" or "latest"') 181 | 182 | params = { 183 | 'module': self.module, 184 | 'action': action, 185 | 'apikey': self.key, 186 | 'address': addresses, 187 | 'tag': tag 188 | } 189 | return await async_get(self.url, params=aiohttp_params(params), headers=self.headers) 190 | 191 | async def txlist( 192 | self, address: str, startblock: Optional[int] = None, endblock: Optional[int] = None, 193 | page: Optional[int] = None, offset: Optional[int] = None, sort: Union[str, Sort] = Sort.Asc 194 | ) -> Dict[str, Any]: 195 | """ 196 | Return the list of transactions performed by an address, with optional pagination. 197 | 198 | https://docs.etherscan.io/api-endpoints/accounts#get-a-list-of-normal-transactions-by-address 199 | 200 | Args: 201 | address (str): the address to get the transaction list. 202 | startblock (Optional[int]): the block number to start searching for transactions. 203 | endblock (Optional[int]): the block number to stop searching for transactions. 204 | page (Optional[int]): the page number, if pagination is enabled. 205 | offset (Optional[int]): the number of transactions displayed per page. 206 | sort (Union[str, Sort]): the sorting preference, use "asc" to sort by ascending and "desc" to sort 207 | by descending. ("asc") 208 | 209 | Returns: 210 | Dict[str, Any]: the dictionary with the list of transactions performed by the address. 211 | 212 | """ 213 | action = 'txlist' 214 | if sort not in ('asc', 'desc'): 215 | raise exceptions.APIException('"sort" parameter have to be either "asc" or "desc"') 216 | 217 | params = { 218 | 'module': self.module, 219 | 'action': action, 220 | 'apikey': self.key, 221 | 'address': address, 222 | 'sort': sort, 223 | 'startblock': startblock, 224 | 'endblock': endblock, 225 | 'page': page, 226 | 'offset': offset 227 | } 228 | 229 | return await async_get(self.url, params=aiohttp_params(params), headers=self.headers) 230 | 231 | async def txlistinternal( 232 | self, address: Optional[str] = None, txhash: Optional[str] = None, startblock: Optional[int] = None, 233 | endblock: Optional[int] = None, page: Optional[int] = None, offset: Optional[int] = None, 234 | sort: Union[str, Sort] = Sort.Asc 235 | ) -> Dict[str, Any]: 236 | """ 237 | Return the list of internal transactions performed by an address, with optional pagination. 238 | 239 | https://docs.etherscan.io/api-endpoints/accounts#get-a-list-of-internal-transactions-by-address 240 | 241 | Args: 242 | address (Optional[str]): the address to get the transaction list. 243 | txhash (Optional[str]): the transaction hash to check for internal transactions. 244 | startblock (Optional[int]): the block number to start searching for transactions. 245 | endblock (Optional[int]): the block number to stop searching for transactions. 246 | page (Optional[int]): the page number, if pagination is enabled. 247 | offset (Optional[int]): the number of transactions displayed per page. 248 | sort (Union[str, Sort]): the sorting preference, use "asc" to sort by ascending and "desc" to sort 249 | by descending. ("asc") 250 | 251 | Returns: 252 | Dict[str, Any]: the dictionary with the list of internal transactions performed by the address. 253 | 254 | """ 255 | action = 'txlistinternal' 256 | if sort not in ('asc', 'desc'): 257 | raise exceptions.APIException('"sort" parameter have to be either "asc" or "desc"') 258 | 259 | params = { 260 | 'module': self.module, 261 | 'action': action, 262 | 'apikey': self.key 263 | } 264 | 265 | if not address and not txhash: 266 | if not startblock and endblock: 267 | raise exceptions.APIException('Specify "startblock" an "endblock" parameters') 268 | 269 | params['startblock'] = startblock 270 | params['endblock'] = endblock 271 | params['sort'] = sort 272 | params['page'] = page 273 | params['offset'] = offset 274 | 275 | elif txhash: 276 | params['txhash'] = txhash 277 | 278 | else: 279 | params['address'] = address 280 | params['sort'] = sort 281 | params['startblock'] = startblock 282 | params['endblock'] = endblock 283 | params['page'] = page 284 | params['offset'] = offset 285 | 286 | return await async_get(self.url, params=aiohttp_params(params), headers=self.headers) 287 | 288 | async def tokentx( 289 | self, address: str, contractaddress: Optional[str] = None, startblock: Optional[int] = None, 290 | endblock: Optional[int] = None, page: Optional[int] = None, offset: Optional[int] = None, 291 | sort: Union[str, Sort] = Sort.Asc 292 | ) -> Dict[str, Any]: 293 | """ 294 | Return the list of ERC-20 tokens transferred by an address, with optional filtering by token contract. 295 | 296 | https://docs.etherscan.io/api-endpoints/accounts#get-a-list-of-erc20-token-transfer-events-by-address 297 | 298 | Args: 299 | address (str): the address to get the transaction list. 300 | contractaddress (Optional[str]): the token contract address to check for transactions. 301 | startblock (Optional[int]): the block number to start searching for transactions. 302 | endblock (Optional[int]): the block number to stop searching for transactions. 303 | page (Optional[int]): the page number, if pagination is enabled. 304 | offset (Optional[int]): the number of transactions displayed per page. 305 | sort (Union[str, Sort]): the sorting preference, use "asc" to sort by ascending and "desc" to sort 306 | by descending. ("asc") 307 | 308 | Returns: 309 | Dict[str, Any]: the dictionary with the list of ERC-20 token transactions performed by the address. 310 | 311 | """ 312 | action = 'tokentx' 313 | if sort not in ('asc', 'desc'): 314 | raise exceptions.APIException('"sort" parameter have to be either "asc" or "desc"') 315 | 316 | params = { 317 | 'module': self.module, 318 | 'action': action, 319 | 'apikey': self.key, 320 | 'address': address, 321 | 'sort': sort, 322 | 'contractaddress': contractaddress, 323 | 'startblock': startblock, 324 | 'endblock': endblock, 325 | 'page': page, 326 | 'offset': offset 327 | } 328 | return await async_get(self.url, params=aiohttp_params(params), headers=self.headers) 329 | 330 | async def tokennfttx( 331 | self, address: str, contractaddress: Optional[str] = None, startblock: Optional[int] = None, 332 | endblock: Optional[int] = None, page: Optional[int] = None, offset: Optional[int] = None, 333 | sort: Union[str, Sort] = Sort.Asc 334 | ) -> Dict[str, Any]: 335 | """ 336 | Return the list of ERC-721 (NFT) tokens transferred by an address, with optional filtering by token contract. 337 | 338 | https://docs.etherscan.io/api-endpoints/accounts#get-a-list-of-erc721-token-transfer-events-by-address 339 | 340 | Args: 341 | address (str): the address to get the transaction list. 342 | contractaddress (Optional[str]): the token contract address to check for transactions. 343 | startblock (Optional[int]): the block number to start searching for transactions. 344 | endblock (Optional[int]): the block number to stop searching for transactions. 345 | page (Optional[int]): the page number, if pagination is enabled. 346 | offset (Optional[int]): the number of transactions displayed per page. 347 | sort (Union[str, Sort]): the sorting preference, use "asc" to sort by ascending and "desc" to sort 348 | by descending. ("asc") 349 | 350 | Returns: 351 | Dict[str, Any]: the dictionary with the list of ERC-721 token transactions performed by the address. 352 | 353 | """ 354 | action = 'tokennfttx' 355 | if sort not in ('asc', 'desc'): 356 | raise exceptions.APIException('"sort" parameter have to be either "asc" or "desc"') 357 | 358 | params = { 359 | 'module': self.module, 360 | 'action': action, 361 | 'apikey': self.key, 362 | 'address': address, 363 | 'sort': sort, 364 | 'contractaddress': contractaddress, 365 | 'startblock': startblock, 366 | 'endblock': endblock, 367 | 'page': page, 368 | 'offset': offset 369 | } 370 | return await async_get(self.url, params=aiohttp_params(params), headers=self.headers) 371 | 372 | async def token1155tx( 373 | self, address: str, contractaddress: Optional[str] = None, startblock: Optional[int] = None, 374 | endblock: Optional[int] = None, page: Optional[int] = None, offset: Optional[int] = None, 375 | sort: Union[str, Sort] = Sort.Asc 376 | ) -> Dict[str, Any]: 377 | """ 378 | Return the list of ERC-1155 (Multi Token Standard) tokens transferred by an address, with optional filtering by token contract. 379 | 380 | https://docs.etherscan.io/api-endpoints/accounts#get-a-list-of-erc1155-token-transfer-events-by-address 381 | 382 | Args: 383 | address (str): the address to get the transaction list. 384 | contractaddress (Optional[str]): the token contract address to check for transactions. 385 | startblock (Optional[int]): the block number to start searching for transactions. 386 | endblock (Optional[int]): the block number to stop searching for transactions. 387 | page (Optional[int]): the page number, if pagination is enabled. 388 | offset (Optional[int]): the number of transactions displayed per page. 389 | sort (Union[str, Sort]): the sorting preference, use "asc" to sort by ascending and "desc" to sort 390 | by descending. ("asc") 391 | 392 | Returns: 393 | Dict[str, Any]: the dictionary with the list of ERC-1155 token transactions performed by the address. 394 | 395 | """ 396 | action = 'token1155tx' 397 | if sort not in ('asc', 'desc'): 398 | raise exceptions.APIException('"sort" parameter have to be either "asc" or "desc"') 399 | 400 | params = { 401 | 'module': self.module, 402 | 'action': action, 403 | 'apikey': self.key, 404 | 'address': address, 405 | 'sort': sort, 406 | 'contractaddress': contractaddress, 407 | 'startblock': startblock, 408 | 'endblock': endblock, 409 | 'page': page, 410 | 'offset': offset 411 | } 412 | return await async_get(self.url, params=aiohttp_params(params), headers=self.headers) 413 | 414 | async def getminedblocks( 415 | self, address: str, blocktype: Union[str, BlockType] = BlockType.Blocks, page: Optional[int] = None, 416 | offset: Optional[int] = None 417 | ) -> Dict[str, Any]: 418 | """ 419 | Return the list of blocks mined by an address. 420 | 421 | https://docs.etherscan.io/api-endpoints/accounts#get-list-of-blocks-mined-by-address 422 | 423 | Args: 424 | address (str): the address to check for mined blocks. 425 | blocktype (Union[str, BlockType]): the pre-defined block type, either "blocks" for canonical blocks 426 | or "uncles" for uncle blocks only. ("blocks") 427 | page (Optional[int]): the page number, if pagination is enabled. 428 | offset (Optional[int]): the number of transactions displayed per page. 429 | 430 | Returns: 431 | Dict[str, Any]: the dictionary with the list of mined blocks by the address. 432 | 433 | """ 434 | action = 'getminedblocks' 435 | if blocktype not in ('blocks', 'uncles'): 436 | raise exceptions.APIException('"blocktype" parameter have to be either "blocks" or "uncles"') 437 | 438 | params = { 439 | 'module': self.module, 440 | 'action': action, 441 | 'apikey': self.key, 442 | 'address': address, 443 | 'blocktype': blocktype, 444 | 'page': page, 445 | 'offset': offset 446 | } 447 | return await async_get(self.url, params=aiohttp_params(params), headers=self.headers) 448 | 449 | async def balancehistory(self, address: str, blockno: int) -> Dict[str, Any]: 450 | """ 451 | Return the balance of an address at a certain block height. (PRO) 452 | 453 | https://docs.etherscan.io/api-endpoints/accounts#get-historical-ether-balance-for-a-single-address-by-blockno 454 | 455 | Args: 456 | address (str): the address to check for balance. 457 | blockno (int): the block number to check balance. 458 | 459 | Returns: 460 | Dict[str, Any]: the dictionary with the Ether balance of the address in wei. 461 | 462 | """ 463 | action = 'balancehistory' 464 | params = { 465 | 'module': self.module, 466 | 'action': action, 467 | 'apikey': self.key, 468 | 'address': address, 469 | 'blockno': blockno 470 | } 471 | return await async_get(self.url, params=aiohttp_params(params), headers=self.headers) 472 | 473 | async def tokenbalance(self, contractaddress: str, address: str) -> Dict[str, Any]: 474 | """ 475 | Return the current balance of an ERC-20 token of an address. 476 | 477 | https://docs.etherscan.io/api-endpoints/tokens#get-erc20-token-account-balance-for-tokencontractaddress 478 | 479 | Args: 480 | contractaddress (str): the contract address of the ERC-20 token. 481 | address (str): the address to check for token balance. 482 | 483 | Returns: 484 | Dict[str, Any]: the dictionary with the token balance of the address. 485 | 486 | """ 487 | action = 'tokenbalance' 488 | params = { 489 | 'module': self.module, 490 | 'action': action, 491 | 'apikey': self.key, 492 | 'contractaddress': contractaddress, 493 | 'address': address 494 | } 495 | return await async_get(self.url, params=aiohttp_params(params), headers=self.headers) 496 | 497 | async def tokenbalancehistory(self, contractaddress: str, address: str, blockno: int) -> Dict[str, Any]: 498 | """ 499 | Return the balance of an ERC-20 token of an address at a certain block height. (PRO) 500 | 501 | https://docs.etherscan.io/api-endpoints/tokens#get-historical-erc20-token-account-balance-for-tokencontractaddress-by-blockno 502 | 503 | Args: 504 | contractaddress (str): the contract address of the ERC-20 token. 505 | address (str): the address to check for balance. 506 | blockno (str): the block number to check balance. 507 | 508 | Returns: 509 | Dict[str, Any]: the dictionary with the ERC-20 token balance of the address. 510 | 511 | """ 512 | action = 'tokenbalancehistory' 513 | params = { 514 | 'module': self.module, 515 | 'action': action, 516 | 'apikey': self.key, 517 | 'contractaddress': contractaddress, 518 | 'address': address, 519 | 'blockno': blockno 520 | } 521 | return await async_get(self.url, params=aiohttp_params(params), headers=self.headers) 522 | 523 | async def addresstokenbalance( 524 | self, address: str, page: Optional[int] = None, offset: Optional[int] = None 525 | ) -> Dict[str, Any]: 526 | """ 527 | Return the ERC-20 tokens and amount held by an address. (PRO) 528 | 529 | https://docs.etherscan.io/api-endpoints/tokens#get-address-erc20-token-holding 530 | 531 | Args: 532 | address (str): the address to check for balance. 533 | page (Optional[int]): the page number, if pagination is enabled. 534 | offset (Optional[int]): the number of transactions displayed per page. 535 | 536 | Returns: 537 | Dict[str, Any]: the dictionary with the ERC-20 tokens and amount held by an address. 538 | 539 | """ 540 | action = 'addresstokenbalance' 541 | params = { 542 | 'module': self.module, 543 | 'action': action, 544 | 'apikey': self.key, 545 | 'address': address, 546 | 'page': page, 547 | 'offset': offset 548 | } 549 | return await async_get(self.url, params=aiohttp_params(params), headers=self.headers) 550 | 551 | async def addresstokennftbalance( 552 | self, address: str, page: Optional[int] = None, offset: Optional[int] = None 553 | ) -> Dict[str, Any]: 554 | """ 555 | Return the ERC-721 tokens and amount held by an address. (PRO) 556 | 557 | https://docs.etherscan.io/api-endpoints/tokens#get-address-erc721-token-holding 558 | 559 | Args: 560 | address (str): the address to check for balance. 561 | page (Optional[int]): the page number, if pagination is enabled. 562 | offset (Optional[int]): the number of transactions displayed per page. 563 | 564 | Returns: 565 | Dict[str, Any]: the dictionary with the ERC-721 tokens and amount held by an address.. 566 | 567 | """ 568 | action = 'addresstokennftbalance' 569 | params = { 570 | 'module': self.module, 571 | 'action': action, 572 | 'apikey': self.key, 573 | 'address': address, 574 | 'page': page, 575 | 'offset': offset 576 | } 577 | return await async_get(self.url, params=aiohttp_params(params), headers=self.headers) 578 | 579 | async def addresstokennftinventory( 580 | self, address: str, page: Optional[int] = None, offset: Optional[int] = None 581 | ) -> Dict[str, Any]: 582 | """ 583 | Return the ERC-721 token inventory of an address, filtered by contract address. (PRO) 584 | 585 | https://docs.etherscan.io/api-endpoints/tokens#get-address-erc721-token-inventory-by-contract-address 586 | 587 | Args: 588 | address (str): the address to check for balance. 589 | page (Optional[int]): the page number, if pagination is enabled. 590 | offset (Optional[int]): the number of transactions displayed per page. 591 | 592 | Returns: 593 | Dict[str, Any]: the dictionary with the ERC-721 tokens and amount held by an address.. 594 | 595 | """ 596 | action = 'addresstokennftinventory' 597 | params = { 598 | 'module': self.module, 599 | 'action': action, 600 | 'apikey': self.key, 601 | 'address': address, 602 | 'page': page, 603 | 'offset': offset 604 | } 605 | return await async_get(self.url, params=aiohttp_params(params), headers=self.headers) 606 | 607 | 608 | class Contract(Module): 609 | """ 610 | Class with functions related to 'contract' API module. 611 | """ 612 | module: str = 'contract' 613 | 614 | async def getabi(self, address: str) -> Dict[str, Any]: 615 | """ 616 | Return the Contract Application Binary Interface (ABI) of a verified smart contract. 617 | 618 | https://docs.etherscan.io/api-endpoints/contracts#get-contract-abi-for-verified-contract-source-codes 619 | 620 | Args: 621 | address (str): the contract address that has a verified source code. 622 | 623 | Returns: 624 | Dict[str, Any]: the dictionary with the contract ABI. 625 | 626 | """ 627 | action = 'getabi' 628 | params = { 629 | 'module': self.module, 630 | 'action': action, 631 | 'apikey': self.key, 632 | 'address': address 633 | } 634 | return await async_get(self.url, params=aiohttp_params(params), headers=self.headers) 635 | 636 | async def getsourcecode(self, address: str) -> Dict[str, Any]: 637 | """ 638 | Return the Solidity source code of a verified smart contract. 639 | 640 | https://docs.etherscan.io/api-endpoints/contracts#get-contract-source-code-for-verified-contract-source-codes 641 | 642 | Args: 643 | address (str): the contract address that has a verified source code. 644 | 645 | Returns: 646 | Dict[str, Any]: the dictionary with the contract source code. 647 | 648 | """ 649 | action = 'getsourcecode' 650 | params = { 651 | 'module': self.module, 652 | 'action': action, 653 | 'apikey': self.key, 654 | 'address': address 655 | } 656 | return await async_get(self.url, params=aiohttp_params(params), headers=self.headers) 657 | 658 | async def getcontractcreation(self, addresses: List[str]) -> Dict[str, Any]: 659 | """ 660 | Return a contract's deployer address and transaction hash it was created, up to 5 at a time. 661 | 662 | https://docs.etherscan.io/api-endpoints/contracts#get-contract-creator-and-creation-tx-hash 663 | 664 | Args: 665 | addresses (str): the contract address, up to 5 at a time. 666 | 667 | Returns: 668 | Dict[str, Any]: the dictionary with a contract's deployer address and transaction hash it was created. 669 | 670 | """ 671 | action = 'getcontractcreation' 672 | params = { 673 | 'module': self.module, 674 | 'action': action, 675 | 'apikey': self.key, 676 | 'address': addresses 677 | } 678 | return await async_get(self.url, params=aiohttp_params(params), headers=self.headers) 679 | 680 | 681 | class Transaction(Module): 682 | """ 683 | Class with functions related to 'transaction' API module. 684 | """ 685 | module: str = 'transaction' 686 | 687 | async def getstatus(self, txhash: str) -> Dict[str, Any]: 688 | """ 689 | Return the status code of a contract execution. 690 | 691 | https://docs.etherscan.io/api-endpoints/stats#check-contract-execution-status 692 | 693 | Args: 694 | txhash (str): the transaction hash to check the execution status. 695 | 696 | Returns: 697 | Dict[str, Any]: the dictionary with the contract status code. 698 | 699 | """ 700 | action = 'getstatus' 701 | params = { 702 | 'module': self.module, 703 | 'action': action, 704 | 'apikey': self.key, 705 | 'txhash': txhash 706 | } 707 | return await async_get(self.url, params=aiohttp_params(params), headers=self.headers) 708 | 709 | async def gettxreceiptstatus(self, txhash: str) -> Dict[str, Any]: 710 | """ 711 | Return the status code of a transaction execution. 712 | 713 | https://docs.etherscan.io/api-endpoints/stats#check-transaction-receipt-status 714 | 715 | Args: 716 | txhash (str): the transaction hash to check the execution status. 717 | 718 | Returns: 719 | Dict[str, Any]: the dictionary with the transaction status code. 720 | 721 | """ 722 | action = 'gettxreceiptstatus' 723 | params = { 724 | 'module': self.module, 725 | 'action': action, 726 | 'apikey': self.key, 727 | 'txhash': txhash 728 | } 729 | return await async_get(self.url, params=aiohttp_params(params), headers=self.headers) 730 | 731 | 732 | class Block(Module): 733 | """ 734 | Class with functions related to 'block' API module. 735 | """ 736 | module: str = 'block' 737 | 738 | async def getblockreward(self, blockno: int) -> Dict[str, Any]: 739 | """ 740 | Return the block reward and 'Uncle' block rewards. 741 | 742 | https://docs.etherscan.io/api-endpoints/blocks#get-block-and-uncle-rewards-by-blockno 743 | 744 | Args: 745 | blockno (int): the block number to check block rewards. 746 | 747 | Returns: 748 | Dict[str, Any]: the dictionary with the block rewards. 749 | 750 | """ 751 | action = 'getblockreward' 752 | params = { 753 | 'module': self.module, 754 | 'action': action, 755 | 'apikey': self.key, 756 | 'blockno': blockno 757 | } 758 | return await async_get(self.url, params=aiohttp_params(params), headers=self.headers) 759 | 760 | async def getblockcountdown(self, blockno: int) -> Dict[str, Any]: 761 | """ 762 | Return the estimated time remaining, in seconds, until a certain block is mined. 763 | 764 | https://docs.etherscan.io/api-endpoints/blocks#get-estimated-block-countdown-time-by-blockno 765 | 766 | Args: 767 | blockno (int): the block number to estimate time remaining to be mined. 768 | 769 | Returns: 770 | Dict[str, Any]: the dictionary with the estimated time remaining until a certain block is mined. 771 | 772 | """ 773 | action = 'getblockcountdown' 774 | params = { 775 | 'module': self.module, 776 | 'action': action, 777 | 'apikey': self.key, 778 | 'blockno': blockno 779 | } 780 | return await async_get(self.url, params=aiohttp_params(params), headers=self.headers) 781 | 782 | async def getblocknobytime(self, timestamp: int, closest: Union[str, Closest] = Closest.Before) -> Dict[str, Any]: 783 | """ 784 | Return the block number that was mined at a certain timestamp. 785 | 786 | https://docs.etherscan.io/api-endpoints/blocks#get-block-number-by-timestamp 787 | 788 | Args: 789 | timestamp (str): the Unix timestamp in seconds. 790 | closest (Union[str, Closest]): the closest available block to the provided timestamp, 791 | either "before" or "after". ("before") 792 | 793 | Returns: 794 | Dict[str, Any]: the dictionary with the block number that was mined at a certain timestamp. 795 | 796 | """ 797 | action = 'getblocknobytime' 798 | if closest not in ('before', 'after'): 799 | raise exceptions.APIException('"closest" parameter have to be either "before" or "after"') 800 | 801 | params = { 802 | 'module': self.module, 803 | 'action': action, 804 | 'apikey': self.key, 805 | 'timestamp': timestamp, 806 | 'closest': closest 807 | } 808 | return await async_get(self.url, params=aiohttp_params(params), headers=self.headers) 809 | 810 | 811 | class Logs(Module): 812 | """ 813 | Class with functions related to 'logs' API module. 814 | """ 815 | module: str = 'log' 816 | 817 | async def getLogs( 818 | self, address: Optional[str], fromBlock: Optional[int], toBlock: Optional[int], page: Optional[int] = None, 819 | offset: Optional[int] = None, **kwargs 820 | ) -> Dict[str, Any]: 821 | """ 822 | Return the event logs from an address, with optional filtering by block range. 823 | 824 | https://docs.etherscan.io/api-endpoints/logs#get-event-logs-by-address 825 | 826 | Args: 827 | address (Optional[str]): the address to get the transaction list. 828 | fromBlock (Optional[int]): the block number to start searching for logs. 829 | toBlock (Optional[int]): the block number to stop searching for logs. 830 | page (Optional[int]): the page number, if pagination is enabled. 831 | offset (Optional[int]): the number of transactions displayed per page. 832 | **kwargs: the topic numbers to search for and the topic operator when multiple topic combinations are used. 833 | 834 | Returns: 835 | Dict[str, Any]: the dictionary with the list of transactions performed by the address. 836 | 837 | """ 838 | action = 'getLogs' 839 | params = { 840 | 'module': self.module, 841 | 'action': action, 842 | 'apikey': self.key, 843 | 'address': address, 844 | 'fromBlock': fromBlock, 845 | 'toBlock': toBlock, 846 | 'page': page, 847 | 'offset': offset 848 | } 849 | for key, value in kwargs.items(): 850 | params[key] = value 851 | 852 | return await async_get(self.url, params=aiohttp_params(params), headers=self.headers) 853 | 854 | 855 | class Token(Module): 856 | """ 857 | Class with functions related to 'token' API module. 858 | """ 859 | module: str = 'token' 860 | 861 | async def tokenholderlist( 862 | self, contractaddress: str, page: Optional[int] = None, offset: Optional[int] = None 863 | ) -> Dict[str, Any]: 864 | """ 865 | Return project information and social media links of an ERC20/ERC721/ERC1155 token. (PRO) 866 | 867 | https://docs.etherscan.io/api-endpoints/tokens#get-token-holder-list-by-contract-address 868 | 869 | Args: 870 | contractaddress (str): the contract address of the ERC-20 token. 871 | page (Optional[int]): the page number, if pagination is enabled. 872 | offset (Optional[int]): the number of transactions displayed per page. 873 | 874 | Returns: 875 | Dict[str, Any]: the dictionary with the project information and social media links of 876 | an ERC20/ERC721/ERC1155 token. 877 | 878 | """ 879 | action = 'tokenholderlist' 880 | params = { 881 | 'module': self.module, 882 | 'action': action, 883 | 'apikey': self.key, 884 | 'contractaddress': contractaddress, 885 | 'page': page, 886 | 'offset': offset 887 | } 888 | return await async_get(self.url, params=aiohttp_params(params), headers=self.headers) 889 | 890 | async def tokeninfo(self, contractaddress: str) -> Dict[str, Any]: 891 | """ 892 | Return project information and social media links of an ERC20/ERC721/ERC1155 token. (PRO) 893 | 894 | https://docs.etherscan.io/api-endpoints/tokens#get-token-info-by-contractaddress 895 | 896 | Args: 897 | contractaddress (str): the contract address of the ERC-20 token. 898 | 899 | Returns: 900 | Dict[str, Any]: the dictionary with the project information and social media links of 901 | an ERC20/ERC721/ERC1155 token. 902 | 903 | """ 904 | action = 'tokeninfo' 905 | params = { 906 | 'module': self.module, 907 | 'action': action, 908 | 'apikey': self.key, 909 | 'contractaddress': contractaddress 910 | } 911 | return await async_get(self.url, params=aiohttp_params(params), headers=self.headers) 912 | 913 | 914 | class Gastracker(Module): 915 | """ 916 | Class with functions related to 'gastracker' API module. 917 | """ 918 | module: str = 'gastracker' 919 | 920 | async def gasestimate(self, gasprice: int) -> Dict[str, Any]: 921 | """ 922 | Return the estimated time, in seconds, for a transaction to be confirmed on the blockchain. 923 | 924 | https://docs.etherscan.io/api-endpoints/gas-tracker#get-estimation-of-confirmation-time 925 | 926 | Args: 927 | gasprice (int): the price paid per unit of gas, in wei. 928 | 929 | Returns: 930 | Dict[str, Any]: the dictionary with the estimated time, in seconds, for a transaction to be confirmed 931 | on the blockchain. 932 | 933 | """ 934 | action = 'gasestimate' 935 | params = { 936 | 'module': self.module, 937 | 'action': action, 938 | 'apikey': self.key, 939 | 'gasprice': gasprice 940 | } 941 | return await async_get(self.url, params=aiohttp_params(params), headers=self.headers) 942 | 943 | async def gasoracle(self) -> Dict[str, Any]: 944 | """ 945 | Return the current Safe, Proposed and Fast gas prices. 946 | 947 | https://docs.etherscan.io/api-endpoints/gas-tracker#get-gas-oracle 948 | 949 | Returns: 950 | Dict[str, Any]: the dictionary with the current Safe, Proposed and Fast gas prices. 951 | 952 | """ 953 | action = 'gasoracle' 954 | params = { 955 | 'module': self.module, 956 | 'action': action, 957 | 'apikey': self.key 958 | } 959 | return await async_get(self.url, params=aiohttp_params(params), headers=self.headers) 960 | 961 | 962 | class Stats(Module): 963 | """ 964 | Class with functions related to 'stats' API module. 965 | """ 966 | module: str = 'stats' 967 | 968 | async def ethsupply(self) -> Dict[str, Any]: 969 | """ 970 | Return the current amount of Ether in circulation excluding ETH2 Staking rewards and EIP1559 burnt fees. 971 | 972 | https://docs.etherscan.io/api-endpoints/stats-1#get-total-supply-of-ether 973 | 974 | Returns: 975 | Dict[str, Any]: the dictionary with the current amount of Ether. 976 | 977 | """ 978 | action = 'ethsupply' 979 | params = { 980 | 'module': self.module, 981 | 'action': action, 982 | 'apikey': self.key 983 | } 984 | return await async_get(self.url, params=aiohttp_params(params), headers=self.headers) 985 | 986 | async def ethsupply2(self) -> Dict[str, Any]: 987 | """ 988 | Return the current amount of Ether in circulation, ETH2 Staking rewards and EIP1559 burnt fees statistics. 989 | 990 | https://docs.etherscan.io/api-endpoints/stats-1#get-total-supply-of-ether-2 991 | 992 | Returns: 993 | Dict[str, Any]: the dictionary with the current amount of Ether. 994 | 995 | """ 996 | action = 'ethsupply2' 997 | params = { 998 | 'module': self.module, 999 | 'action': action, 1000 | 'apikey': self.key 1001 | } 1002 | return await async_get(self.url, params=aiohttp_params(params), headers=self.headers) 1003 | 1004 | async def ethprice(self) -> Dict[str, Any]: 1005 | """ 1006 | Return the latest price of 1 ETH. 1007 | 1008 | https://docs.etherscan.io/api-endpoints/stats-1#get-ether-last-price 1009 | 1010 | Returns: 1011 | Dict[str, Any]: the dictionary with the latest Ether price. 1012 | 1013 | """ 1014 | action = 'ethprice' 1015 | params = { 1016 | 'module': self.module, 1017 | 'action': action, 1018 | 'apikey': self.key 1019 | } 1020 | return await async_get(self.url, params=aiohttp_params(params), headers=self.headers) 1021 | 1022 | async def chainsize( 1023 | self, startdate: str, enddate: str, clienttype: Union[str, ClientType] = ClientType.Geth, 1024 | syncmode: Union[str, SyncMode] = SyncMode.Default, sort: Union[str, Sort] = Sort.Asc 1025 | ) -> Dict[str, Any]: 1026 | """ 1027 | Return the size of the Ethereum blockchain, in bytes, over a date range. 1028 | 1029 | https://docs.etherscan.io/api-endpoints/stats-1#get-ethereum-nodes-size 1030 | 1031 | Args: 1032 | startdate (str): the starting date in yyyy-MM-dd format, eg. 2019-02-01. 1033 | enddate (int): the ending date in yyyy-MM-dd format, eg. 2019-02-28. 1034 | clienttype (Union[str, ClientType]): the Ethereum node client to use, either "geth" or "parity". ("geth") 1035 | syncmode (Union[str, SyncMode]): the type of node to run, either "default" or "archive". ("default") 1036 | sort (Union[str, Sort]): the sorting preference, use "asc" to sort by ascending and "desc" to sort 1037 | by descending. ("asc") 1038 | 1039 | Returns: 1040 | Dict[str, Any]: the dictionary with the list of transactions performed by the address. 1041 | 1042 | """ 1043 | action = 'chainsize' 1044 | if clienttype not in ('geth', 'parity'): 1045 | raise exceptions.APIException('"clienttype" parameter have to be either "geth" or "parity"') 1046 | 1047 | if syncmode not in ('default', 'archive'): 1048 | raise exceptions.APIException('"syncmode" parameter have to be either "default" or "archive"') 1049 | 1050 | if sort not in ('asc', 'desc'): 1051 | raise exceptions.APIException('"sort" parameter have to be either "asc" or "desc"') 1052 | 1053 | params = { 1054 | 'module': self.module, 1055 | 'action': action, 1056 | 'apikey': self.key, 1057 | 'startdate': startdate, 1058 | 'enddate': enddate, 1059 | 'clienttype': clienttype, 1060 | 'syncmode': syncmode, 1061 | 'sort': sort 1062 | } 1063 | return await async_get(self.url, params=aiohttp_params(params), headers=self.headers) 1064 | 1065 | async def nodecount(self) -> Dict[str, Any]: 1066 | """ 1067 | Return the total number of discoverable Ethereum nodes. 1068 | 1069 | https://docs.etherscan.io/api-endpoints/stats-1#get-total-nodes-count 1070 | 1071 | Returns: 1072 | Dict[str, Any]: the dictionary with the total number of discoverable Ethereum nodes. 1073 | 1074 | """ 1075 | action = 'nodecount' 1076 | params = { 1077 | 'module': self.module, 1078 | 'action': action, 1079 | 'apikey': self.key 1080 | } 1081 | return await async_get(self.url, params=aiohttp_params(params), headers=self.headers) 1082 | 1083 | async def general( 1084 | self, action: str, startdate: str, enddate: str, sort: Union[str, Sort] = Sort.Asc 1085 | ) -> Dict[str, Any]: 1086 | """ 1087 | Function for sending similar requests. 1088 | 1089 | Args: 1090 | action (str): the action name. 1091 | startdate (str): the starting date in yyyy-MM-dd format, eg. 2019-02-01. 1092 | enddate (str): the ending date in yyyy-MM-dd format, eg. 2019-02-28. 1093 | sort (Union[str, Sort]): the sorting preference, use "asc" to sort by ascending and "desc" to sort 1094 | by descending. ("asc") 1095 | 1096 | Returns: 1097 | Dict[str, Any]: the dictionary with the data. 1098 | 1099 | """ 1100 | if sort not in ('asc', 'desc'): 1101 | raise exceptions.APIException('"sort" parameter have to be either "asc" or "desc"') 1102 | 1103 | params = { 1104 | 'module': self.module, 1105 | 'action': action, 1106 | 'apikey': self.key, 1107 | 'startdate': startdate, 1108 | 'enddate': enddate, 1109 | 'sort': sort 1110 | } 1111 | return await async_get(self.url, params=aiohttp_params(params), headers=self.headers) 1112 | 1113 | async def dailytxnfee(self, startdate: str, enddate: str, sort: Union[str, Sort] = Sort.Asc) -> Dict[str, Any]: 1114 | """ 1115 | Return the amount of transaction fees paid to miners per day. (PRO) 1116 | 1117 | https://docs.etherscan.io/api-endpoints/stats-1#get-daily-network-transaction-fee 1118 | 1119 | Args: 1120 | startdate (str): the starting date in yyyy-MM-dd format, eg. 2019-02-01. 1121 | enddate (str): the ending date in yyyy-MM-dd format, eg. 2019-02-28. 1122 | sort (Union[str, Sort]): the sorting preference, use "asc" to sort by ascending and "desc" to sort 1123 | by descending. ("asc") 1124 | 1125 | Returns: 1126 | Dict[str, Any]: the dictionary with the amount of transaction fees paid to miners per day. 1127 | 1128 | """ 1129 | return await self.general(action='dailytxnfee', startdate=startdate, enddate=enddate, sort=sort) 1130 | 1131 | async def dailynewaddress(self, startdate: str, enddate: str, sort: Union[str, Sort] = Sort.Asc) -> Dict[str, Any]: 1132 | """ 1133 | Return the number of new Ethereum addresses created per day. (PRO) 1134 | 1135 | https://docs.etherscan.io/api-endpoints/stats-1#get-daily-new-address-count 1136 | 1137 | Args: 1138 | startdate (str): the starting date in yyyy-MM-dd format, eg. 2019-02-01. 1139 | enddate (str): the ending date in yyyy-MM-dd format, eg. 2019-02-28. 1140 | sort (Union[str, Sort]): the sorting preference, use "asc" to sort by ascending and "desc" to sort 1141 | by descending. ("asc") 1142 | 1143 | Returns: 1144 | Dict[str, Any]: the dictionary with the number of new Ethereum addresses created per day. 1145 | 1146 | """ 1147 | return await self.general(action='dailynewaddress', startdate=startdate, enddate=enddate, sort=sort) 1148 | 1149 | async def dailynetutilization( 1150 | self, startdate: str, enddate: str, sort: Union[str, Sort] = Sort.Asc 1151 | ) -> Dict[str, Any]: 1152 | """ 1153 | Return the daily average gas used over gas limit, in percentage. (PRO) 1154 | 1155 | https://docs.etherscan.io/api-endpoints/stats-1#get-daily-new-address-count 1156 | 1157 | Args: 1158 | startdate (str): the starting date in yyyy-MM-dd format, eg. 2019-02-01. 1159 | enddate (str): the ending date in yyyy-MM-dd format, eg. 2019-02-28. 1160 | sort (Union[str, Sort]): the sorting preference, use "asc" to sort by ascending and "desc" to sort 1161 | by descending. ("asc") 1162 | 1163 | Returns: 1164 | Dict[str, Any]: the dictionary with the daily average gas used over gas limit. 1165 | 1166 | """ 1167 | return await self.general(action='dailynetutilization', startdate=startdate, enddate=enddate, sort=sort) 1168 | 1169 | async def dailyavghashrate(self, startdate: str, enddate: str, sort: Union[str, Sort] = Sort.Asc) -> Dict[str, Any]: 1170 | """ 1171 | Return the historical measure of processing power of the Ethereum network. (PRO) 1172 | 1173 | https://docs.etherscan.io/api-endpoints/stats-1#get-daily-average-network-hash-rate 1174 | 1175 | Args: 1176 | startdate (str): the starting date in yyyy-MM-dd format, eg. 2019-02-01. 1177 | enddate (str): the ending date in yyyy-MM-dd format, eg. 2019-02-28. 1178 | sort (Union[str, Sort]): the sorting preference, use "asc" to sort by ascending and "desc" to sort 1179 | by descending. ("asc") 1180 | 1181 | Returns: 1182 | Dict[str, Any]: the dictionary with the historical measure of processing power. 1183 | 1184 | """ 1185 | return await self.general(action='dailyavghashrate', startdate=startdate, enddate=enddate, sort=sort) 1186 | 1187 | async def dailytx(self, startdate: str, enddate: str, sort: Union[str, Sort] = Sort.Asc) -> Dict[str, Any]: 1188 | """ 1189 | Return the number of transactions performed on the Ethereum blockchain per day. (PRO) 1190 | 1191 | https://docs.etherscan.io/api-endpoints/stats-1#get-daily-transaction-count 1192 | 1193 | Args: 1194 | startdate (str): the starting date in yyyy-MM-dd format, eg. 2019-02-01. 1195 | enddate (str): the ending date in yyyy-MM-dd format, eg. 2019-02-28. 1196 | sort (Union[str, Sort]): the sorting preference, use "asc" to sort by ascending and "desc" to sort 1197 | by descending. ("asc") 1198 | 1199 | Returns: 1200 | Dict[str, Any]: the dictionary with the number of transactions performed per day. 1201 | 1202 | """ 1203 | return await self.general(action='dailytx', startdate=startdate, enddate=enddate, sort=sort) 1204 | 1205 | async def dailyavgnetdifficulty( 1206 | self, startdate: str, enddate: str, sort: Union[str, Sort] = Sort.Asc 1207 | ) -> Dict[str, Any]: 1208 | """ 1209 | Return the historical mining difficulty of the Ethereum network. (PRO) 1210 | 1211 | https://docs.etherscan.io/api-endpoints/stats-1#get-daily-average-network-difficulty 1212 | 1213 | Args: 1214 | startdate (str): the starting date in yyyy-MM-dd format, eg. 2019-02-01. 1215 | enddate (str): the ending date in yyyy-MM-dd format, eg. 2019-02-28. 1216 | sort (Union[str, Sort]): the sorting preference, use "asc" to sort by ascending and "desc" to sort 1217 | by descending. ("asc") 1218 | 1219 | Returns: 1220 | Dict[str, Any]: the dictionary with the historical mining difficulty. 1221 | 1222 | """ 1223 | return await self.general(action='dailyavgnetdifficulty', startdate=startdate, enddate=enddate, sort=sort) 1224 | 1225 | async def ethdailymarketcap( 1226 | self, startdate: str, enddate: str, sort: Union[str, Sort] = Sort.Asc 1227 | ) -> Dict[str, Any]: 1228 | """ 1229 | Return the historical Ether daily market capitalization. (PRO) 1230 | 1231 | https://docs.etherscan.io/api-endpoints/stats-1#get-ether-historical-daily-market-cap 1232 | 1233 | Args: 1234 | startdate (str): the starting date in yyyy-MM-dd format, eg. 2019-02-01. 1235 | enddate (str): the ending date in yyyy-MM-dd format, eg. 2019-02-28. 1236 | sort (Union[str, Sort]): the sorting preference, use "asc" to sort by ascending and "desc" to sort 1237 | by descending. ("asc") 1238 | 1239 | Returns: 1240 | Dict[str, Any]: the dictionary with the historical Ether daily market capitalization. 1241 | 1242 | """ 1243 | return await self.general(action='ethdailymarketcap', startdate=startdate, enddate=enddate, sort=sort) 1244 | 1245 | async def ethdailyprice(self, startdate: str, enddate: str, sort: Union[str, Sort] = Sort.Asc) -> Dict[str, Any]: 1246 | """ 1247 | Return the historical price of 1 ETH. (PRO) 1248 | 1249 | https://docs.etherscan.io/api-endpoints/stats-1#get-ether-historical-price 1250 | 1251 | Args: 1252 | startdate (str): the starting date in yyyy-MM-dd format, eg. 2019-02-01. 1253 | enddate (str): the ending date in yyyy-MM-dd format, eg. 2019-02-28. 1254 | sort (Union[str, Sort]): the sorting preference, use "asc" to sort by ascending and "desc" to sort 1255 | by descending. ("asc") 1256 | 1257 | Returns: 1258 | Dict[str, Any]: the dictionary with the historical price 1 ETH. 1259 | 1260 | """ 1261 | return await self.general(action='ethdailyprice', startdate=startdate, enddate=enddate, sort=sort) 1262 | 1263 | async def dailyavgblocksize( 1264 | self, startdate: str, enddate: str, sort: Union[str, Sort] = Sort.Asc 1265 | ) -> Dict[str, Any]: 1266 | """ 1267 | Return the daily average block size within a date range. (PRO) 1268 | 1269 | https://docs.etherscan.io/api-endpoints/blocks#get-daily-average-block-size 1270 | 1271 | Args: 1272 | startdate (str): the starting date in yyyy-MM-dd format, eg. 2019-02-01. 1273 | enddate (str): the ending date in yyyy-MM-dd format, eg. 2019-02-28. 1274 | sort (Union[str, Sort]): the sorting preference, use "asc" to sort by ascending and "desc" to sort 1275 | by descending. ("asc") 1276 | 1277 | Returns: 1278 | Dict[str, Any]: the dictionary with the daily average block size within a date range. 1279 | 1280 | """ 1281 | return await self.general(action='dailyavgblocksize', startdate=startdate, enddate=enddate, sort=sort) 1282 | 1283 | async def dailyblkcount(self, startdate: str, enddate: str, sort: Union[str, Sort] = Sort.Asc) -> Dict[str, Any]: 1284 | """ 1285 | Return the number of blocks mined daily and the amount of block rewards. (PRO) 1286 | 1287 | https://docs.etherscan.io/api-endpoints/blocks#get-daily-block-count-and-rewards 1288 | 1289 | Args: 1290 | startdate (str): the starting date in yyyy-MM-dd format, eg. 2019-02-01. 1291 | enddate (str): the ending date in yyyy-MM-dd format, eg. 2019-02-28. 1292 | sort (Union[str, Sort]): the sorting preference, use "asc" to sort by ascending and "desc" to sort 1293 | by descending. ("asc") 1294 | 1295 | Returns: 1296 | Dict[str, Any]: the dictionary with the number of blocks mined daily and the amount of block rewards. 1297 | 1298 | """ 1299 | return await self.general(action='dailyblkcount', startdate=startdate, enddate=enddate, sort=sort) 1300 | 1301 | async def dailyblockrewards( 1302 | self, startdate: str, enddate: str, sort: Union[str, Sort] = Sort.Asc 1303 | ) -> Dict[str, Any]: 1304 | """ 1305 | Return the amount of block rewards distributed to miners daily. (PRO) 1306 | 1307 | https://docs.etherscan.io/api-endpoints/blocks#get-daily-block-rewards 1308 | 1309 | Args: 1310 | startdate (str): the starting date in yyyy-MM-dd format, eg. 2019-02-01. 1311 | enddate (str): the ending date in yyyy-MM-dd format, eg. 2019-02-28. 1312 | sort (Union[str, Sort]): the sorting preference, use "asc" to sort by ascending and "desc" to sort 1313 | by descending. ("asc") 1314 | 1315 | Returns: 1316 | Dict[str, Any]: the dictionary with the amount of block rewards distributed to miners daily. 1317 | 1318 | """ 1319 | return await self.general(action='dailyblockrewards', startdate=startdate, enddate=enddate, sort=sort) 1320 | 1321 | async def dailyavgblocktime( 1322 | self, startdate: str, enddate: str, sort: Union[str, Sort] = Sort.Asc 1323 | ) -> Dict[str, Any]: 1324 | """ 1325 | Return the daily average of time needed for a block to be successfully mined. (PRO) 1326 | 1327 | https://docs.etherscan.io/api-endpoints/blocks#get-daily-average-time-for-a-block-to-be-included-in-the-ethereum-blockchain 1328 | 1329 | Args: 1330 | startdate (str): the starting date in yyyy-MM-dd format, eg. 2019-02-01. 1331 | enddate (str): the ending date in yyyy-MM-dd format, eg. 2019-02-28. 1332 | sort (Union[str, Sort]): the sorting preference, use "asc" to sort by ascending and "desc" to sort 1333 | by descending. ("asc") 1334 | 1335 | Returns: 1336 | Dict[str, Any]: the dictionary with the daily average of time needed for a block to be successfully mined. 1337 | 1338 | """ 1339 | return await self.general(action='dailyavgblocktime', startdate=startdate, enddate=enddate, sort=sort) 1340 | 1341 | async def dailyuncleblkcount( 1342 | self, startdate: str, enddate: str, sort: Union[str, Sort] = Sort.Asc 1343 | ) -> Dict[str, Any]: 1344 | """ 1345 | Return the number of 'Uncle' blocks mined daily and the amount of 'Uncle' block rewards. (PRO) 1346 | 1347 | https://docs.etherscan.io/api-endpoints/blocks#get-daily-uncle-block-count-and-rewards 1348 | 1349 | Args: 1350 | startdate (str): the starting date in yyyy-MM-dd format, eg. 2019-02-01. 1351 | enddate (str): the ending date in yyyy-MM-dd format, eg. 2019-02-28. 1352 | sort (Union[str, Sort]): the sorting preference, use "asc" to sort by ascending and "desc" to sort 1353 | by descending. ("asc") 1354 | 1355 | Returns: 1356 | Dict[str, Any]: the dictionary with the number of 'Uncle' blocks mined daily and the amount of 1357 | 'Uncle' block rewards. 1358 | 1359 | """ 1360 | return await self.general(action='dailyuncleblkcount', startdate=startdate, enddate=enddate, sort=sort) 1361 | 1362 | async def dailyavggaslimit(self, startdate: str, enddate: str, sort: Union[str, Sort] = Sort.Asc) -> Dict[str, Any]: 1363 | """ 1364 | Return the historical daily average gas limit of the Ethereum network. (PRO) 1365 | 1366 | https://docs.etherscan.io/api-endpoints/gas-tracker#get-daily-average-gas-limit 1367 | 1368 | Args: 1369 | startdate (str): the starting date in yyyy-MM-dd format, eg. 2019-02-01. 1370 | enddate (str): the ending date in yyyy-MM-dd format, eg. 2019-02-28. 1371 | sort (Union[str, Sort]): the sorting preference, use "asc" to sort by ascending and "desc" to sort 1372 | by descending. ("asc") 1373 | 1374 | Returns: 1375 | Dict[str, Any]: the dictionary with the historical daily average gas limit of the Ethereum network. 1376 | 1377 | """ 1378 | return await self.general(action='dailyavggaslimit', startdate=startdate, enddate=enddate, sort=sort) 1379 | 1380 | async def dailygasused(self, startdate: str, enddate: str, sort: Union[str, Sort] = Sort.Asc) -> Dict[str, Any]: 1381 | """ 1382 | Return the total amount of gas used daily for transctions on the Ethereum network. (PRO) 1383 | 1384 | https://docs.etherscan.io/api-endpoints/gas-tracker#get-ethereum-daily-total-gas-used 1385 | 1386 | Args: 1387 | startdate (str): the starting date in yyyy-MM-dd format, eg. 2019-02-01. 1388 | enddate (str): the ending date in yyyy-MM-dd format, eg. 2019-02-28. 1389 | sort (Union[str, Sort]): the sorting preference, use "asc" to sort by ascending and "desc" to sort 1390 | by descending. ("asc") 1391 | 1392 | Returns: 1393 | Dict[str, Any]: the dictionary with the total amount of gas used daily for transctions on the Ethereum 1394 | network. 1395 | 1396 | """ 1397 | return await self.general(action='dailygasused', startdate=startdate, enddate=enddate, sort=sort) 1398 | 1399 | async def dailyavggasprice(self, startdate: str, enddate: str, sort: Union[str, Sort] = Sort.Asc) -> Dict[str, Any]: 1400 | """ 1401 | Return the daily average gas price used on the Ethereum network. (PRO) 1402 | 1403 | https://docs.etherscan.io/api-endpoints/gas-tracker#get-daily-average-gas-price 1404 | 1405 | Args: 1406 | startdate (str): the starting date in yyyy-MM-dd format, eg. 2019-02-01. 1407 | enddate (str): the ending date in yyyy-MM-dd format, eg. 2019-02-28. 1408 | sort (Union[str, Sort]): the sorting preference, use "asc" to sort by ascending and "desc" to sort 1409 | by descending. ("asc") 1410 | 1411 | Returns: 1412 | Dict[str, Any]: the dictionary with the daily average gas price used on the Ethereum network. 1413 | 1414 | """ 1415 | return await self.general(action='dailyavggasprice', startdate=startdate, enddate=enddate, sort=sort) 1416 | 1417 | async def tokensupply(self, contractaddress: str) -> Dict[str, Any]: 1418 | """ 1419 | Return the current amount of an ERC-20 token in circulation. 1420 | 1421 | https://docs.etherscan.io/api-endpoints/tokens#get-erc20-token-totalsupply-by-contractaddress 1422 | 1423 | Args: 1424 | contractaddress (str): the contract address of the ERC-20 token. 1425 | 1426 | Returns: 1427 | Dict[str, Any]: the dictionary with the current amount of an ERC-20 token in circulation. 1428 | 1429 | """ 1430 | action = 'tokensupply' 1431 | params = { 1432 | 'module': self.module, 1433 | 'action': action, 1434 | 'apikey': self.key, 1435 | 'contractaddress': contractaddress 1436 | } 1437 | return await async_get(self.url, params=aiohttp_params(params), headers=self.headers) 1438 | 1439 | async def tokensupplyhistory(self, contractaddress: str, blockno: int) -> Dict[str, Any]: 1440 | """ 1441 | Return the amount of an ERC-20 token in circulation at a certain block height. (PRO) 1442 | 1443 | https://docs.etherscan.io/api-endpoints/tokens#get-historical-erc20-token-totalsupply-by-contractaddress-and-blockno 1444 | 1445 | Args: 1446 | contractaddress (str): the contract address of the ERC-20 token. 1447 | blockno (int): the block number to check total supply. 1448 | 1449 | Returns: 1450 | Dict[str, Any]: the dictionary with the amount of an ERC-20 token in circulation at a certain block height. 1451 | 1452 | """ 1453 | action = 'tokensupplyhistory' 1454 | params = { 1455 | 'module': self.module, 1456 | 'action': action, 1457 | 'apikey': self.key, 1458 | 'contractaddress': contractaddress, 1459 | 'blockno': blockno 1460 | } 1461 | return await async_get(self.url, params=aiohttp_params(params), headers=self.headers) 1462 | -------------------------------------------------------------------------------- /py_eth_async/client.py: -------------------------------------------------------------------------------- 1 | import random 2 | from typing import Optional 3 | 4 | import aiohttp 5 | import requests 6 | from aiohttp_socks import ProxyConnector 7 | from eth_account.signers.local import LocalAccount 8 | from fake_useragent import UserAgent 9 | from web3 import Web3 10 | 11 | from py_eth_async.contracts import Contracts 12 | from py_eth_async.data.models import Network, Networks 13 | from py_eth_async.exceptions import InvalidProxy 14 | from py_eth_async.nfts import NFTs 15 | from py_eth_async.transactions import Transactions 16 | from py_eth_async.wallet import Wallet 17 | 18 | 19 | class Client: 20 | """ 21 | The client that is used to interact with all library functions. 22 | 23 | Attributes: 24 | network (Network): a network instance. 25 | account (Optional[LocalAccount]): imported account. 26 | w3 (Web3): a Web3 instance. 27 | 28 | """ 29 | network: Network 30 | account: Optional[LocalAccount] 31 | w3: Web3 32 | 33 | def __init__( 34 | self, private_key: Optional[str] = None, network: Network = Networks.Goerli, proxy: Optional[str] = None, 35 | check_proxy: bool = True 36 | ) -> None: 37 | """ 38 | Initialize the class. 39 | 40 | Args: 41 | private_key (str): a private key of a wallet, specify '' in order not to import the wallet. 42 | (generate a new one) 43 | network (Network): a network instance. (Goerli) 44 | proxy (Optional[str]): an HTTP or SOCKS5 IPv4 proxy in one of the following formats: 45 | 46 | - login:password@proxy:port 47 | - http://login:password@proxy:port 48 | - socks5://login:password@proxy:port 49 | - proxy:port 50 | - http://proxy:port 51 | 52 | check_proxy (bool): check if the proxy is working. (True) 53 | 54 | """ 55 | self.network = network 56 | self.headers = { 57 | 'accept': '*/*', 58 | 'accept-language': 'en-US,en;q=0.9', 59 | 'content-type': 'application/json', 60 | 'user-agent': UserAgent().chrome 61 | } 62 | self.proxy = proxy 63 | self.connector = None 64 | if self.proxy: 65 | try: 66 | if 'http' not in self.proxy and 'socks5' not in self.proxy: 67 | self.proxy = f'http://{self.proxy}' 68 | 69 | self.connector = ProxyConnector.from_url(url=self.proxy) 70 | if check_proxy: 71 | your_ip = requests.get( 72 | 'http://eth0.me/', proxies={'http': self.proxy, 'https': self.proxy}, timeout=10 73 | ).text.rstrip() 74 | if your_ip not in proxy: 75 | raise InvalidProxy(f"Proxy doesn't work! Your IP is {your_ip}.") 76 | 77 | except InvalidProxy: 78 | pass 79 | 80 | except Exception as e: 81 | raise InvalidProxy(str(e)) 82 | 83 | self.w3 = Web3(provider=Web3.AsyncHTTPProvider( 84 | endpoint_uri=self.network.rpc, request_kwargs={'proxy': self.proxy, 'headers': self.headers} 85 | )) 86 | if private_key: 87 | self.account = self.w3.eth.account.from_key(private_key=private_key) 88 | 89 | elif private_key is None: 90 | self.account = self.w3.eth.account.create(extra_entropy=str(random.randint(1, 999_999_999))) 91 | 92 | else: 93 | self.account = None 94 | 95 | self.contracts = Contracts(self) 96 | self.nfts = NFTs(self) 97 | self.transactions = Transactions(self) 98 | self.wallet = Wallet(self) 99 | 100 | async def setup_proxy(self) -> Web3: 101 | provider = Web3.AsyncHTTPProvider( 102 | endpoint_uri=self.network.rpc, request_kwargs={'headers': self.headers} 103 | ) 104 | await provider.cache_async_session( 105 | session=aiohttp.ClientSession(connector=self.connector, raise_for_status=True) 106 | ) 107 | self.w3 = Web3(provider=provider) 108 | return self.w3 109 | -------------------------------------------------------------------------------- /py_eth_async/contracts.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import json 3 | from typing import Union, Optional, List, Dict, Any, Tuple 4 | 5 | from eth_typing import ChecksumAddress 6 | from evmdasm import EvmBytecode 7 | from pretty_utils.type_functions.strings import text_between 8 | from web3.contract import AsyncContract 9 | 10 | from py_eth_async.data import types 11 | from py_eth_async.data.models import DefaultABIs, ABI, Function, RawContract 12 | from py_eth_async.utils import checksum, async_get 13 | 14 | 15 | class Contracts: 16 | """ 17 | Class with functions related to contracts. 18 | 19 | Attributes: 20 | client (Client): the Client instance. 21 | 22 | """ 23 | 24 | def __init__(self, client) -> None: 25 | """ 26 | Initialize the class. 27 | 28 | Args: 29 | client (Client): the Client instance. 30 | 31 | """ 32 | self.client = client 33 | 34 | @staticmethod 35 | async def get_signature(hex_signature: str) -> Optional[list]: 36 | """ 37 | Find all matching signatures in the database of https://www.4byte.directory/. 38 | 39 | Args: 40 | hex_signature (str): a signature hash. 41 | 42 | Returns: 43 | Optional[list]: matches found. 44 | 45 | """ 46 | try: 47 | response = await async_get(f'https://www.4byte.directory/api/v1/signatures/?hex_signature={hex_signature}') 48 | results = response['results'] 49 | return [m['text_signature'] for m in sorted(results, key=lambda result: result['created_at'])] 50 | 51 | except: 52 | return 53 | 54 | @staticmethod 55 | async def parse_function(text_signature: str) -> dict: 56 | """ 57 | Construct a function dictionary for the Application Binary Interface (ABI) based on the provided text signature. 58 | 59 | Args: 60 | text_signature (str): a text signature, e.g. approve(address,uint256). 61 | 62 | Returns: 63 | dict: the function dictionary for the ABI. 64 | 65 | """ 66 | name, sign = text_signature.split('(', 1) 67 | sign = sign[:-1] 68 | tuples = [] 69 | while '(' in sign: 70 | tuple_ = text_between(text=sign[:-1], begin='(', end=')') 71 | tuples.append(tuple_.split(',') or []) 72 | sign = sign.replace(f'({tuple_})', 'tuple') 73 | 74 | inputs = sign.split(',') 75 | if inputs == ['']: 76 | inputs = [] 77 | 78 | function = { 79 | 'type': 'function', 80 | 'name': name, 81 | 'inputs': [], 82 | 'outputs': [{'type': 'uint256'}] 83 | } 84 | i = 0 85 | for type_ in inputs: 86 | input_ = {'type': type_} 87 | if type_ == 'tuple': 88 | input_['components'] = [{'type': comp_type} for comp_type in tuples[i]] 89 | i += 1 90 | 91 | function['inputs'].append(input_) 92 | 93 | return function 94 | 95 | @staticmethod 96 | async def get_contract_attributes(contract: types.Contract) -> Tuple[ChecksumAddress, Optional[list]]: 97 | """ 98 | Convert different types of contract to its address and ABI. 99 | 100 | Args: 101 | contract (Contract): the contract address or instance. 102 | 103 | Returns: 104 | Tuple[ChecksumAddress, Optional[list]]: the checksummed contract address and ABI. 105 | 106 | """ 107 | if isinstance(contract, (AsyncContract, RawContract)): 108 | return contract.address, contract.abi 109 | 110 | return checksum(contract), None 111 | 112 | async def get_abi( 113 | self, contract_address: types.Contract, raw_json: bool = False 114 | ) -> Union[str, List[Dict[str, Any]]]: 115 | """ 116 | Get a contract ABI from the Blockscan API, if unsuccessful, parses it based on the contract source code 117 | (it may be incorrect or incomplete). 118 | 119 | Args: 120 | contract_address (Contract): the contract address or instance. 121 | raw_json (bool): if True, it returns serialize string, otherwise it returns Python list. (False) 122 | 123 | Returns: 124 | Union[str, List[Dict[str, Any]]]: the ABI. 125 | 126 | """ 127 | contract_address, abi = await self.get_contract_attributes(contract_address) 128 | abi = [] 129 | if self.client.network.api and self.client.network.api.key: 130 | try: 131 | abi = (await self.client.network.api.functions.contract.getabi(contract_address))['result'] 132 | abi = json.loads(abi) 133 | 134 | except: 135 | abi = [] 136 | 137 | if not abi: 138 | bytecode = await self.client.w3.eth.get_code(contract_address) 139 | opcodes = EvmBytecode(bytecode).disassemble() 140 | hex_signatures = set() 141 | for i in range(len(opcodes) - 3): 142 | if ( 143 | opcodes[i].name == 'PUSH4' 144 | and opcodes[i + 1].name == 'EQ' 145 | and opcodes[i + 2].name == 'PUSH2' 146 | and opcodes[i + 3].name == 'JUMPI' 147 | ): 148 | hex_signatures.add(opcodes[i].operand) 149 | hex_signatures = list(hex_signatures) 150 | 151 | text_signatures = [] 152 | for i, hex_signature in enumerate(hex_signatures): 153 | signature_for_hash = await Contracts.get_signature(hex_signature=hex_signature) 154 | while signature_for_hash is None: 155 | await asyncio.sleep(1) 156 | signature_for_hash = await Contracts.get_signature(hex_signature=hex_signature) 157 | 158 | if signature_for_hash: 159 | text_signatures.append(signature_for_hash[0]) 160 | 161 | for text_signature in text_signatures: 162 | try: 163 | abi.append(await Contracts.parse_function(text_signature=text_signature)) 164 | 165 | except: 166 | pass 167 | 168 | if raw_json: 169 | return json.dumps(abi) 170 | 171 | return abi 172 | 173 | async def default_token(self, contract_address: types.Contract) -> AsyncContract: 174 | """ 175 | Get a token contract instance with a standard set of functions. 176 | 177 | Args: 178 | contract_address (Contract): the contract address or instance of token. 179 | 180 | Returns: 181 | AsyncContract: the token contract instance. 182 | 183 | """ 184 | contract_address, abi = await self.get_contract_attributes(contract_address) 185 | return self.client.w3.eth.contract(address=contract_address, abi=DefaultABIs.Token) 186 | 187 | async def default_nft(self, contract_address: types.Contract) -> AsyncContract: 188 | """ 189 | Get a NFT contract instance with a standard set of functions. 190 | 191 | Args: 192 | contract_address (Contract): the contract address or instance of a NFT collection. 193 | 194 | Returns: 195 | AsyncContract: the NFT contract instance. 196 | 197 | """ 198 | contract_address, abi = await self.get_contract_attributes(contract_address) 199 | return self.client.w3.eth.contract(address=contract_address, abi=DefaultABIs.NFT) 200 | 201 | async def get( 202 | self, contract_address: types.Contract, abi: Optional[Union[list, str]] = None, 203 | proxy_address: Optional[types.Contract] = None 204 | ) -> AsyncContract: 205 | """ 206 | Get a contract instance. 207 | 208 | Args: 209 | contract_address (Contract): the contract address or instance. 210 | abi (Optional[Union[list, str]]): the contract ABI. (get it using the 'get_abi' function) 211 | proxy_address (Optional[Contract]): the contract proxy address. (None) 212 | 213 | Returns: 214 | AsyncContract: the contract instance. 215 | 216 | """ 217 | contract_address, contract_abi = await self.get_contract_attributes(contract_address) 218 | if not abi and not contract_abi: 219 | if proxy_address: 220 | proxy_address, proxy_abi = await self.get_contract_attributes(proxy_address) 221 | if not proxy_abi: 222 | proxy_abi = await self.get_abi(contract_address=proxy_address) 223 | 224 | contract_abi = proxy_abi 225 | 226 | else: 227 | contract_abi = await self.get_abi(contract_address=contract_address) 228 | 229 | if not abi: 230 | abi = contract_abi 231 | 232 | if abi: 233 | return self.client.w3.eth.contract(address=contract_address, abi=abi) 234 | 235 | return self.client.w3.eth.contract(address=contract_address) 236 | 237 | async def get_functions(self, contract: types.Contract) -> List[Function]: 238 | """ 239 | Get functions of a contract in human-readable form. 240 | 241 | Args: 242 | contract (Contract): the contract address or instance. 243 | 244 | Returns: 245 | List[Function]: functions of the contract. 246 | 247 | """ 248 | if not isinstance(contract, AsyncContract): 249 | contract = await self.get(contract_address=contract) 250 | 251 | abi = contract.abi or [] 252 | return ABI(abi=abi).functions 253 | -------------------------------------------------------------------------------- /py_eth_async/data/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SecorD0/py-eth-async/3b00a55e3b678c0c6e458ee93dd67c6690c7826d/py_eth_async/data/__init__.py -------------------------------------------------------------------------------- /py_eth_async/data/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from dotenv import load_dotenv 4 | 5 | load_dotenv() 6 | 7 | ETHEREUM_API_KEY = str(os.getenv('ETHEREUM_API_KEY')) 8 | ARBITRUM_API_KEY = str(os.getenv('ARBITRUM_API_KEY')) 9 | OPTIMISM_API_KEY = str(os.getenv('OPTIMISM_API_KEY')) 10 | BSC_API_KEY = str(os.getenv('BSC_API_KEY')) 11 | POLYGON_API_KEY = str(os.getenv('POLYGON_API_KEY')) 12 | AVALANCHE_API_KEY = str(os.getenv('AVALANCHE_API_KEY')) 13 | ZKSYNC_ERA_API_KEY = str(os.getenv('ZKSYNC_ERA_API_KEY')) 14 | MOONBEAM_API_KEY = str(os.getenv('MOONBEAM_API_KEY')) 15 | FANTOM_API_KEY = str(os.getenv('FANTOM_API_KEY')) 16 | CELO_API_KEY = str(os.getenv('CELO_API_KEY')) 17 | GNOSIS_API_KEY = str(os.getenv('GNOSIS_API_KEY')) 18 | HECO_API_KEY = str(os.getenv('HECO_API_KEY')) 19 | GOERLI_API_KEY = str(os.getenv('GOERLI_API_KEY')) 20 | SEPOLIA_API_KEY = str(os.getenv('SEPOLIA_API_KEY')) 21 | -------------------------------------------------------------------------------- /py_eth_async/data/types.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | 3 | from web3 import Web3, types 4 | from web3.contract import AsyncContract 5 | 6 | from .models import RawContract, TokenAmount, Ether, Wei, GWei 7 | 8 | Address = Union[str, types.Address, types.ChecksumAddress, types.ENS] 9 | Amount = Union[float, int, TokenAmount, Ether, Wei] 10 | Contract = Union[str, types.Address, types.ChecksumAddress, types.ENS, RawContract, AsyncContract] 11 | GasLimit = Union[int, Wei] 12 | GasPrice = Union[float, int, Wei, GWei] 13 | Web3Async = Web3 14 | -------------------------------------------------------------------------------- /py_eth_async/exceptions.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Dict, Any 2 | 3 | 4 | class ClientException(Exception): 5 | pass 6 | 7 | 8 | class InvalidProxy(ClientException): 9 | pass 10 | 11 | 12 | class APIException(Exception): 13 | pass 14 | 15 | 16 | class ContractException(Exception): 17 | pass 18 | 19 | 20 | class NFTException(Exception): 21 | pass 22 | 23 | 24 | class TransactionException(Exception): 25 | pass 26 | 27 | 28 | class NoSuchToken(TransactionException): 29 | pass 30 | 31 | 32 | class InsufficientBalance(TransactionException): 33 | pass 34 | 35 | 36 | class GasPriceTooHigh(TransactionException): 37 | pass 38 | 39 | 40 | class FailedToApprove(TransactionException): 41 | pass 42 | 43 | 44 | class WalletException(Exception): 45 | pass 46 | 47 | 48 | class HTTPException(Exception): 49 | """ 50 | An exception that occurs when an HTTP request is unsuccessful. 51 | 52 | Attributes: 53 | response (Optional[Dict[str, Any]]): a JSON response to a request. 54 | status_code (Optional[int]): a request status code. 55 | 56 | """ 57 | response: Optional[Dict[str, Any]] 58 | status_code: Optional[int] 59 | 60 | def __init__(self, response: Optional[Dict[str, Any]] = None, status_code: Optional[int] = None) -> None: 61 | """ 62 | Initialize the class. 63 | 64 | Args: 65 | response (Optional[Dict[str, Any]]): a JSON response to a request. (None) 66 | status_code (Optional[int]): a request status code. (None) 67 | 68 | """ 69 | self.response = response 70 | self.status_code = status_code 71 | 72 | def __str__(self): 73 | if self.response: 74 | return f'{self.status_code}: {self.response}' 75 | 76 | return f'{self.status_code}' 77 | -------------------------------------------------------------------------------- /py_eth_async/nfts.py: -------------------------------------------------------------------------------- 1 | import json 2 | from typing import Union 3 | from urllib.request import urlopen 4 | 5 | from py_eth_async import exceptions 6 | from py_eth_async.data import types 7 | from py_eth_async.data.models import NFT 8 | from py_eth_async.utils import async_get 9 | 10 | 11 | class NFTs: 12 | """ 13 | Class with functions related to NTFs. 14 | 15 | Attributes: 16 | client (Client): the Client instance. 17 | 18 | """ 19 | 20 | def __init__(self, client) -> None: 21 | """ 22 | Initialize the class. 23 | 24 | Args: 25 | client (Client): the Client instance. 26 | 27 | """ 28 | self.client = client 29 | 30 | async def get_info(self, contract: types.Contract, token_id: Union[int, str] = None) -> NFT: 31 | """ 32 | Get information about a NFT. 33 | 34 | Args: 35 | contract (Contract): the contract address or instance of a NFT collection. 36 | token_id (Union[int, str]): the NFT ID to parse the owner and attributes. (None) 37 | 38 | Returns: 39 | NFT: the NFT. 40 | 41 | """ 42 | contract_address, abi = await self.client.contracts.get_contract_attributes(contract) 43 | contract = await self.client.contracts.default_nft(contract_address) 44 | nft = NFT(contract_address=contract_address) 45 | nft.name = await contract.functions.name().call() 46 | nft.symbol = await contract.functions.symbol().call() 47 | nft.total_supply = await contract.functions.totalSupply().call() 48 | if token_id: 49 | try: 50 | token_id = int(token_id) 51 | if token_id >= nft.total_supply: 52 | exceptions.NFTException('The token ID exceeds total supply!') 53 | 54 | nft.id = token_id 55 | try: 56 | nft.owner = await contract.functions.ownerOf(token_id).call() 57 | except: 58 | pass 59 | 60 | image_url = await contract.functions.tokenURI(token_id).call() 61 | if 'data:application/json' in image_url: 62 | with urlopen(image_url) as response: 63 | response = json.loads(response.read()) 64 | 65 | else: 66 | if 'ipfs://' in image_url: 67 | image_url = image_url.replace('ipfs://', 'https://ipfs.io/ipfs/') 68 | 69 | nft.image_url = image_url 70 | response = await async_get(image_url) 71 | 72 | if 'attributes' in response and response['attributes']: 73 | nft.parse_attributes(response['attributes']) 74 | 75 | except: 76 | pass 77 | 78 | return nft 79 | -------------------------------------------------------------------------------- /py_eth_async/transactions.py: -------------------------------------------------------------------------------- 1 | from typing import Union, Optional, Dict, Any, Tuple, List 2 | 3 | from eth_account.datastructures import SignedTransaction, SignedMessage 4 | from eth_account.messages import encode_defunct 5 | from hexbytes import HexBytes 6 | from pretty_utils.type_functions.classes import AutoRepr 7 | from web3.contract import AsyncContract 8 | from web3.types import TxReceipt, _Hash32, TxParams, Address 9 | 10 | from py_eth_async import exceptions 11 | from py_eth_async.data import types 12 | from py_eth_async.data.models import ( 13 | TxHistory, RawTxHistory, GWei, Wei, Ether, TokenAmount, CommonValues, CoinTx, TxArgs 14 | ) 15 | from py_eth_async.data.types import Web3Async 16 | from py_eth_async.utils import api_key_required, checksum 17 | 18 | 19 | class Tx(AutoRepr): 20 | """ 21 | An instance of transaction for easy execution of actions on it. 22 | 23 | Attributes: 24 | hash (Optional[_Hash32]): a transaction hash. 25 | params (Optional[dict]): the transaction parameters. 26 | receipt (Optional[TxReceipt]): a transaction receipt. 27 | function_identifier (Optional[str]): a function identifier. 28 | input_data (Optional[Dict[str, Any]]): an input data. 29 | 30 | """ 31 | hash: Optional[_Hash32] 32 | params: Optional[dict] 33 | receipt: Optional[TxReceipt] 34 | function_identifier: Optional[str] 35 | input_data: Optional[Dict[str, Any]] 36 | 37 | def __init__(self, tx_hash: Optional[Union[str, _Hash32]] = None, params: Optional[dict] = None) -> None: 38 | """ 39 | Initialize the class. 40 | 41 | Args: 42 | tx_hash (Optional[Union[str, _Hash32]]): the transaction hash. (None) 43 | params (Optional[dict]): a dictionary with transaction parameters. (None) 44 | 45 | """ 46 | if not tx_hash and not params: 47 | raise exceptions.TransactionException("Specify 'tx_hash' or 'params' argument values!") 48 | 49 | if isinstance(tx_hash, str): 50 | tx_hash = HexBytes(tx_hash) 51 | 52 | self.hash = tx_hash 53 | self.params = params 54 | self.receipt = None 55 | self.function_identifier = None 56 | self.input_data = None 57 | 58 | async def parse_params(self, client) -> Dict[str, Any]: 59 | """ 60 | Parse the parameters of a sent transaction. 61 | 62 | Args: 63 | client (Client): the Client instance. 64 | 65 | Returns: 66 | Dict[str, Any]: the parameters of a sent transaction. 67 | 68 | """ 69 | tx_data = await client.w3.eth.get_transaction(transaction_hash=self.hash) 70 | self.params = { 71 | 'chainId': client.network.chain_id, 72 | 'nonce': int(tx_data.get('nonce')), 73 | 'gasPrice': int(tx_data.get('gasPrice')), 74 | 'gas': int(tx_data.get('gas')), 75 | 'from': tx_data.get('from'), 76 | 'to': tx_data.get('to'), 77 | 'data': tx_data.get('input'), 78 | 'value': int(tx_data.get('value')) 79 | } 80 | return self.params 81 | 82 | async def decode_input_data( 83 | self, client, contract: types.Contract 84 | ) -> Tuple[Optional[str], Optional[Dict[str, Any]]]: 85 | """ 86 | Decode the input data of a sent transaction. 87 | 88 | Args: 89 | client (Client): the Client instance. 90 | contract (Contract): the contract instance whose ABI will be used to decode input data. 91 | 92 | Returns: 93 | Tuple[Optional[str], Optional[Dict[str, Any]]]: the function identifier and decoded input data 94 | of a sent transaction. 95 | 96 | """ 97 | if not self.params or not self.params.get('data'): 98 | await self.parse_params(client=client) 99 | 100 | try: 101 | self.function_identifier, self.input_data = await Transactions.decode_input_data( 102 | client=client, contract=contract, input_data=self.params.get('data') 103 | ) 104 | 105 | except: 106 | pass 107 | 108 | return self.function_identifier, self.input_data 109 | 110 | async def wait_for_receipt( 111 | self, client, timeout: Union[int, float] = 120, poll_latency: float = 0.1 112 | ) -> Dict[str, Any]: 113 | """ 114 | Wait for the transaction receipt. 115 | 116 | Args: 117 | client (Client): the Client instance. 118 | timeout (Union[int, float]): the receipt waiting timeout. (120 sec) 119 | poll_latency (float): the poll latency. (0.1 sec) 120 | 121 | Returns: 122 | Dict[str, Any]: the transaction receipt. 123 | 124 | """ 125 | self.receipt = dict(await client.w3.eth.wait_for_transaction_receipt( 126 | transaction_hash=self.hash, timeout=timeout, poll_latency=poll_latency 127 | )) 128 | return self.receipt 129 | 130 | async def cancel( 131 | self, client, gas_price: Optional[types.GasPrice] = None, 132 | gas_limit: Optional[types.GasLimit] = None 133 | ) -> bool: 134 | """ 135 | Cancel the transaction. 136 | 137 | Args: 138 | client (Client): the Client instance. 139 | gas_price (Optional[GasPrice]): the gas price in GWei. (parsed from the network) 140 | gas_limit (Optional[GasLimit]): the gas limit in Wei. (parsed from the network) 141 | 142 | Returns: 143 | bool: True if the transaction was sent successfully. 144 | 145 | """ 146 | if self.params and 'nonce' in self.params: 147 | if not gas_price: 148 | gas_price = (await Transactions.gas_price(w3=client.w3)).Wei 149 | 150 | elif isinstance(gas_price, (int, float)): 151 | gas_price = GWei(gas_price).Wei 152 | 153 | if gas_price < self.params.get('gasPrice') * 1.11: 154 | gas_price = int(self.params.get('gasPrice') * 1.11) 155 | 156 | tx_params = { 157 | 'chainId': client.network.chain_id, 158 | 'nonce': self.params.get('nonce'), 159 | 'to': client.account.address, 160 | 'value': 0 161 | } 162 | if client.network.tx_type == 2: 163 | tx_params['maxPriorityFeePerGas'] = (await client.transactions.max_priority_fee(w3=client.w3)).Wei 164 | tx_params['maxFeePerGas'] = gas_price + tx_params['maxPriorityFeePerGas'] 165 | 166 | else: 167 | tx_params['gasPrice'] = gas_price 168 | 169 | if not gas_limit: 170 | gas_limit = await Transactions.estimate_gas(w3=client.w3, tx_params=tx_params) 171 | 172 | elif isinstance(gas_limit, int): 173 | gas_limit = Wei(gas_limit) 174 | 175 | tx_params['gas'] = gas_limit.Wei 176 | signed_tx = client.w3.eth.account.sign_transaction( 177 | transaction_dict=tx_params, private_key=client.account.key 178 | ) 179 | tx_hash = await client.w3.eth.send_raw_transaction(transaction=signed_tx.rawTransaction) 180 | if tx_hash: 181 | self.hash = tx_hash 182 | self.params = tx_params.copy() 183 | return True 184 | 185 | return False 186 | 187 | async def speed_up( 188 | self, client, gas_price: Optional[types.GasPrice] = None, gas_limit: Optional[types.GasLimit] = None 189 | ) -> bool: 190 | """ 191 | Speed up the transaction. 192 | 193 | Args: 194 | client (Client): the Client instance. 195 | gas_price (Optional[GasPrice]): the gas price in GWei. (parsed from the network * 1.5) 196 | gas_limit (Optional[GasLimit]): the gas limit in Wei. (parsed from the network) 197 | 198 | Returns: 199 | bool: True if the transaction was sent successfully. 200 | 201 | """ 202 | if self.params and 'nonce' in self.params: 203 | if not gas_price: 204 | gas_price = int((await Transactions.gas_price(w3=client.w3)).Wei * 1.5) 205 | 206 | elif isinstance(gas_price, (int, float)): 207 | gas_price = GWei(gas_price).Wei 208 | 209 | tx_params = self.params.copy() 210 | if client.network.tx_type == 2: 211 | tx_params['maxPriorityFeePerGas'] = (await client.transactions.max_priority_fee(w3=client.w3)).Wei 212 | tx_params['maxFeePerGas'] = gas_price + tx_params['maxPriorityFeePerGas'] 213 | 214 | else: 215 | tx_params['gasPrice'] = gas_price 216 | 217 | if not gas_limit: 218 | gas_limit = await Transactions.estimate_gas(w3=client.w3, tx_params=tx_params) 219 | 220 | elif isinstance(gas_limit, int): 221 | gas_limit = Wei(gas_limit) 222 | 223 | tx_params['gas'] = gas_limit.Wei 224 | signed_tx = client.w3.eth.account.sign_transaction( 225 | transaction_dict=tx_params, private_key=client.account.key 226 | ) 227 | tx_hash = await client.w3.eth.send_raw_transaction(transaction=signed_tx.rawTransaction) 228 | if tx_hash: 229 | self.hash = tx_hash 230 | self.params = tx_params.copy() 231 | return True 232 | 233 | return False 234 | 235 | 236 | class Transactions: 237 | """ 238 | Class with functions related to transactions. 239 | 240 | Attributes: 241 | client (Client): the Client instance. 242 | 243 | """ 244 | 245 | def __init__(self, client) -> None: 246 | """ 247 | Initialize the class. 248 | 249 | Args: 250 | client (Client): the Client instance. 251 | 252 | """ 253 | self.client = client 254 | 255 | @staticmethod 256 | async def current_gas_price(w3: Web3Async) -> Wei: 257 | print("This method will be deprecated in a future update. Use 'gas_price' instead.") 258 | return await Transactions.gas_price(w3=w3) 259 | 260 | @staticmethod 261 | async def gas_price(w3: Web3Async) -> Wei: 262 | """ 263 | Get the current gas price. 264 | 265 | Args: 266 | w3 (Web3): the Web3 instance. 267 | 268 | Returns: 269 | Wei: the current gas price. 270 | 271 | """ 272 | return Wei(await w3.eth.gas_price) 273 | 274 | @staticmethod 275 | async def max_priority_fee(w3: Web3Async) -> Wei: 276 | """ 277 | Get the current max priority fee. 278 | 279 | Args: 280 | w3 (Web3): the Web3 instance. 281 | 282 | Returns: 283 | Wei: the current max priority fee. 284 | 285 | """ 286 | return Wei(await w3.eth.max_priority_fee) 287 | 288 | @staticmethod 289 | async def estimate_gas(w3: Web3Async, tx_params: TxParams) -> Wei: 290 | """ 291 | Get the estimate gas limit for a transaction with specified parameters. 292 | 293 | Args: 294 | w3 (Web3): the Web3 instance. 295 | tx_params (TxParams): parameters of the transaction. 296 | 297 | Returns: 298 | Wei: the estimate gas. 299 | 300 | """ 301 | return Wei(await w3.eth.estimate_gas(transaction=tx_params)) 302 | 303 | @staticmethod 304 | async def decode_input_data( 305 | client, contract: types.Contract, input_data: Optional[str] = None, tx_hash: Optional[_Hash32] = None 306 | ) -> Tuple[str, Dict[str, Any]]: 307 | """ 308 | Decode the input data of a sent transaction. 309 | 310 | Args: 311 | client (Client): the Client instance. 312 | contract (Contract): the contract address or instance whose ABI will be used to decode input data. 313 | input_data (Optional[str]): the raw input data. (None) 314 | tx_hash (Optional[_Hash32]): the transaction hash to parse input data. (None) 315 | 316 | Returns: 317 | Tuple[str, Dict[str, Any]]: the function identifier and decoded input data of a sent transaction. 318 | 319 | """ 320 | if not input_data and not tx_hash: 321 | raise exceptions.TransactionException("Specify 'input_data' or 'tx_hash' argument values!") 322 | 323 | if not isinstance(contract, AsyncContract): 324 | contract = await client.contracts.get(contract_address=contract) 325 | 326 | if input_data: 327 | input_data = input_data 328 | 329 | else: 330 | input_data = (await client.w3.eth.get_transaction(transaction_hash=tx_hash)).get('input') 331 | 332 | function_instance, input_data = contract.decode_function_input(input_data) 333 | return function_instance.function_identifier, input_data 334 | 335 | async def auto_add_params(self, tx_params: TxParams) -> TxParams: 336 | """ 337 | Add 'chainId', 'nonce', 'from', 'gasPrice' or 'maxFeePerGas' + 'maxPriorityFeePerGas' and 'gas' parameters to 338 | transaction parameters if they are missing. 339 | 340 | Args: 341 | tx_params (TxParams): parameters of the transaction. 342 | 343 | Returns: 344 | TxParams: parameters of the transaction with added values. 345 | 346 | """ 347 | if 'chainId' not in tx_params: 348 | tx_params['chainId'] = self.client.network.chain_id 349 | 350 | if 'nonce' not in tx_params: 351 | tx_params['nonce'] = await self.client.wallet.nonce() 352 | 353 | if 'from' not in tx_params: 354 | tx_params['from'] = self.client.account.address 355 | 356 | if 'gasPrice' not in tx_params and 'maxFeePerGas' not in tx_params: 357 | gas_price = (await self.gas_price(w3=self.client.w3)).Wei 358 | if self.client.network.tx_type == 2: 359 | tx_params['maxFeePerGas'] = gas_price 360 | 361 | else: 362 | tx_params['gasPrice'] = gas_price 363 | 364 | elif 'gasPrice' in tx_params and not int(tx_params['gasPrice']): 365 | tx_params['gasPrice'] = (await self.gas_price(w3=self.client.w3)).Wei 366 | 367 | if 'maxFeePerGas' in tx_params and 'maxPriorityFeePerGas' not in tx_params: 368 | tx_params['maxPriorityFeePerGas'] = (await self.max_priority_fee(w3=self.client.w3)).Wei 369 | tx_params['maxFeePerGas'] = tx_params['maxFeePerGas'] + tx_params['maxPriorityFeePerGas'] 370 | 371 | if 'gas' not in tx_params or not int(tx_params['gas']): 372 | tx_params['gas'] = (await self.estimate_gas(w3=self.client.w3, tx_params=tx_params)).Wei 373 | 374 | return tx_params 375 | 376 | async def sign(self, tx_params: TxParams) -> SignedTransaction: 377 | print("This method will be deprecated in a future update. Use 'sign_transaction' instead.") 378 | return await self.sign_transaction(tx_params=tx_params) 379 | 380 | async def sign_transaction(self, tx_params: TxParams) -> SignedTransaction: 381 | """ 382 | Sign a transaction. 383 | 384 | Args: 385 | tx_params (TxParams): parameters of the transaction. 386 | 387 | Returns: 388 | SignedTransaction: the signed transaction. 389 | 390 | """ 391 | return self.client.w3.eth.account.sign_transaction( 392 | transaction_dict=tx_params, private_key=self.client.account.key 393 | ) 394 | 395 | async def sign_message(self, message: str) -> SignedMessage: 396 | """ 397 | Sign a message. 398 | 399 | Args: 400 | message (str): the message. 401 | 402 | Returns: 403 | str: the signed message. 404 | 405 | """ 406 | return self.client.w3.eth.account.sign_message( 407 | encode_defunct(text=message), private_key=self.client.account.key 408 | ) 409 | 410 | async def sign_and_send(self, tx_params: TxParams) -> Tx: 411 | """ 412 | Sign and send a transaction. Additionally, add 'chainId', 'nonce', 'from', 'gasPrice' or 413 | 'maxFeePerGas' + 'maxPriorityFeePerGas' and 'gas' parameters to transaction parameters if they are missing. 414 | 415 | Args: 416 | tx_params (TxParams): parameters of the transaction. 417 | 418 | Returns: 419 | Tx: the instance of the sent transaction. 420 | 421 | """ 422 | await self.auto_add_params(tx_params=tx_params) 423 | signed_tx = await self.sign_transaction(tx_params) 424 | tx_hash = await self.client.w3.eth.send_raw_transaction(transaction=signed_tx.rawTransaction) 425 | return Tx(tx_hash=tx_hash, params=tx_params) 426 | 427 | @api_key_required 428 | async def history(self, address: Optional[str] = None, raw: bool = False) -> Union[RawTxHistory, TxHistory]: 429 | """ 430 | Get the history instance containing list of normal, internal, ERC-20 and ERC-721 transactions of 431 | a specified address. 432 | 433 | Args: 434 | address (Optional[str]): the address to get the transaction list. (imported to client address) 435 | raw (bool): whether to return the raw data. (False) 436 | 437 | Returns: 438 | Union[RawTxHistory, TxHistory]: the history instance. 439 | 440 | """ 441 | if not address: 442 | address = self.client.account.address 443 | 444 | account_api = self.client.network.api.functions.account 445 | coin_txs = (await account_api.txlist(address))['result'] 446 | internal_txs = (await account_api.txlistinternal(address))['result'] 447 | erc20_txs = (await account_api.tokentx(address))['result'] 448 | erc721_txs = (await account_api.tokennfttx(address))['result'] 449 | if raw: 450 | return RawTxHistory( 451 | address=address, coin_txs=coin_txs, internal_txs=internal_txs, erc20_txs=erc20_txs, 452 | erc721_txs=erc721_txs 453 | ) 454 | 455 | return TxHistory( 456 | address=address, coin_txs=coin_txs, internal_txs=internal_txs, erc20_txs=erc20_txs, erc721_txs=erc721_txs 457 | ) 458 | 459 | @api_key_required 460 | async def find_txs( 461 | self, contract: Union[types.Contract, List[types.Contract]], function_name: Optional[str] = '', 462 | address: Optional[types.Address] = None, after_timestamp: int = 0, before_timestamp: int = 999_999_999_999 463 | ) -> Dict[str, CoinTx]: 464 | """ 465 | Find all transactions of interaction with the contract, in addition, you can filter transactions by 466 | the name of the contract function. 467 | 468 | Args: 469 | contract (Union[Contract, List[Contract]]): the contract or a list of contracts with which 470 | the interaction took place. 471 | function_name (Optional[str]): the function name for sorting. (any) 472 | address (Optional[Address]): the address to get the transaction list. (imported to client address) 473 | after_timestamp (int): after what time to filter transactions. (0) 474 | before_timestamp (int): before what time to filter transactions. (infinity) 475 | 476 | Returns: 477 | Dict[str, CoinTx]: transactions found. 478 | 479 | """ 480 | contract_addresses = [] 481 | if isinstance(contract, list): 482 | for contract_ in contract: 483 | contract_address, abi = await self.client.contracts.get_contract_attributes(contract_) 484 | contract_addresses.append(contract_address.lower()) 485 | 486 | else: 487 | contract_address, abi = await self.client.contracts.get_contract_attributes(contract) 488 | contract_addresses.append(contract_address.lower()) 489 | 490 | if not address: 491 | address = self.client.account.address 492 | 493 | txs = {} 494 | coin_txs = (await self.client.network.api.functions.account.txlist(address))['result'] 495 | for tx in coin_txs: 496 | if ( 497 | after_timestamp < int(tx.get('timeStamp')) < before_timestamp and 498 | tx.get('isError') == '0' and 499 | tx.get('to') in contract_addresses and 500 | function_name in tx.get('functionName') 501 | ): 502 | txs[tx.get('hash')] = CoinTx(data=tx) 503 | 504 | return txs 505 | 506 | async def approved_amount( 507 | self, token: types.Contract, spender: types.Contract, owner: Optional[types.Address] = None 508 | ) -> TokenAmount: 509 | """ 510 | Get approved amount of token. 511 | 512 | Args: 513 | token (Contract): the contract address or instance of token. 514 | spender (Contract): the spender address, contract address or instance. 515 | owner (Optional[Address]): the owner address. (imported to client address) 516 | 517 | Returns: 518 | TokenAmount: the approved amount. 519 | 520 | """ 521 | contract_address, abi = await self.client.contracts.get_contract_attributes(token) 522 | contract = await self.client.contracts.default_token(contract_address) 523 | spender, abi = await self.client.contracts.get_contract_attributes(spender) 524 | if not owner: 525 | owner = self.client.account.address 526 | 527 | return TokenAmount( 528 | amount=await contract.functions.allowance(checksum(owner), checksum(spender)).call(), 529 | decimals=await contract.functions.decimals().call(), wei=True 530 | ) 531 | 532 | async def wait_for_receipt( 533 | self, tx_hash: Union[str, _Hash32], timeout: Union[int, float] = 120, poll_latency: float = 0.1 534 | ) -> Dict[str, Any]: 535 | """ 536 | Wait for a transaction receipt. 537 | 538 | Args: 539 | tx_hash (Union[str, _Hash32]): the transaction hash. 540 | timeout (Union[int, float]): the receipt waiting timeout. (120) 541 | poll_latency (float): the poll latency. (0.1 sec) 542 | 543 | Returns: 544 | Dict[str, Any]: the transaction receipt. 545 | 546 | """ 547 | return dict(await self.client.w3.eth.wait_for_transaction_receipt( 548 | transaction_hash=tx_hash, timeout=timeout, poll_latency=poll_latency 549 | )) 550 | 551 | async def send( 552 | self, token: types.Contract, recipient: types.Address, amount: types.Amount = 999_999_999_999_999, 553 | gas_price: Optional[types.GasPrice] = None, gas_limit: Optional[types.GasLimit] = None, 554 | nonce: Optional[int] = None, check_gas_price: bool = False, dry_run: bool = False 555 | ) -> Tx: 556 | """ 557 | Send a coin or token. 558 | 559 | Args: 560 | token (Contract): the contract address or instance of token to send, use '' to send the coin. 561 | recipient (Address): the recipient address. 562 | amount (Amount): an amount to send. (entire balance) 563 | gas_price (Optional[GasPrice]): the gas price in GWei. (parsed from the network) 564 | gas_limit (Optional[GasLimit]): the gas limit in Wei. (parsed from the network) 565 | nonce (Optional[int]): a nonce of the sender address. (get it using the 'nonce' function) 566 | check_gas_price (bool): if True and the gas price is higher than that specified in the 'gas_price' 567 | argument, the 'GasPriceTooHigh' error will raise. (False) 568 | dry_run (bool): if True, it creates a parameter dictionary, but doesn't send the transaction. (False) 569 | 570 | Returns: 571 | Tx: the instance of the sent transaction. 572 | 573 | """ 574 | if not token: 575 | contract = None 576 | 577 | else: 578 | contract_address, abi = await self.client.contracts.get_contract_attributes(token) 579 | contract = await self.client.contracts.default_token(contract_address) 580 | 581 | if isinstance(amount, (int, float)): 582 | if contract: 583 | amount = TokenAmount(amount=amount, decimals=await contract.functions.decimals().call()) 584 | 585 | else: 586 | amount = Ether(amount=amount) 587 | 588 | amount = amount.Wei 589 | recipient = checksum(recipient) 590 | current_gas_price = await self.gas_price(w3=self.client.w3) 591 | if not gas_price: 592 | gas_price = current_gas_price 593 | 594 | elif gas_price: 595 | if isinstance(gas_price, (int, float)): 596 | gas_price = GWei(gas_price) 597 | 598 | if check_gas_price and current_gas_price > gas_price: 599 | raise exceptions.GasPriceTooHigh() 600 | 601 | if not nonce: 602 | nonce = await self.client.wallet.nonce() 603 | 604 | tx_params = { 605 | 'chainId': self.client.network.chain_id, 606 | 'nonce': nonce, 607 | 'from': self.client.account.address 608 | } 609 | if self.client.network.tx_type == 2: 610 | tx_params['maxPriorityFeePerGas'] = (await self.client.transactions.max_priority_fee(w3=self.client.w3)).Wei 611 | tx_params['maxFeePerGas'] = gas_price.Wei + tx_params['maxPriorityFeePerGas'] 612 | 613 | else: 614 | tx_params['gasPrice'] = gas_price.Wei 615 | 616 | if contract: 617 | balance = (await self.client.wallet.balance(token=contract)).Wei 618 | if balance < amount: 619 | amount = balance 620 | 621 | tx_params.update({ 622 | 'to': contract.address, 623 | 'data': contract.encodeABI('transfer', args=TxArgs(recipient=recipient, amount=amount).tuple()) 624 | }) 625 | 626 | else: 627 | balance = (await self.client.wallet.balance()).Wei 628 | if balance < amount: 629 | amount = balance 630 | 631 | tx_params.update({ 632 | 'to': recipient, 633 | 'value': amount 634 | }) 635 | 636 | if not amount: 637 | raise exceptions.InsufficientBalance() 638 | 639 | if not gas_limit: 640 | gas_limit = await self.estimate_gas(w3=self.client.w3, tx_params=tx_params) 641 | 642 | elif isinstance(gas_limit, int): 643 | gas_limit = Wei(gas_limit) 644 | 645 | tx_params['gas'] = gas_limit.Wei 646 | if 'value' in tx_params: 647 | balance = (await self.client.wallet.balance()).Wei 648 | gas_price = tx_params.get('gasPrice') if 'gasPrice' in tx_params else tx_params.get('maxFeePerGas') 649 | available_to_send = balance - int(gas_price * tx_params.get('gas') * 1.1) 650 | if available_to_send < amount: 651 | tx_params['value'] = available_to_send 652 | 653 | if dry_run: 654 | return Tx(params=tx_params) 655 | 656 | return await self.sign_and_send(tx_params=tx_params) 657 | 658 | async def approve( 659 | self, token: types.Contract, spender: types.Address, amount: Optional[types.Amount] = None, 660 | gas_price: Optional[types.GasPrice] = None, gas_limit: Optional[types.GasLimit] = None, 661 | nonce: Optional[int] = None, check_gas_price: bool = False 662 | ) -> Tx: 663 | """ 664 | Approve token spending for specified address. 665 | 666 | Args: 667 | token (Contract): the contract address or instance of token to approve. 668 | spender (Address): the spender address, contract address or instance. 669 | amount (Optional[Amount]): an amount to approve. (infinity) 670 | gas_price (Optional[GasPrice]): the gas price in GWei. (parsed from the network) 671 | gas_limit (Optional[GasLimit]): the gas limit in Wei. (parsed from the network) 672 | nonce (Optional[int]): a nonce of the sender address. (get it using the 'nonce' function) 673 | check_gas_price (bool): if True and the gas price is higher than that specified in the 'gas_price' 674 | argument, the 'GasPriceTooHigh' error will raise. (False) 675 | 676 | Returns: 677 | Tx: the instance of the sent transaction. 678 | 679 | """ 680 | contract_address, abi = await self.client.contracts.get_contract_attributes(token) 681 | contract = await self.client.contracts.default_token(contract_address) 682 | if not amount: 683 | amount = CommonValues.InfinityInt 684 | 685 | elif isinstance(amount, (int, float)): 686 | amount = TokenAmount(amount=amount, decimals=await contract.functions.decimals().call()).Wei 687 | 688 | else: 689 | amount = amount.Wei 690 | 691 | spender = checksum(spender) 692 | current_gas_price = await self.gas_price(w3=self.client.w3) 693 | if not gas_price: 694 | gas_price = current_gas_price 695 | 696 | elif gas_price: 697 | if isinstance(gas_price, (int, float)): 698 | gas_price = GWei(gas_price) 699 | 700 | if check_gas_price and current_gas_price > gas_price: 701 | raise exceptions.GasPriceTooHigh() 702 | 703 | if not nonce: 704 | nonce = await self.client.wallet.nonce() 705 | 706 | tx_params = { 707 | 'chainId': self.client.network.chain_id, 708 | 'nonce': nonce, 709 | 'from': self.client.account.address, 710 | 'to': contract.address, 711 | 'data': contract.encodeABI('approve', args=TxArgs(spender=spender, amount=amount).tuple()) 712 | } 713 | if self.client.network.tx_type == 2: 714 | tx_params['maxPriorityFeePerGas'] = (await self.client.transactions.max_priority_fee(w3=self.client.w3)).Wei 715 | tx_params['maxFeePerGas'] = gas_price.Wei + tx_params['maxPriorityFeePerGas'] 716 | 717 | else: 718 | tx_params['gasPrice'] = gas_price.Wei 719 | 720 | if not gas_limit: 721 | gas_limit = await self.estimate_gas(w3=self.client.w3, tx_params=tx_params) 722 | 723 | elif isinstance(gas_limit, int): 724 | gas_limit = Wei(gas_limit) 725 | 726 | tx_params['gas'] = gas_limit.Wei 727 | return await self.sign_and_send(tx_params=tx_params) 728 | -------------------------------------------------------------------------------- /py_eth_async/utils.py: -------------------------------------------------------------------------------- 1 | from typing import Union, Optional, Dict, Any 2 | 3 | import aiohttp 4 | from eth_typing import ChecksumAddress 5 | from eth_utils import to_checksum_address 6 | 7 | from py_eth_async import exceptions 8 | 9 | 10 | def api_key_required(func): 11 | """Check if the Blockscan API key is specified.""" 12 | 13 | def func_wrapper(self, *args, **kwargs): 14 | if not self.client.network.api.key or not self.client.network.api.functions: 15 | raise exceptions.APIException('To use this function, you must specify the explorer API key!') 16 | 17 | else: 18 | return func(self, *args, **kwargs) 19 | 20 | return func_wrapper 21 | 22 | 23 | def checksum(address: str) -> ChecksumAddress: 24 | """ 25 | Convert an address to checksummed. 26 | 27 | Args: 28 | address (str): the address. 29 | 30 | Returns: 31 | ChecksumAddress: the checksummed address. 32 | 33 | """ 34 | return to_checksum_address(address) 35 | 36 | 37 | def aiohttp_params(params: Optional[Dict[str, Any]]) -> Optional[Dict[str, Union[str, int, float]]]: 38 | """ 39 | Convert requests params to aiohttp params. 40 | 41 | Args: 42 | params (Optional[Dict[str, Any]]): requests params. 43 | 44 | Returns: 45 | Optional[Dict[str, Union[str, int, float]]]: aiohttp params. 46 | 47 | """ 48 | print("This function will be deprecated in the next major update. " 49 | "Use the 'aiohttp_params' function from the 'pretty-utils' library.") 50 | new_params = params.copy() 51 | if not params: 52 | return 53 | 54 | for key, value in params.items(): 55 | if value is None: 56 | del new_params[key] 57 | 58 | if isinstance(value, bool): 59 | new_params[key] = str(value).lower() 60 | 61 | elif isinstance(value, bytes): 62 | new_params[key] = value.decode('utf-8') 63 | 64 | return new_params 65 | 66 | 67 | async def async_get(url: str, headers: Optional[dict] = None, **kwargs) -> Optional[dict]: 68 | """ 69 | Make a GET request and check if it was successful. 70 | 71 | Args: 72 | url (str): a URL. 73 | headers (Optional[dict]): the headers. (None) 74 | **kwargs: arguments for a GET request, e.g. 'params', 'headers', 'data' or 'json'. 75 | 76 | Returns: 77 | Optional[dict]: received dictionary in response. 78 | 79 | """ 80 | async with aiohttp.ClientSession(headers=headers) as session: 81 | async with session.get(url=url, **kwargs) as response: 82 | status_code = response.status 83 | response = await response.json() 84 | if status_code <= 201: 85 | return response 86 | 87 | raise exceptions.HTTPException(response=response, status_code=status_code) 88 | 89 | 90 | async def get_coin_symbol(chain_id: Union[int, str]) -> str: 91 | """ 92 | Get a coin symbol on a network with the specified ID. 93 | 94 | Args: 95 | chain_id (Union[int, str]): the network ID. 96 | 97 | Returns: 98 | str: the coin symbol. 99 | 100 | """ 101 | response = await async_get('https://chainid.network/chains.json') 102 | network = next((network for network in response if network['chainId'] == int(chain_id)), None) 103 | if network: 104 | return network['nativeCurrency']['symbol'] 105 | 106 | return '' 107 | -------------------------------------------------------------------------------- /py_eth_async/wallet.py: -------------------------------------------------------------------------------- 1 | from typing import Optional, Union 2 | 3 | from eth_typing import Address 4 | 5 | from py_eth_async.data import types 6 | from py_eth_async.data.models import Wei, TokenAmount 7 | from py_eth_async.utils import checksum 8 | 9 | 10 | class Wallet: 11 | """ 12 | Class with functions related to wallet. 13 | 14 | Attributes: 15 | client (Client): the Client instance. 16 | 17 | """ 18 | 19 | def __init__(self, client) -> None: 20 | """ 21 | Initialize the class. 22 | 23 | Args: 24 | client (Client): the Client instance. 25 | 26 | """ 27 | self.client = client 28 | 29 | async def balance( 30 | self, token: Optional[types.Contract] = None, address: Optional[types.Address] = None 31 | ) -> Union[Wei, TokenAmount]: 32 | """ 33 | Get a coin or token balance of a specified address. 34 | 35 | Args: 36 | token (Optional[Contract]): the contact address or instance of token. (coin) 37 | address (Optional[Address]): the address. (imported to client address) 38 | 39 | Returns: 40 | Union[Wei, TokenAmount]: the coin or token balance. 41 | 42 | """ 43 | if not address: 44 | address = self.client.account.address 45 | 46 | address = checksum(address) 47 | if not token: 48 | return Wei(await self.client.w3.eth.get_balance(account=address)) 49 | 50 | contract_address, abi = await self.client.contracts.get_contract_attributes(token) 51 | contract = await self.client.contracts.default_token(contract_address=contract_address) 52 | return TokenAmount( 53 | amount=await contract.functions.balanceOf(address).call(), 54 | decimals=await contract.functions.decimals().call(), wei=True 55 | ) 56 | 57 | async def nonce(self, address: Optional[types.Contract] = None) -> int: 58 | """ 59 | Get a nonce of the specified address. 60 | 61 | Args: 62 | address (Optional[Contract]): the address. (imported to client address) 63 | 64 | Returns: 65 | int: the nonce of the address. 66 | 67 | """ 68 | if not address: 69 | address = self.client.account.address 70 | 71 | else: 72 | address, abi = await self.client.contracts.get_contract_attributes(address) 73 | 74 | return await self.client.w3.eth.get_transaction_count(address) 75 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SecorD0/py-eth-async/3b00a55e3b678c0c6e458ee93dd67c6690c7826d/requirements.txt -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from setuptools import setup, find_packages 4 | 5 | with open(os.path.join(os.path.abspath(os.path.dirname(__file__)), 'README.md'), encoding='utf-8') as fh: 6 | long_description = '\n' + fh.read() 7 | 8 | setup( 9 | name='py-eth-async', 10 | version='1.10.4', 11 | license='Apache-2.0', 12 | author='SecorD', 13 | description='', 14 | long_description_content_type='text/markdown', 15 | long_description=long_description, 16 | packages=find_packages(), 17 | install_requires=[ 18 | 'aiohttp-socks==0.8.0', 'aiohttp==3.9.3', 'eth-account==0.8.0', 19 | 'evmdasm @ git+https://github.com/SecorD0/evmdasm@e8389f223746a0d8c94c627397d0dc639633e869', 20 | 'fake-useragent', 'pretty-utils @ git+https://github.com/SecorD0/pretty-utils@main', 'PySocks==1.7.1', 21 | 'python-dotenv==0.21.1', 'web3 @ git+https://github.com/ethereum/web3.py@v6.0.0-beta.9' 22 | ], 23 | keywords=[ 24 | 'eth', 'pyeth', 'py-eth', 'ethpy', 'eth-py', 'web3', 'pyweb3', 'py-web3', 'web3py', 'web3-py', 'async-eth', 25 | 'pyethasync', 'py-eth-async', 'asyncethpy', 'async-eth-py', 'async-web3', 'pyweb3-async', 'py-web3-async', 26 | 'async-web3py', 'async-web3-py' 27 | ], 28 | classifiers=[ 29 | 'Programming Language :: Python :: 3.8' 30 | ] 31 | ) 32 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import os 3 | import random 4 | 5 | from web3.contract import AsyncContract 6 | 7 | from py_eth_async.client import Client 8 | from py_eth_async.data.models import Networks, Wei, Ether, GWei, TokenAmount, Network, TxArgs 9 | from py_eth_async.transactions import Tx 10 | from py_eth_async.utils import get_coin_symbol 11 | 12 | 13 | class Account: 14 | @staticmethod 15 | async def generate_wallets(n: int = 2): 16 | """Generate wallets.""" 17 | print('\n--- generate_wallets ---') 18 | for i in range(n): 19 | client = Client() 20 | print(f'{client.account.address} {client.account.key.hex()}') 21 | 22 | @staticmethod 23 | async def show_coin_balance(): 24 | """Show balance in coin.""" 25 | print('\n--- show_coin_balance ---') 26 | client = Client(private_key=private_key) 27 | address = '0xf5de760f2e916647fd766B4AD9E85ff943cE3A2b' 28 | coin_symbol = await get_coin_symbol(chain_id=client.network.chain_id) 29 | print(f'''Your ({client.account.address}) balance: {(await client.wallet.balance()).Ether} {coin_symbol} 30 | {address} balance: {(await client.wallet.balance(address=address)).Ether} {coin_symbol}''') 31 | 32 | @staticmethod 33 | async def show_token_balance(): 34 | """Show balance in token.""" 35 | print('\n--- show_token_balance ---') 36 | client = Client(private_key=private_key) 37 | address = '0xe4C004883BE7EB6e059005aAf6A34B45722fE3c9' 38 | contract = await client.contracts.get(contract_address='0x326c977e6efc84e512bb9c30f76e30c160ed06fb') 39 | coin_symbol = await contract.functions.symbol().call() 40 | print( 41 | f'''Your ({client.account.address}) balance: {(await client.wallet.balance(token=contract)).Ether} {coin_symbol} 42 | {address} balance: {(await client.wallet.balance(token='0x326c977e6efc84e512bb9c30f76e30c160ed06fb', 43 | address=address)).Ether} {coin_symbol}''') 44 | 45 | 46 | class Contracts: 47 | @staticmethod 48 | async def get_abi(): 49 | """Get a contract ABI.""" 50 | print('\n--- get_abi ---') 51 | network = Networks.Ethereum 52 | client = Client(private_key='', network=network) 53 | print(f'''Python list (parsed from Blockscan): 54 | {await client.contracts.get_abi(contract_address='0xE592427A0AEce92De3Edee1F18E0157C05861564')} 55 | ''') 56 | 57 | network.api.key = '' 58 | client = Client(private_key='', network=network) 59 | print(f'''JSON serialize string (parsed from contract code): 60 | {await client.contracts.get_abi(contract_address='0xE592427A0AEce92De3Edee1F18E0157C05861564', raw_json=True)}''') 61 | 62 | @staticmethod 63 | async def get_contracts(): 64 | """Get contract instances and print info about tokens.""" 65 | print('\n--- get_contracts ---') 66 | tokens = ( 67 | ('BNB', Networks.Ethereum, '0xB8c77482e45F1F44dE1745F52C74426C631bDD52'), 68 | ('ETH', Networks.BSC, '0x2170ed0880ac9a755fd29b2688956bd959f933f8'), 69 | ('OP', Networks.Optimism, '0x4200000000000000000000000000000000000042') 70 | ) 71 | for symbol, network, address in tokens: 72 | client = Client(private_key='', network=network) 73 | contract = await client.contracts.get(contract_address=address) 74 | functions = await client.contracts.get_functions(contract=contract) 75 | print(f'''Name: {await contract.functions.name().call()} 76 | Symbol: {await contract.functions.symbol().call()} 77 | Total Supply: {Wei(await contract.functions.totalSupply().call()).Ether} 78 | Functions ({len(functions)}):''') 79 | for function in functions: 80 | print(f'\t{function}') 81 | 82 | print('---') 83 | 84 | 85 | class NFTs: 86 | @staticmethod 87 | async def get_info(): 88 | """Get info about NFTs.""" 89 | print('\n--- get_info ---') 90 | client = Client(private_key='', network=Networks.Ethereum) 91 | contract_addresses = ( 92 | '0x12b180b635dd9f07a78736fb4e43438fcdb41555', '0x33c6eec1723b12c46732f7ab41398de45641fa42', 93 | '0x684e4ed51d350b4d76a3a07864df572d24e6dc4c', '0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d', 94 | '0xb47e3cd837ddf8e4c57f05d70ab865de6e193bbb', '0xa6c1c8ef0179071c16e066171d660da4ad314687', 95 | '0x34d85c9cdeb23fa97cb08333b511ac86e1c4e258', '0x23581767a106ae21c074b2276d25e5c3e136a68b', 96 | '0x49cf6f5d44e70224e2e23fdcdd2c053f30ada28b', '0x7d8e1909d22372e44c7b67bc7d5cba089eedca3d', 97 | '0x39ee2c7b3cb80254225884ca001f57118c8f21b6', '0x60e4d786628fea6478f785a6d7e704777c86a7c6' 98 | ) 99 | for contract_address in contract_addresses: 100 | nft_info = await client.nfts.get_info(contract_address, random.choice((random.randint(0, 9), None))) 101 | print(nft_info) 102 | for attribute in nft_info.attributes: 103 | print(f'\t{attribute}') 104 | 105 | print('---') 106 | 107 | 108 | class Transactions: 109 | @staticmethod 110 | async def gas_price(): 111 | """Get the current gas price.""" 112 | print('\n--- current_gas_price ---') 113 | client = Client(private_key='', network=Networks.Ethereum) 114 | print(f'''Gas price: {(await client.transactions.gas_price(w3=client.w3)).GWei} GWei 115 | Max priority fee: {(await client.transactions.max_priority_fee(w3=client.w3)).GWei} GWei''') 116 | 117 | @staticmethod 118 | async def estimate_gas(): 119 | """Get the estimate gas limit for a transaction with specified parameters.""" 120 | print('\n--- estimate_gas ---') 121 | client = Client(private_key=private_key) 122 | tx_params = { 123 | 'nonce': 100, 124 | 'gasPrice': (await client.transactions.gas_price(w3=client.w3)).Wei, 125 | 'to': client.account.address, 126 | 'value': 1000000 127 | } 128 | print(f'''{(await client.transactions.estimate_gas(w3=client.w3, tx_params=tx_params)).Wei} Wei''') 129 | 130 | @staticmethod 131 | async def auto_add_params(): 132 | """Add 'chainId', 'from', 'gasPrice' or 'maxFeePerGas' + 'maxPriorityFeePerGas' and 'gas' parameters to transaction parameters if they are missing.""" 133 | print('\n--- auto_add_params ---') 134 | client = Client(private_key=private_key) 135 | tx_params = { 136 | 'nonce': 100, 137 | 'to': client.account.address, 138 | 'value': 1000000 139 | } 140 | print(f'''Source transaction params: 141 | {tx_params} 142 | 143 | Processed transaction params: 144 | {await client.transactions.auto_add_params(tx_params=tx_params)}''') 145 | 146 | @staticmethod 147 | async def parse_params(): 148 | """Parse params of the existing transaction.""" 149 | print('\n--- parse_params ---') 150 | client = Client(private_key='', network=Networks.Ethereum) 151 | tx = Tx(tx_hash='0xd41a90a7c465a33e028cfc43e3b024c06d8f023ae4a42557269bad93e73edb6c') 152 | print(f'''Tx instance attribute: 153 | {tx.params} 154 | 155 | Tx instance function: 156 | {await tx.parse_params(client=client)} 157 | 158 | Tx instance attribute: 159 | {tx.params}''') 160 | 161 | @staticmethod 162 | async def decode_input_data(): 163 | """Decode input data using contract ABI.""" 164 | print('\n--- decode_input_data ---') 165 | client = Client(private_key='', network=Networks.Ethereum) 166 | contract = await client.contracts.get(contract_address='0xd9e1ce17f2641f24ae83637ab66a2cca9c378b9f') 167 | tx_hash = '0xd41a90a7c465a33e028cfc43e3b024c06d8f023ae4a42557269bad93e73edb6c' 168 | tx = Tx(tx_hash=tx_hash) 169 | print(f'''Tx instance function: 170 | {await tx.decode_input_data(client=client, contract=contract)} 171 | 172 | Tx instance attributes: 173 | {tx.function_identifier} {tx.input_data} 174 | 175 | Transactions class function: 176 | {await client.transactions.decode_input_data(client=client, contract=contract, tx_hash=tx_hash)}''') 177 | 178 | @staticmethod 179 | async def history(): 180 | """Show the transaction history.""" 181 | print('\n--- history ---') 182 | client = Client(private_key=private_key, network=Networks.Ethereum) 183 | address = '0x7E695e0f3BD9ba81EddAA800D06267d32E4CE484' 184 | history = await client.transactions.history(address=address, raw=True) 185 | print(f'''\t\t\tRaw 186 | Coin: {len(history.coin)} 187 | Internal: {len(history.internal)} 188 | ERC-20: {len(history.erc20)} 189 | ERC-721: {len(history.erc721)} 190 | 191 | --- 192 | \t\t\tProcessed''') 193 | 194 | history = await client.transactions.history(address=address) 195 | print(f'''Coin: {len(history.coin.all)} 196 | Internal: {len(history.internal.all)} 197 | ERC-20: {len(history.erc20.all)} 198 | ERC-721: {len(history.erc721.all)} 199 | ''') 200 | if history.coin: 201 | print('\nCoin:') 202 | for tx_hash, tx in history.coin.all.items(): 203 | print(f'\t{tx}\n') 204 | 205 | if history.internal: 206 | print('\nInternal:') 207 | for tx_hash, tx in history.internal.all.items(): 208 | print(f'\t{tx}\n') 209 | 210 | if history.erc20: 211 | print('\nERC20:') 212 | for tx_hash, tx in history.erc20.all.items(): 213 | print(f'\t{tx}\n') 214 | 215 | if history.erc721: 216 | print('\nERC721:') 217 | for tx_hash, tx in history.erc721.all.items(): 218 | print(f'\t{tx}\n') 219 | 220 | @staticmethod 221 | async def find_txs(): 222 | """Find all transactions of interaction with the contract, in addition, you can filter transactions by the name of the contract function.""" 223 | print('\n--- find_txs ---') 224 | client = Client(private_key='', network=Networks.Ethereum) 225 | txs = await client.transactions.find_txs( 226 | contract='0xd9e1ce17f2641f24ae83637ab66a2cca9c378b9f', address='0x89D022F95dC5073B7479699044fDf6E3Bc74D3b3' 227 | ) 228 | print(f'All interaction transactions ({len(txs)}):') 229 | for tx_hash, tx in txs.items(): 230 | print(f'\t{tx}\n') 231 | 232 | function_name = 'swapExactETHForTokens' 233 | txs = await client.transactions.find_txs( 234 | contract='0xd9e1ce17f2641f24ae83637ab66a2cca9c378b9f', function_name=function_name, 235 | address='0x89D022F95dC5073B7479699044fDf6E3Bc74D3b3' 236 | ) 237 | print(f'\n\n{function_name} transactions ({len(txs)}):') 238 | for tx_hash, tx in txs.items(): 239 | print(f'\t{tx}\n') 240 | 241 | @staticmethod 242 | async def approved_amount(): 243 | """Get approved amount of token.""" 244 | print('\n--- approved_amount ---') 245 | client = Client(private_key='', network=Networks.Ethereum) 246 | token = await client.contracts.default_token(contract_address='0xf4d2888d29D722226FafA5d9B24F9164c092421E') 247 | spender = '0xe592427a0aece92de3edee1f18e0157c05861564' 248 | owner = '0x6a32729ae1a19f0cafd468d0082a1a9355167692' 249 | approved_amount = await client.transactions.approved_amount(token=token, spender=spender, owner=owner) 250 | print(f'''{owner} allowed {spender} to spend {approved_amount.Ether} {await token.functions.symbol().call()}''') 251 | 252 | @staticmethod 253 | async def send_coin(): 254 | """Send transaction in the coin.""" 255 | print('\n--- send_coin ---') 256 | client = Client(private_key=private_key) 257 | print(f'''Balance before sending: {(await client.wallet.balance()).Ether} 258 | Raw tx: 259 | {await client.transactions.send(token='', recipient=client.account.address, dry_run=True)}''') 260 | tx = await client.transactions.send(token='', recipient=client.account.address) 261 | receipt = await tx.wait_for_receipt(client=client) 262 | print(f'''Tx: 263 | {tx} 264 | Receipt: 265 | {dict(receipt)} 266 | Balance after sending: {(await client.wallet.balance()).Ether} {await get_coin_symbol(chain_id=client.network.chain_id)}''') 267 | 268 | @staticmethod 269 | async def send_token(): 270 | """Send transaction in token.""" 271 | print('\n--- send_token ---') 272 | client = Client(private_key=private_key) 273 | for contract, amount in ( 274 | (await client.contracts.get('0x11fE4B6AE13d2a6055C8D9cF65c55bac32B5d844'), 10.5), 275 | ('0x11fE4B6AE13d2a6055C8D9cF65c55bac32B5d844', 9999999999) 276 | ): 277 | if isinstance(contract, AsyncContract): 278 | contract_obj = contract 279 | 280 | else: 281 | contract_obj = await client.contracts.get(contract) 282 | 283 | print( 284 | f'{await contract_obj.functions.symbol().call()} balance before sending: {(await client.wallet.balance(contract_obj)).Ether}') 285 | tx = await client.transactions.send(token=contract, recipient=client.account.address, amount=amount) 286 | receipt = await tx.wait_for_receipt(client=client) 287 | print(f'''Tx: 288 | {tx} 289 | Receipt: 290 | {dict(receipt)} 291 | {await contract_obj.functions.symbol().call()} balance after sending: {(await client.wallet.balance(contract_obj)).Ether} 292 | --- 293 | ''') 294 | 295 | @staticmethod 296 | async def cancel_coin(): 297 | """Cancel a transaction in coin.""" 298 | print('\n--- cancel_coin ---') 299 | client = Client(private_key=private_key) 300 | tx = await client.transactions.send(token='', recipient=client.account.address, amount=0.05) 301 | print(f'Stuck tx:\n{tx}') 302 | success = await tx.cancel(client=client) 303 | print(f'Success: {success}') 304 | receipt = await tx.wait_for_receipt(client=client) 305 | print(f'''Tx: 306 | {tx} 307 | Receipt: 308 | {dict(receipt)} 309 | Balance after sending: {(await client.wallet.balance()).Ether} {await get_coin_symbol(chain_id=client.network.chain_id)}''') 310 | 311 | @staticmethod 312 | async def cancel_token(): 313 | """Cancel a transaction in token.""" 314 | print('\n--- cancel_token ---') 315 | client = Client(private_key=private_key) 316 | token = '0x11fE4B6AE13d2a6055C8D9cF65c55bac32B5d844' 317 | tx = await client.transactions.send(token=token, recipient=client.account.address) 318 | print(f'Stuck tx:\n{tx}') 319 | success = await tx.cancel(client=client) 320 | print(f'\nSuccess: {success}') 321 | receipt = await tx.wait_for_receipt(client=client) 322 | print(f'''Tx: 323 | {tx} 324 | Receipt: 325 | {dict(receipt)} 326 | Balance after sending: {(await client.wallet.balance(token=token)).Ether}''') 327 | 328 | @staticmethod 329 | async def speed_up_coin(): 330 | """Speed up a transaction in coin.""" 331 | print('\n--- speed_up_coin ---') 332 | client = Client(private_key=private_key) 333 | tx = await client.transactions.send(token='', recipient=client.account.address, amount=0.05) 334 | print(f'Stuck tx:\n{tx}') 335 | success = await tx.speed_up(client=client, gas_price=10) 336 | print(f'Success: {success}') 337 | receipt = await tx.wait_for_receipt(client=client) 338 | print(f'''Tx: 339 | {tx} 340 | Receipt: 341 | {dict(receipt)} 342 | Balance after sending: {(await client.wallet.balance()).Ether} {await get_coin_symbol(chain_id=client.network.chain_id)}''') 343 | 344 | @staticmethod 345 | async def speed_up_token(): 346 | """Speed up a transaction in token.""" 347 | print('\n--- speed_up_token ---') 348 | client = Client(private_key=private_key) 349 | token = '0x11fE4B6AE13d2a6055C8D9cF65c55bac32B5d844' 350 | tx = await client.transactions.send(token=token, recipient=client.account.address) 351 | print(f'Stuck tx:\n{tx}') 352 | success = await tx.speed_up(client=client, gas_price=10) 353 | print(f'\nSuccess: {success}') 354 | receipt = await tx.wait_for_receipt(client=client) 355 | print(f'''Tx: 356 | {tx} 357 | Receipt: 358 | {dict(receipt)} 359 | Balance after sending: {(await client.wallet.balance(token=token)).Ether}''') 360 | 361 | @staticmethod 362 | async def approve(): 363 | """Approve token for swap.""" 364 | print('\n--- approve ---') 365 | client = Client(private_key=private_key) 366 | tx = await client.transactions.approve( 367 | token='0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984', spender='0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45' 368 | ) 369 | receipt = await tx.wait_for_receipt(client=client) 370 | print(f'''Tx: 371 | {tx} 372 | Receipt: 373 | {dict(receipt)}''') 374 | 375 | 376 | class Miscellaneous: 377 | @staticmethod 378 | async def custom_network(): 379 | """Initialize the custom network.""" 380 | print('\n--- custom_network ---') 381 | network = Network(name='moonriver', rpc='https://rpc.api.moonriver.moonbeam.network') 382 | print(network) 383 | 384 | @staticmethod 385 | async def tx_args(): 386 | """Use this to give human names to arguments and then get a tuple or list of arguments to send to transactions.""" 387 | print('\n--- tx_args ---') 388 | client = Client(private_key=private_key) 389 | args = TxArgs( 390 | amount=1000000, 391 | address=client.account.address, 392 | pair=TxArgs( 393 | suource_token='0x7F5c764cBc14f9669B88837ca1490cCa17c31607', 394 | dest_token='0x94b008aA00579c1307B0EF2c499aD98a8ce58e58' 395 | ).list() 396 | ) 397 | print(f'''{args} 398 | {args.list()} 399 | {args.tuple()}''') 400 | 401 | @staticmethod 402 | async def units(): 403 | """Use these units when working with the library.""" 404 | print('\n--- units ---') 405 | print(f'''Usage for coin: 406 | {Ether(0.015200945)} 407 | 408 | Usage for token: 409 | {TokenAmount(amount=500.73467)} 410 | 411 | Usage for gas price: 412 | {GWei(14.64734)}''') 413 | 414 | @staticmethod 415 | async def unit_math_operations(): 416 | """Example of mathematical operations on a Unit instance.""" 417 | print('\n--- unit_math_operations ---') 418 | print(f'''Addition 419 | 100 + 20 + 5 = {(Wei(100) + 20 + Wei(5)).Wei} 420 | 4.1 + 1 + 2.0 = {(4.1 + GWei(1) + 2.0).GWei} 421 | 3020 + 100 + 8 = {(Wei(3020) + Wei(100) + 8).Wei} 422 | 0.5 + 0.1 + 0.03 = {(0.5 + 0.1 + Ether(0.03)).Ether} 423 | 424 | Subtraction 425 | 100 - 20 - 5 = {(Wei(100) - 20 - 5).Wei} 426 | 4.1 - 1 - 2.0 = {(4.1 - GWei(1) - 2.0).GWei} 427 | 3020 - 100 - 8 = {(Wei(3020) - Wei(100) - 8).Wei} 428 | 0.5 - 0.1 - 0.03 = {(0.5 - 0.1 - Ether(0.03)).Ether} 429 | 430 | Multiplication 431 | 100 * 20 * 5 = {(Wei(100) * 20 * 5).Wei} 432 | 4.1 * 1 * 2.0 = {(4.1 * GWei(1) * 2.0).GWei} 433 | 3020 * 100 * 8 = {(Wei(3020) * Wei(100) * 8).Wei} 434 | 0.5 * 0.1 * 0.03 = {(0.5 * 0.1 * Ether(0.03)).Ether} 435 | 436 | Division 437 | 100 / 20 / 5 = {(Wei(100) / 20 / 5).Wei} 438 | 4.1 / 1 / 2.0 = {(4.1 / GWei(1) / 2.0).GWei} 439 | 3020 / 100 / 8 = {(Wei(3020) / Wei(100) / 8).Wei} 440 | 0.5 / 0.1 / 0.03 = {(0.5 / 0.1 / Ether(0.03)).Ether} 441 | 442 | Less than 443 | 100 < 20: {Wei(100) < 20} 444 | 1 < 2.0 < 4.1: {GWei(1) < 2.0 < 4.1} 445 | 100 < 8 < 3020: {Wei(100) < 8 < Wei(3020)} 446 | 0.03 < 0.5: {0.03 < Ether(0.5)} 447 | 448 | Less than or equal to 449 | 100 <= 100: {Wei(100) <= 100} 450 | 4.1 <= 1: {4.1 <= GWei(1)} 451 | 100 <= 3020: {Wei(100) <= Wei(3020)} 452 | 0.5 <= 0.03: {0.5 <= Ether(0.03)} 453 | 454 | Equal 455 | 100 == 20: {Wei(100) == 20} 456 | 4.1 == 1: {4.1 == GWei(1)} 457 | 3020 == 3020: {3020 == Wei(3020)} 458 | 0.5 == 0.5: {0.5 == Ether(0.5)} 459 | 460 | Not equal to 461 | 100 != 20: {Wei(100) != 20} 462 | 4.1 != 4.1: {4.1 != GWei(4.1)} 463 | 3020 != 3020: {3020 != Wei(3020)} 464 | 0.03 != 0.5: {Ether(0.03) != 0.5} 465 | 466 | Great than 467 | 100 > 20: {Wei(100) > 20} 468 | 4.1 > 4.1: {4.1 > GWei(4.1)} 469 | 8 > 3020: {8 > Wei(3020)} 470 | 0.5 > 0.03: {0.5 > Ether(0.03)} 471 | 472 | Great and equal 473 | 100 >= 100: {Wei(100) >= 100} 474 | 2.0 >= 4.1: {2.0 >= GWei(4.1)} 475 | 8 >= 3020: {8 >= Wei(3020)} 476 | 0.5 >= 0.03: {0.5 >= Ether(0.03)}''') 477 | 478 | @staticmethod 479 | async def unit_advanced_math_operations(): 480 | """Example of advanced mathematical operations on a Unit instance.""" 481 | print('\n--- unit_advanced_math_operations ---') 482 | amount = Wei(100) 483 | print(f'Before the transformation: {amount.Wei}') 484 | amount += 20 + Wei(5) 485 | print(f'After: {amount.Wei}\n') 486 | 487 | amount = GWei(4.1) 488 | print(f'Before the transformation: {amount.GWei}') 489 | amount -= 2.0 490 | # amount -= 2.0 - GWei(1) # Works incorrect 491 | print(f'After: {amount.GWei}\n') 492 | 493 | amount = Wei(3020) 494 | print(f'Before the transformation: {amount.Wei}') 495 | amount *= Wei(100) * 8 496 | print(f'After: {amount.Wei}\n') 497 | 498 | amount = Ether(0.5) 499 | print(f'Before the transformation: {amount.Ether}') 500 | amount /= 0.1 501 | # amount /= 0.1 - Ether(0.03) # Works incorrect 502 | print(f'After: {amount.Ether}') 503 | 504 | @staticmethod 505 | async def token_amount_math_operations(): 506 | """Example of mathematical operations on a TokenAmount instance.""" 507 | print('\n--- token_amount_math_operations ---') 508 | print(f'''Addition 509 | 100 + 20.0 + 5 = {(TokenAmount(100, decimals=6) + 20.0 + TokenAmount(5, decimals=6)).Ether} 510 | 511 | Subtraction 512 | 100 - 20.0 - 5 = {(TokenAmount(100, decimals=6) - 20.0 - TokenAmount(5, decimals=6)).Ether} 513 | 514 | Multiplication 515 | 100 * 20.0 * 5 = {(TokenAmount(100, decimals=6) * 20.0 * TokenAmount(5, decimals=6)).Ether} 516 | 517 | Division 518 | 100 / 20.0 / 5 = {(TokenAmount(100, decimals=6) / 20.0 / TokenAmount(5, decimals=6)).Ether} 519 | 520 | Less than 521 | 100 < 20.0: {TokenAmount(100, decimals=6) < 20.0} 522 | 1 < 2.0 < 4.1: {TokenAmount(1, decimals=6) < 2.0 < TokenAmount(4.1, decimals=6) < 20.0} 523 | 524 | Less than or equal to 525 | 100 <= 100.0: {TokenAmount(100, decimals=6) <= 100.0} 526 | 4.1 <= 1: {4.1 <= TokenAmount(1, decimals=6)} 527 | 528 | Equal 529 | 20.0 == 100: {20.0 == TokenAmount(100, decimals=6)} 530 | 4.1 == 4.1: {TokenAmount(4.1, decimals=6) == 4.1} 531 | 532 | Not equal to 533 | 100 != 20: {TokenAmount(100, decimals=6) != 20.0} 534 | 4.1 != 4.1: {4.1 != TokenAmount(4.1, decimals=6)} 535 | 536 | Great than 537 | 100 > 20: {100.0 > TokenAmount(20, decimals=6)} 538 | 4.1 > 4.1: {TokenAmount(4.1, decimals=6) > 4.1} 539 | 540 | Great and equal 541 | 100 >= 100: {TokenAmount(100, decimals=6) >= 100.0} 542 | 2.0 >= 4.1: {2.0 >= TokenAmount(4.1, decimals=6)}''') 543 | 544 | @staticmethod 545 | async def token_amount_advanced_math_operations(): 546 | """Example of advanced mathematical operations on a TokenAmount instance.""" 547 | print('\n--- token_amount_advanced_math_operations ---') 548 | amount = TokenAmount(100, decimals=6) 549 | print(f'Before the transformation: {amount.Ether}') 550 | amount += 20.0 + TokenAmount(5, decimals=6) 551 | print(f'After: {amount.Ether}\n') 552 | 553 | amount = TokenAmount(100, decimals=6) 554 | print(f'Before the transformation: {amount.Ether}') 555 | amount -= 20.0 556 | # amount -= 20.0 - TokenAmount(5, decimals=6) # Works incorrect 557 | print(f'After: {amount.Ether}\n') 558 | 559 | amount = TokenAmount(100, decimals=6) 560 | print(f'Before the transformation: {amount.Ether}') 561 | amount *= 20.0 * TokenAmount(5, decimals=6) 562 | print(f'After: {amount.Ether}\n') 563 | 564 | amount = TokenAmount(100, decimals=6) 565 | print(f'Before the transformation: {amount.Ether}') 566 | amount /= 20.0 567 | # amount /= 20.0 / TokenAmount(5, decimals=6) # Works incorrect 568 | print(f'After: {amount.Ether}') 569 | 570 | @staticmethod 571 | async def change_decimals(): 572 | """Leave the Ether amount and change the Wei based on the new decimals.""" 573 | print('\n--- change_decimals ---') 574 | token = TokenAmount(amount=500.73467) 575 | print(f'''Instance from Ether amount: {token} 576 | Change decimals and get Wei: {token.change_decimals(new_decimals=6)} 577 | Instance with changed decimals: {token} 578 | Instance from Wei amount: {TokenAmount(amount=500734670000000008192, wei=True)}''') 579 | 580 | 581 | async def main() -> None: 582 | print('--------- Account ---------') 583 | account = Account() 584 | await account.generate_wallets() 585 | await account.show_coin_balance() 586 | await account.show_token_balance() 587 | 588 | print('\n--------- Contracts ---------') 589 | contracts = Contracts() 590 | await contracts.get_abi() 591 | await contracts.get_contracts() 592 | 593 | print('\n--------- NFTs ---------') 594 | nfts = NFTs() 595 | await nfts.get_info() 596 | 597 | print('\n--------- Transactions ---------') 598 | transactions = Transactions() 599 | await transactions.gas_price() 600 | await transactions.estimate_gas() 601 | await transactions.auto_add_params() 602 | await transactions.parse_params() 603 | await transactions.decode_input_data() 604 | await transactions.find_txs() 605 | await transactions.approved_amount() 606 | await transactions.history() 607 | await transactions.send_coin() 608 | await transactions.send_token() 609 | await transactions.cancel_coin() 610 | await transactions.cancel_token() 611 | await transactions.speed_up_coin() 612 | await transactions.speed_up_token() 613 | await transactions.approve() 614 | 615 | print('\n--------- Miscellaneous ---------') 616 | miscellaneous = Miscellaneous() 617 | await miscellaneous.custom_network() 618 | await miscellaneous.tx_args() 619 | await miscellaneous.units() 620 | await miscellaneous.unit_math_operations() 621 | await miscellaneous.unit_advanced_math_operations() 622 | await miscellaneous.token_amount_math_operations() 623 | await miscellaneous.token_amount_advanced_math_operations() 624 | await miscellaneous.change_decimals() 625 | 626 | 627 | if __name__ == '__main__': 628 | private_key = str(os.getenv('PRIVATE_KEY')) 629 | if private_key: 630 | loop = asyncio.get_event_loop() 631 | loop.run_until_complete(main()) 632 | 633 | else: 634 | print("Specify the private key in the 'PRIVATE_KEY' variable in the .env file!") 635 | --------------------------------------------------------------------------------