├── .github └── workflows │ ├── publish.yaml │ └── test.yaml ├── .gitignore ├── LICENSE ├── README.md ├── ankr ├── __init__.py ├── advanced_apis.py ├── exceptions.py ├── providers.py ├── types.py └── web3.py ├── pyproject.toml ├── setup.cfg └── tests ├── __init__.py ├── conftest.py ├── test_client.py ├── test_providers.py └── test_web3.py /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: publish 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | publish: 9 | runs-on: "ubuntu-latest" 10 | steps: 11 | - name: Check out repository 12 | uses: actions/checkout@v2 13 | 14 | - name: Set up python 15 | id: setup-python 16 | uses: actions/setup-python@v2 17 | with: 18 | python-version: "3.11" 19 | 20 | - name: Install Poetry 21 | uses: snok/install-poetry@v1 22 | 23 | - name: Build library 24 | run: poetry build 25 | 26 | - name: Authenticate in PyPi 27 | run: poetry config http-basic.pypi __token__ ${{ secrets.PYPI_TOKEN }} 28 | 29 | - name: Publish library 30 | run: poetry publish 31 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | linting: 8 | runs-on: ubuntu-latest 9 | steps: 10 | #---------------------------------------------- 11 | # check-out repo and set-up python 12 | #---------------------------------------------- 13 | - uses: actions/checkout@v2 14 | - uses: actions/setup-python@v2 15 | #---------------------------------------------- 16 | # load pip cache if cache exists 17 | #---------------------------------------------- 18 | - uses: actions/cache@v2 19 | with: 20 | path: ~/.cache/pip 21 | key: ${{ runner.os }}-pip 22 | restore-keys: ${{ runner.os }}-pip 23 | #---------------------------------------------- 24 | # install and run linters 25 | #---------------------------------------------- 26 | - run: python -m pip install black==23.11.0 flake8 isort 27 | - run: | 28 | flake8 . 29 | black . --check 30 | isort . 31 | test: 32 | needs: linting 33 | env: 34 | ANKR_API_KEY: ${{ secrets.ANKR_API_KEY }} 35 | strategy: 36 | fail-fast: true 37 | matrix: 38 | python-version: [ "3.8", "3.9", "3.10", "3.11" ] 39 | runs-on: "ubuntu-latest" 40 | steps: 41 | #---------------------------------------------- 42 | # check-out repo and set-up python 43 | #---------------------------------------------- 44 | - name: Check out repository 45 | uses: actions/checkout@v2 46 | - name: Set up python ${{ matrix.python-version }} 47 | id: setup-python 48 | uses: actions/setup-python@v2 49 | with: 50 | python-version: ${{ matrix.python-version }} 51 | #---------------------------------------------- 52 | # ----- install & configure poetry ----- 53 | #---------------------------------------------- 54 | - name: Install Poetry 55 | uses: snok/install-poetry@v1 56 | with: 57 | virtualenvs-create: true 58 | virtualenvs-in-project: true 59 | #---------------------------------------------- 60 | # load cached venv if cache exists 61 | #---------------------------------------------- 62 | - name: Load cached venv 63 | id: cached-poetry-dependencies 64 | uses: actions/cache@v2 65 | with: 66 | path: .venv 67 | key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }} 68 | #---------------------------------------------- 69 | # install dependencies if cache does not exist 70 | #---------------------------------------------- 71 | - name: Install dependencies 72 | if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true' 73 | run: poetry install --no-interaction --no-root 74 | #---------------------------------------------- 75 | # install your root project, if required 76 | #---------------------------------------------- 77 | - name: Install library 78 | run: poetry install --no-interaction 79 | 80 | - name: Run tests 81 | run: | 82 | source .venv/bin/activate 83 | pytest -------------------------------------------------------------------------------- /.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 | 131 | # Pycharm 132 | .idea/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Ankr 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ⚓️ Ankr Python SDK 2 | 3 | Compact Python library for interacting with Ankr's [Advanced APIs](https://www.ankr.com/advanced-api/). 4 | 5 | ## Get started in 2 minutes 6 | 7 | #### 1. Install the package from PyPi 8 | 9 | ```bash 10 | pip install ankr-sdk 11 | ``` 12 | 13 | #### 2. Initialize the SDK 14 | 15 | _Note: to use Advanced API for free starting from May 29th, 2023 you have to register on the platform._ 16 | 17 | Get your individual endpoint here https://www.ankr.com/rpc/advanced-api and provide it to the `AnkrWeb3` class. 18 | 19 | ```python3 20 | from ankr import AnkrWeb3 21 | 22 | ankr_w3 = AnkrWeb3("YOUR-TOKEN") 23 | ``` 24 | 25 | #### 3. Use the sdk and call one of the supported methods 26 | 27 | #### Node's API 28 | ```python3 29 | from ankr import AnkrWeb3 30 | ankr_w3 = AnkrWeb3("YOUR-TOKEN") 31 | 32 | eth_block = ankr_w3.eth.get_block("latest") 33 | bsc_block = ankr_w3.bsc.get_block("latest") 34 | polygon_block = ankr_w3.polygon.get_block("latest") 35 | ``` 36 | 37 | #### Ankr NFT API 38 | 39 | ```python3 40 | from ankr import AnkrWeb3 41 | from ankr.types import Blockchain, GetNFTsByOwnerRequest 42 | 43 | ankr_w3 = AnkrWeb3("YOUR-TOKEN") 44 | 45 | nfts = ankr_w3.nft.get_nfts( 46 | request=GetNFTsByOwnerRequest( 47 | blockchain=Blockchain.Eth, 48 | walletAddress="0x0E11A192d574b342C51be9e306694C41547185DD" 49 | ) 50 | ) 51 | ``` 52 | 53 | #### Ankr Token API 54 | ```python3 55 | from ankr import AnkrWeb3 56 | from ankr.types import GetAccountBalanceRequest 57 | 58 | ankr_w3 = AnkrWeb3("YOUR-TOKEN") 59 | 60 | assets = ankr_w3.token.get_account_balance( 61 | request=GetAccountBalanceRequest( 62 | walletAddress="0x77A859A53D4de24bBC0CC80dD93Fbe391Df45527" 63 | ) 64 | ) 65 | ``` 66 | 67 | #### Ankr Query API 68 | ```python3 69 | from ankr import AnkrWeb3 70 | from ankr.types import Blockchain, GetLogsRequest 71 | 72 | ankr_w3 = AnkrWeb3("YOUR-TOKEN") 73 | 74 | logs = ankr_w3.query.get_logs( 75 | request=GetLogsRequest( 76 | blockchain=[Blockchain.Eth], 77 | fromBlock=1181739, 78 | toBlock=1181739, 79 | address=["0x3589d05a1ec4af9f65b0e5554e645707775ee43c"], 80 | topics=[ 81 | [], 82 | ["0x000000000000000000000000feb92d30bf01ff9a1901666c5573532bfa07eeec"], 83 | ], 84 | decodeLogs=True, 85 | ), 86 | limit=10 87 | ) 88 | ``` 89 | 90 | ## Ankr Advanced APIs supported chains 91 | 92 | `ankr-sdk` supports the following chains at this time: 93 | 94 | Mainnet 95 | 96 | - Ethereum: `"eth"` 97 | - BNB Smart Chain: `"bsc"` 98 | - Polygon: `"polygon"` 99 | - Fantom: `"fantom"` 100 | - Arbitrum: `"arbitrum"` 101 | - Avalanche: `"avalanche"` 102 | - Syscoin NEVM: `"syscoin"` 103 | - Optimism: `"optimism"` 104 | - Polygon zkEVM: `"polygon_zkevm"` 105 | - Rollux: `"rollux"` 106 | - Base: `"base"` 107 | - Flare: `"flare"` 108 | - Gnosis Chain: `"gnosis"` 109 | - Scroll: `"scroll"` 110 | - Linea: `"linea"` 111 | - Xai: `"xai"` 112 | - Xlayer: `"xlayer"` 113 | - Telos: `"telos"` 114 | 115 | Testnet 116 | 117 | - Ethereum Sepolia: `"eth_sepolia"` 118 | - Ethereum Holesky: `"eth_holesky"` 119 | - Avalanche Fuji: `"avalanche_fuji"` 120 | - Polygon Amoy: `"polygon_amoy"` 121 | - Optimism Testnet: `"optimism_testnet"` 122 | - Base Sepolia: `"base_sepolia"` 123 | 124 | Appchain 125 | 126 | - META Apes: `"bas_metaapes"` 127 | 128 | Appchain Testnet 129 | 130 | - META Apes Testnet: `"bas_metaapes_testnet"` 131 | - Neura Devnet `"neura_devnet"` 132 | - Neura Testnet `"neura_testnet_v1"` 133 | - Incentiv Devnet `"incentiv_devnet"` 134 | 135 | When passing blockchain, you can use one available from `types.py` (preferred) or just a string value. 136 | 137 | ## Available methods 138 | 139 | `ankr-sdk` supports the following methods: 140 | 141 | Early Access 142 | 143 | - [`get_token_price_history`](#gettokenpricehistory--gettokenpricehistoryraw) 144 | - [`get_account_balance_historical`](#getaccountbalancehistorical--getaccountbalancehistoricalraw) 145 | - [`get_internal_transactions_by_block_number`](#getinternaltransactionsbyblocknumber--getinternaltransactionsbyblocknumberraw) 146 | - [`get_internal_transactions_by_parent_hash`](#getinternaltransactionsbyparenthash--getinternaltransactionsbyparenthashraw) 147 | 148 | Token API 149 | 150 | - [`explain_token_price`](#explaintokenprice--explaintokenpriceraw) 151 | - [`get_account_balance`](#getaccountbalance--getaccountbalanceraw) 152 | - [`get_currencies`](#getcurrencies--getcurrenciesraw) 153 | - [`get_token_holders`](#gettokenholders--gettokenholdersraw) 154 | - [`get_token_holders_count_history`](#gettokenholderscounthistory--gettokenholderscounthistoryraw) 155 | - [`get_token_holders_count`](#gettokenholderscount--gettokenholderscountraw) 156 | - [`get_token_price`](#gettokenprice--gettokenpriceraw) 157 | - [`get_token_transfers`](#gettokentransfers--gettokentransfersraw) 158 | 159 | NFT API 160 | 161 | - [`get_nfts`](#getnfts--getnftsraw) 162 | - [`get_nft_metadata`](#getnftmetadata--getnftmetadataraw) 163 | - [`get_nft_holders`](#getnftholders--getnftholdersraw) 164 | - [`get_nft_transfers`](#getnfttransfers--getnfttransfersraw) 165 | 166 | Query API 167 | 168 | - [`get_logs`](#getlogs--getlogsraw) 169 | - [`get_blocks`](#getblocks--getblocksraw) 170 | - [`get_transaction`](#gettransaction--gettransactionraw) 171 | - [`get_transactions_by_address`](#gettransactionsbyaddress--gettransactionsbyaddressraw) 172 | - [`get_blockchain_stats`](#getblockchainstats--getblockchainstatsraw) 173 | - [`get_interactions`](#getinteractions--getinteractionsraw) 174 | 175 | 176 | Note: some methods are available in *_raw format, allowing to get full reply with syncStatus and control pagination by hands. 177 | 178 | ### Pagination 179 | 180 | - methods with *_raw ending support customized pagination, where you are controlling it, using `pageSize` and `pageToken` 181 | - other methods support automatic pagination, DON'T use `pageSize` and `pageToken` fields to these methods 182 | 183 | --- 184 | 185 | ## Examples 186 | 187 | ### Early Access API 188 | 189 | #### `get_token_price_history` / `get_token_price_history_raw` 190 | 191 | Get a list of history of the price for given contract to given timestamp. 192 | 193 | ```python3 194 | from ankr import AnkrAdvancedAPI 195 | from ankr.types import Blockchain, GetTokenPriceHistoryRequest 196 | 197 | advancedAPI = AnkrAdvancedAPI("YOUR-TOKEN") 198 | 199 | result = advancedAPI.get_token_price_history( 200 | request=GetTokenPriceHistoryRequest( 201 | blockchain=Blockchain.Eth, 202 | contractAddress='0x50327c6c5a14dcade707abad2e27eb517df87ab5', 203 | toTimestamp=1696970653, 204 | interval=100, 205 | limit=10 206 | ) 207 | ) 208 | print(result) 209 | ``` 210 | 211 | #### `get_account_balance_historical` / `get_account_balance_historical_raw` 212 | 213 | Get the coin and token balances of the wallet at specified block. 214 | 215 | ```python3 216 | from ankr import AnkrAdvancedAPI 217 | from ankr.types import Blockchain, GetAccountBalanceHistoricalRequest 218 | 219 | advancedAPI = AnkrAdvancedAPI("YOUR-TOKEN") 220 | 221 | result = advancedAPI.get_account_balance_historical( 222 | request=GetAccountBalanceHistoricalRequest( 223 | blockchain=Blockchain.Eth, 224 | walletAddress='vitalik.eth', 225 | onlyWhitelisted=False, 226 | blockHeight=17967813, 227 | ) 228 | ) 229 | print(result) 230 | ``` 231 | 232 | #### `get_internal_transactions_by_block_number` / `get_internal_transactions_by_block_number_raw` 233 | 234 | Get a list of internal transactions in the block. 235 | 236 | ```python3 237 | from ankr import AnkrAdvancedAPI 238 | from ankr.types import Blockchain, GetInternalTransactionsByBlockNumberRequest 239 | 240 | advancedAPI = AnkrAdvancedAPI("YOUR-TOKEN") 241 | 242 | result = advancedAPI.get_internal_transactions_by_block_number( 243 | request=GetInternalTransactionsByBlockNumberRequest( 244 | blockchain=Blockchain.Eth, 245 | blockNumber=10000000, 246 | onlyWithValue=True, 247 | ) 248 | ) 249 | for transaction in result: 250 | print(transaction) 251 | ``` 252 | 253 | #### `get_internal_transactions_by_parent_hash` / `get_internal_transactions_by_parent_hash_raw` 254 | 255 | Get a list of internal transactions in the transaction. 256 | 257 | ```python3 258 | from ankr import AnkrAdvancedAPI 259 | from ankr.types import Blockchain, GetInternalTransactionsByParentHashRequest 260 | 261 | advancedAPI = AnkrAdvancedAPI("YOUR-TOKEN") 262 | 263 | result = advancedAPI.get_internal_transactions_by_parent_hash( 264 | request=GetInternalTransactionsByParentHashRequest( 265 | blockchain=Blockchain.Eth, 266 | parentTransactionHash='0xa50f8744e65cb76f66f9d54499d5401866a75d93db2e784952f55205afc3acc5', 267 | onlyWithValue=True, 268 | ) 269 | ) 270 | for transaction in result: 271 | print(transaction) 272 | ``` 273 | 274 | ### Token API 275 | 276 | #### `explain_token_price` / `explain_token_price_raw` 277 | 278 | Get a list of tokens and pool how price for calculated. 279 | 280 | ```python3 281 | from ankr import AnkrAdvancedAPI 282 | from ankr.types import Blockchain, ExplainTokenPriceRequest 283 | 284 | advancedAPI = AnkrAdvancedAPI("YOUR-TOKEN") 285 | 286 | pairs, estimates = advancedAPI.explain_token_price( 287 | request=ExplainTokenPriceRequest( 288 | blockchain=Blockchain.Eth, 289 | tokenAddress='0x8290333cef9e6d528dd5618fb97a76f268f3edd4', 290 | blockHeight=17463534, 291 | ) 292 | ) 293 | 294 | print(pairs) 295 | print(estimates) 296 | ``` 297 | 298 | #### `get_account_balance` / `get_account_balance_raw` 299 | 300 | Get the coin and token balances of a wallet. 301 | 302 | ```python3 303 | from ankr import AnkrAdvancedAPI 304 | from ankr.types import GetAccountBalanceRequest 305 | 306 | advancedAPI = AnkrAdvancedAPI("YOUR-TOKEN") 307 | 308 | result = advancedAPI.get_account_balance( 309 | request=GetAccountBalanceRequest( 310 | walletAddress="0x77A859A53D4de24bBC0CC80dD93Fbe391Df45527" 311 | ) 312 | ) 313 | 314 | for balance in result: 315 | print(balance) 316 | ``` 317 | 318 | #### `get_currencies` / `get_currencies_raw` 319 | 320 | Get a list of supported currencies for a given blockchain. 321 | 322 | ```python3 323 | from ankr import AnkrAdvancedAPI 324 | from ankr.types import Blockchain, GetCurrenciesRequest 325 | 326 | advancedAPI = AnkrAdvancedAPI("YOUR-TOKEN") 327 | 328 | result = advancedAPI.get_currencies( 329 | request=GetCurrenciesRequest( 330 | blockchain=Blockchain.Fantom, 331 | ) 332 | ) 333 | 334 | for currency in result: 335 | print(currency) 336 | ``` 337 | 338 | #### `get_token_holders` / `get_token_holders_raw` 339 | 340 | Get the list of token holders for a given contract address. 341 | 342 | ```python3 343 | from ankr import AnkrAdvancedAPI 344 | from ankr.types import Blockchain, GetTokenHoldersRequest 345 | 346 | advancedAPI = AnkrAdvancedAPI("YOUR-TOKEN") 347 | 348 | result = advancedAPI.get_token_holders( 349 | request=GetTokenHoldersRequest( 350 | blockchain=Blockchain.Eth, 351 | contractAddress='0xdac17f958d2ee523a2206206994597c13d831ec7', 352 | ) 353 | ) 354 | 355 | for balance in result: 356 | print(balance) 357 | ``` 358 | 359 | #### `get_token_holders_count_history` / `get_token_holders_count_history_raw` 360 | 361 | Get historical data about the number of token holders for a given contract address. 362 | 363 | ```python3 364 | from ankr import AnkrAdvancedAPI 365 | from ankr.types import Blockchain, GetTokenHoldersCountRequest 366 | 367 | advancedAPI = AnkrAdvancedAPI("YOUR-TOKEN") 368 | 369 | result = advancedAPI.get_token_holders_count_history( 370 | request=GetTokenHoldersCountRequest( 371 | blockchain=Blockchain.Eth, 372 | contractAddress='0xdAC17F958D2ee523a2206206994597C13D831ec7', 373 | ) 374 | ) 375 | 376 | for balance in result: 377 | print(balance) 378 | ``` 379 | 380 | #### `get_token_holders_count` / `get_token_holders_count_raw` 381 | 382 | Get current data about the number of token holders for a given contract address. 383 | 384 | ```python3 385 | from ankr import AnkrAdvancedAPI 386 | from ankr.types import Blockchain, GetTokenHoldersCountRequest 387 | 388 | advancedAPI = AnkrAdvancedAPI("YOUR-TOKEN") 389 | 390 | result = advancedAPI.get_token_holders_count_history_raw( 391 | request=GetTokenHoldersCountRequest( 392 | blockchain=Blockchain.Eth, 393 | contractAddress='0xdAC17F958D2ee523a2206206994597C13D831ec7', 394 | ) 395 | ) 396 | 397 | print(result) 398 | ``` 399 | 400 | #### `get_token_price` / `get_token_price_raw` 401 | 402 | Get token price by contract. 403 | 404 | ```python3 405 | from ankr import AnkrAdvancedAPI 406 | from ankr.types import Blockchain, GetTokenPriceRequest 407 | 408 | advancedAPI = AnkrAdvancedAPI("YOUR-TOKEN") 409 | 410 | result = advancedAPI.get_token_price( 411 | request=GetTokenPriceRequest( 412 | blockchain=Blockchain.Eth, 413 | contractAddress='', 414 | ) 415 | ) 416 | 417 | print(result) 418 | ``` 419 | 420 | #### `get_token_transfers` / `get_token_transfers_raw` 421 | 422 | Get token transfers of specified address. 423 | 424 | ```python3 425 | from ankr import AnkrAdvancedAPI 426 | from ankr.types import Blockchain, GetTransfersRequest 427 | 428 | advancedAPI = AnkrAdvancedAPI("YOUR-TOKEN") 429 | 430 | result = advancedAPI.get_token_transfers( 431 | request=GetTransfersRequest( 432 | blockchain=Blockchain.Eth, 433 | address=['0xf16e9b0d03470827a95cdfd0cb8a8a3b46969b91'], 434 | fromTimestamp=1674441035, 435 | toTimestamp=1674441035, 436 | descOrder=True, 437 | ) 438 | ) 439 | 440 | for transfer in result: 441 | print(transfer) 442 | ``` 443 | 444 | ### NFT API 445 | 446 | #### `get_nfts` / `get_nfts_raw` 447 | 448 | Get data about all the NFTs (collectibles) owned by a wallet. 449 | 450 | ```python3 451 | from ankr import AnkrAdvancedAPI 452 | from ankr.types import Blockchain, GetNFTsByOwnerRequest 453 | 454 | advancedAPI = AnkrAdvancedAPI("YOUR-TOKEN") 455 | 456 | result = advancedAPI.get_nfts( 457 | request=GetNFTsByOwnerRequest( 458 | blockchain=Blockchain.Eth, 459 | walletAddress='0x0E11A192d574b342C51be9e306694C41547185DD', 460 | ) 461 | ) 462 | 463 | for nft in result: 464 | print(nft) 465 | ``` 466 | 467 | #### `get_nft_metadata` / `get_nft_metadata_raw` 468 | 469 | Get NFT's contract metadata. 470 | 471 | ```python3 472 | from ankr import AnkrAdvancedAPI 473 | from ankr.types import Blockchain, GetNFTMetadataRequest 474 | 475 | advancedAPI = AnkrAdvancedAPI("YOUR-TOKEN") 476 | 477 | reply = advancedAPI.get_nft_metadata( 478 | request=GetNFTMetadataRequest( 479 | blockchain=Blockchain.Eth, 480 | contractAddress='0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d', 481 | tokenId='1500', 482 | forceFetch=False, 483 | ) 484 | ) 485 | 486 | print(reply.metadata) 487 | print(reply.attributes) 488 | ``` 489 | 490 | #### `get_nft_holders` / `get_nft_holders_raw` 491 | 492 | Get NFT's holders. 493 | 494 | ```python3 495 | from ankr import AnkrAdvancedAPI 496 | from ankr.types import Blockchain, GetNFTHoldersRequest 497 | 498 | advancedAPI = AnkrAdvancedAPI("YOUR-TOKEN") 499 | 500 | result = advancedAPI.get_nft_holders( 501 | request=GetNFTHoldersRequest( 502 | blockchain=Blockchain.Arbitrum, 503 | contractAddress='0xc36442b4a4522e871399cd717abdd847ab11fe88', 504 | ), 505 | limit=1000 506 | ) 507 | 508 | for holder in result: 509 | print(holder) 510 | ``` 511 | 512 | #### `get_nft_transfers` / `get_nft_transfers_raw` 513 | 514 | Get NFT Transfers of specified address. 515 | 516 | ```python3 517 | from ankr import AnkrAdvancedAPI 518 | from ankr.types import Blockchain, GetTransfersRequest 519 | 520 | advancedAPI = AnkrAdvancedAPI("YOUR-TOKEN") 521 | 522 | result = advancedAPI.get_nft_transfers( 523 | request=GetTransfersRequest( 524 | blockchain=[Blockchain.Eth, Blockchain.Bsc], 525 | address=['0xd8da6bf26964af9d7eed9e03e53415d37aa96045'], 526 | fromTimestamp=1672553107, 527 | toTimestamp=1672683207, 528 | ) 529 | ) 530 | 531 | for transfer in result: 532 | print(transfer) 533 | ``` 534 | 535 | ### Query API 536 | 537 | #### `get_logs` / `get_logs_raw` 538 | 539 | Get logs matching the filter. 540 | 541 | ```python3 542 | from ankr import AnkrAdvancedAPI 543 | from ankr.types import Blockchain, GetLogsRequest 544 | 545 | advancedAPI = AnkrAdvancedAPI("YOUR-TOKEN") 546 | 547 | result = advancedAPI.get_logs( 548 | request=GetLogsRequest( 549 | blockchain=[Blockchain.Eth], 550 | fromBlock=1181739, 551 | toBlock=1181739, 552 | address=["0x3589d05a1ec4af9f65b0e5554e645707775ee43c"], 553 | topics=[ 554 | [], 555 | ["0x000000000000000000000000feb92d30bf01ff9a1901666c5573532bfa07eeec"], 556 | ], 557 | decodeLogs=True, 558 | ), 559 | limit=10 560 | ) 561 | 562 | for log in result: 563 | print(log) 564 | ``` 565 | 566 | #### `get_blocks` / `get_blocks_raw` 567 | 568 | Query data about blocks within a specified range. 569 | 570 | ```python3 571 | from ankr import AnkrAdvancedAPI 572 | from ankr.types import Blockchain, GetBlocksRequest 573 | 574 | advancedAPI = AnkrAdvancedAPI("YOUR-TOKEN") 575 | 576 | result = advancedAPI.get_blocks( 577 | request=GetBlocksRequest( 578 | blockchain=Blockchain.Eth, 579 | fromBlock=14500001, 580 | toBlock=14500004, 581 | descOrder=True, 582 | includeLogs=True, 583 | includeTxs=True, 584 | decodeLogs=True, 585 | ) 586 | ) 587 | 588 | for block in result: 589 | print(block) 590 | ``` 591 | 592 | #### `get_transaction` / `get_transaction_raw` 593 | 594 | Query data about transaction by the transaction hash. 595 | 596 | ```python3 597 | from ankr import AnkrAdvancedAPI 598 | from ankr.types import GetTransactionsByHashRequest 599 | 600 | advancedAPI = AnkrAdvancedAPI("YOUR-TOKEN") 601 | 602 | result = advancedAPI.get_transaction( 603 | request=GetTransactionsByHashRequest( 604 | transactionHash='0x82c13aaac6f0b6471afb94a3a64ae89d45baa3608ad397621dbb0d847f51196f', 605 | decodeTxData=True 606 | ) 607 | ) 608 | 609 | print(result) 610 | ``` 611 | 612 | #### `get_transactions_by_address` / `get_transactions_by_address_raw` 613 | 614 | Query data about transactions of specified address. 615 | 616 | ```python3 617 | from ankr import AnkrAdvancedAPI 618 | from ankr.types import Blockchain, GetTransactionsByAddressRequest 619 | 620 | advancedAPI = AnkrAdvancedAPI("YOUR-TOKEN") 621 | 622 | result = advancedAPI.get_transactions_by_address( 623 | request=GetTransactionsByAddressRequest( 624 | blockchain=Blockchain.Bsc, 625 | fromBlock=23593283, 626 | toBlock=23593283, 627 | address=[ 628 | "0x97242e3315c7ece760dc7f83a7dd8af6659f8c4c" 629 | ], 630 | descOrder=True, 631 | ) 632 | ) 633 | 634 | for transaction in result: 635 | print(transaction) 636 | ``` 637 | 638 | #### `get_blockchain_stats` / `get_blockchain_stats_raw` 639 | 640 | Returns blockchain stats (num of txs, etc.). 641 | 642 | ```python3 643 | from ankr import AnkrAdvancedAPI 644 | from ankr.types import Blockchain, GetBlockchainStatsRequest 645 | 646 | advancedAPI = AnkrAdvancedAPI("YOUR-TOKEN") 647 | 648 | result = advancedAPI.get_blockchain_stats( 649 | request=GetBlockchainStatsRequest( 650 | blockchain=Blockchain.Bsc, 651 | ) 652 | ) 653 | 654 | for stat in result: 655 | print(stat) 656 | ``` 657 | 658 | #### `get_interactions` / `get_interactions_raw` 659 | 660 | Returns on which chain address was interacting. 661 | 662 | ```python3 663 | from ankr import AnkrAdvancedAPI 664 | from ankr.types import GetInteractionsRequest 665 | 666 | advancedAPI = AnkrAdvancedAPI("YOUR-TOKEN") 667 | 668 | result = advancedAPI.get_interactions( 669 | request=GetInteractionsRequest( 670 | address='0xF977814e90dA44bFA03b6295A0616a897441aceC', 671 | ) 672 | ) 673 | 674 | for blockchain in result: 675 | print(blockchain) 676 | ``` 677 | 678 | 679 | 680 | ### About API keys 681 | 682 | Ankr is offering _free_ access to Advanced API, however you have to register on Ankr platform to access it. 683 | 684 | Get your individual API Key here https://www.ankr.com/rpc/advanced-api. 685 | -------------------------------------------------------------------------------- /ankr/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from ankr.advanced_apis import AnkrAdvancedAPI 4 | from ankr.web3 import AnkrWeb3 5 | -------------------------------------------------------------------------------- /ankr/advanced_apis.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Iterable, List, Optional 4 | 5 | from ankr import types 6 | from ankr.exceptions import APIError 7 | from ankr.providers import MultichainHTTPProvider 8 | 9 | 10 | class AnkrMultichainAPI: 11 | def __init__( 12 | self, 13 | api_key: str, 14 | endpoint_uri: Optional[str] = None, 15 | ) -> None: 16 | self.provider = MultichainHTTPProvider(api_key, endpoint_uri) 17 | 18 | 19 | class AnkrEarlyAccessAPI(AnkrMultichainAPI): 20 | def get_token_price_history( 21 | self, 22 | request: types.GetTokenPriceHistoryRequest, 23 | ) -> [types.Quote]: 24 | reply = self.provider.call_method( 25 | rpc="ankr_getTokenPriceHistory", 26 | request=request, 27 | reply=types.GetTokenPriceHistoryReply, 28 | ) 29 | 30 | if reply.quotes: 31 | return reply.quotes 32 | else: 33 | return [] 34 | 35 | def get_token_price_history_raw( 36 | self, 37 | request: types.GetTokenPriceHistoryRequest, 38 | ) -> types.GetTokenPriceHistoryReply: 39 | reply = self.provider.call_method( 40 | rpc="ankr_getTokenPriceHistory", 41 | request=request, 42 | reply=types.GetTokenPriceHistoryReply, 43 | ) 44 | 45 | return reply 46 | 47 | def get_account_balance_historical( 48 | self, 49 | request: types.GetAccountBalanceHistoricalRequest, 50 | limit: Optional[int] = None, 51 | ) -> Iterable[types.Balance]: 52 | for asset in self.provider.call_method_paginated( 53 | rpc="ankr_getAccountBalanceHistorical", 54 | request=request, 55 | reply=types.GetAccountBalanceHistoricalReply, 56 | iterable_name="assets", 57 | iterable_type=types.Balance, 58 | limit=limit, 59 | ): 60 | yield asset 61 | 62 | def get_account_balance_historical_raw( 63 | self, 64 | request: types.GetAccountBalanceHistoricalRequest, 65 | ) -> types.GetAccountBalanceHistoricalReply: 66 | reply = self.provider.call_method( 67 | rpc="ankr_getAccountBalanceHistorical", 68 | request=request, 69 | reply=types.GetAccountBalanceReply, 70 | ) 71 | 72 | return reply 73 | 74 | def get_internal_transactions_by_block_number( 75 | self, 76 | request: types.GetInternalTransactionsByBlockNumberRequest, 77 | limit: Optional[int] = None, 78 | ) -> Iterable[types.InternalTransaction]: 79 | for asset in self.provider.call_method_paginated( 80 | rpc="ankr_getInternalTransactionsByBlockNumber", 81 | request=request, 82 | reply=types.GetInternalTransactionsReply, 83 | iterable_name="internalTransactions", 84 | iterable_type=types.InternalTransaction, 85 | limit=limit, 86 | ): 87 | yield asset 88 | 89 | def get_internal_transactions_by_block_number_raw( 90 | self, 91 | request: types.GetInternalTransactionsByBlockNumberRequest, 92 | ) -> types.GetInternalTransactionsReply: 93 | reply = self.provider.call_method( 94 | rpc="ankr_getInternalTransactionsByBlockNumber", 95 | request=request, 96 | reply=types.GetInternalTransactionsReply, 97 | ) 98 | 99 | return reply 100 | 101 | def get_internal_transactions_by_parent_hash( 102 | self, 103 | request: types.GetInternalTransactionsByParentHashRequest, 104 | limit: Optional[int] = None, 105 | ) -> Iterable[types.InternalTransaction]: 106 | for asset in self.provider.call_method_paginated( 107 | rpc="ankr_getInternalTransactionsByParentHash", 108 | request=request, 109 | reply=types.GetInternalTransactionsReply, 110 | iterable_name="internalTransactions", 111 | iterable_type=types.InternalTransaction, 112 | limit=limit, 113 | ): 114 | yield asset 115 | 116 | def get_internal_transactions_by_parent_hash_raw( 117 | self, 118 | request: types.GetInternalTransactionsByBlockNumberRequest, 119 | ) -> types.GetInternalTransactionsReply: 120 | reply = self.provider.call_method( 121 | rpc="ankr_getInternalTransactionsByParentHash", 122 | request=request, 123 | reply=types.GetInternalTransactionsReply, 124 | ) 125 | 126 | return reply 127 | 128 | 129 | class AnkrQueryAPI(AnkrMultichainAPI): 130 | def get_logs( 131 | self, 132 | request: types.GetLogsRequest, 133 | limit: Optional[int] = None, 134 | ) -> Iterable[types.Log]: 135 | for log in self.provider.call_method_paginated( 136 | rpc="ankr_getLogs", 137 | request=request, 138 | reply=types.GetLogsReply, 139 | iterable_name="logs", 140 | iterable_type=types.Log, 141 | limit=limit, 142 | ): 143 | yield log 144 | 145 | def get_logs_raw( 146 | self, 147 | request: types.GetLogsRequest, 148 | ) -> types.GetLogsReply: 149 | reply = self.provider.call_method( 150 | rpc="ankr_getLogs", 151 | request=request, 152 | reply=types.GetLogsReply, 153 | ) 154 | 155 | return reply 156 | 157 | def get_blocks( 158 | self, 159 | request: types.GetBlocksRequest, 160 | ) -> List[types.Block]: 161 | reply = self.provider.call_method( 162 | rpc="ankr_getBlocks", 163 | request=request, 164 | reply=types.GetBlocksReply, 165 | ) 166 | 167 | return reply.blocks 168 | 169 | def get_blocks_raw( 170 | self, 171 | request: types.GetBlocksRequest, 172 | ) -> types.GetBlocksReply: 173 | reply = self.provider.call_method( 174 | rpc="ankr_getBlocks", 175 | request=request, 176 | reply=types.GetBlocksReply, 177 | ) 178 | 179 | return reply 180 | 181 | def get_transaction( 182 | self, 183 | request: types.GetTransactionsByHashRequest, 184 | ) -> types.Transaction | None: 185 | reply = self.provider.call_method( 186 | rpc="ankr_getTransactionsByHash", 187 | request=request, 188 | reply=types.GetTransactionsByHashReply, 189 | ) 190 | 191 | if len(reply.transactions) > 0: 192 | return reply.transactions[0] 193 | else: 194 | return None 195 | 196 | def get_transaction_raw( 197 | self, 198 | request: types.GetTransactionsByHashRequest, 199 | ) -> types.GetTransactionsByHashReply: 200 | reply = self.provider.call_method( 201 | rpc="ankr_getTransactionsByHash", 202 | request=request, 203 | reply=types.GetTransactionsByHashReply, 204 | ) 205 | 206 | return reply 207 | 208 | def get_transactions_by_address( 209 | self, 210 | request: types.GetTransactionsByAddressRequest, 211 | limit: Optional[int] = None, 212 | ) -> Iterable[types.Transaction]: 213 | for transaction in self.provider.call_method_paginated( 214 | rpc="ankr_getTransactionsByAddress", 215 | request=request, 216 | reply=types.GetTransactionsByAddressReply, 217 | iterable_name="transactions", 218 | iterable_type=types.Transaction, 219 | limit=limit, 220 | ): 221 | yield transaction 222 | 223 | def get_transactions_by_address_raw( 224 | self, 225 | request: types.GetTransactionsByAddressRequest, 226 | ) -> types.GetTransactionsByAddressReply: 227 | reply = self.provider.call_method( 228 | rpc="ankr_getTransactionsByAddress", 229 | request=request, 230 | reply=types.GetTransactionsByAddressReply, 231 | ) 232 | 233 | return reply 234 | 235 | def get_blockchain_stats( 236 | self, 237 | request: types.GetBlockchainStatsRequest, 238 | ) -> List[types.BlockchainStats]: 239 | reply = self.provider.call_method( 240 | rpc="ankr_getBlockchainStats", 241 | request=request, 242 | reply=types.GetBlockchainStatsReply, 243 | ) 244 | 245 | return reply.stats 246 | 247 | def get_blockchain_stats_raw( 248 | self, 249 | request: types.GetBlockchainStatsRequest, 250 | ) -> types.GetBlockchainStatsReply: 251 | reply = self.provider.call_method( 252 | rpc="ankr_getBlockchainStats", 253 | request=request, 254 | reply=types.GetBlockchainStatsReply, 255 | ) 256 | 257 | return reply 258 | 259 | def get_interactions( 260 | self, 261 | request: types.GetInteractionsRequest, 262 | ) -> List[types.Blockchain]: 263 | reply = self.provider.call_method( 264 | rpc="ankr_getInteractions", 265 | request=request, 266 | reply=types.GetInteractionsReply, 267 | ) 268 | 269 | return reply.blockchains 270 | 271 | def get_interactions_raw( 272 | self, 273 | request: types.GetInteractionsRequest, 274 | ) -> types.GetInteractionsReply: 275 | reply = self.provider.call_method( 276 | rpc="ankr_getInteractions", 277 | request=request, 278 | reply=types.GetInteractionsReply, 279 | ) 280 | 281 | return reply 282 | 283 | 284 | class AnkrTokenAPI(AnkrMultichainAPI): 285 | def explain_token_price( 286 | self, 287 | request: types.ExplainTokenPriceRequest, 288 | ) -> ([types.ExplainTokenPriceSinglePair], [types.PriceEstimate]): 289 | reply = self.provider.call_method( 290 | rpc="ankr_explainTokenPrice", 291 | request=request, 292 | reply=types.ExplainTokenPriceReply, 293 | ) 294 | 295 | if reply.pairs and reply.priceEstimates: 296 | return reply.pairs, reply.priceEstimates 297 | elif reply.pairs: 298 | return reply.pairs, [] 299 | elif reply.priceEstimates: 300 | return [], reply.priceEstimates 301 | else: 302 | return [], [] 303 | 304 | def explain_token_price_raw( 305 | self, 306 | request: types.ExplainTokenPriceRequest, 307 | ) -> types.ExplainTokenPriceReply: 308 | reply = self.provider.call_method( 309 | rpc="ankr_explainTokenPrice", 310 | request=request, 311 | reply=types.ExplainTokenPriceReply, 312 | ) 313 | 314 | return reply 315 | 316 | def get_account_balance( 317 | self, 318 | request: types.GetAccountBalanceRequest, 319 | limit: Optional[int] = None, 320 | ) -> Iterable[types.Balance]: 321 | for asset in self.provider.call_method_paginated( 322 | rpc="ankr_getAccountBalance", 323 | request=request, 324 | reply=types.GetAccountBalanceReply, 325 | iterable_name="assets", 326 | iterable_type=types.Balance, 327 | limit=limit, 328 | ): 329 | yield asset 330 | 331 | def get_account_balance_raw( 332 | self, 333 | request: types.GetAccountBalanceRequest, 334 | ) -> types.GetAccountBalanceReply: 335 | reply = self.provider.call_method( 336 | rpc="ankr_getAccountBalance", 337 | request=request, 338 | reply=types.GetAccountBalanceReply, 339 | ) 340 | 341 | return reply 342 | 343 | def get_currencies( 344 | self, 345 | request: types.GetCurrenciesRequest, 346 | ) -> List[types.CurrencyDetailsExtended]: 347 | reply = self.provider.call_method( 348 | rpc="ankr_getCurrencies", 349 | request=request, 350 | reply=types.GetCurrenciesReply, 351 | ) 352 | 353 | return reply.currencies 354 | 355 | def get_currencies_raw( 356 | self, 357 | request: types.GetCurrenciesRequest, 358 | ) -> types.GetCurrenciesReply: 359 | reply = self.provider.call_method( 360 | rpc="ankr_getCurrencies", 361 | request=request, 362 | reply=types.GetCurrenciesReply, 363 | ) 364 | 365 | return reply 366 | 367 | def get_token_holders( 368 | self, 369 | request: types.GetTokenHoldersRequest, 370 | limit: Optional[int] = None, 371 | ) -> Iterable[types.HolderBalance]: 372 | for asset in self.provider.call_method_paginated( 373 | rpc="ankr_getTokenHolders", 374 | request=request, 375 | reply=types.GetTokenHoldersReply, 376 | iterable_name="holders", 377 | iterable_type=types.HolderBalance, 378 | limit=limit, 379 | ): 380 | yield asset 381 | 382 | def get_token_holders_raw( 383 | self, 384 | request: types.GetTokenHoldersRequest, 385 | ) -> types.GetTokenHoldersReply: 386 | reply = self.provider.call_method( 387 | rpc="ankr_getTokenHolders", 388 | request=request, 389 | reply=types.GetTokenHoldersReply, 390 | ) 391 | return reply 392 | 393 | def get_token_holders_count_history( 394 | self, 395 | request: types.GetTokenHoldersCountRequest, 396 | limit: Optional[int] = None, 397 | ) -> Iterable[types.DailyHolderCount]: 398 | for holder in self.provider.call_method_paginated( 399 | rpc="ankr_getTokenHoldersCount", 400 | request=request, 401 | reply=types.GetTokenHoldersCountReply, 402 | iterable_name="holderCountHistory", 403 | iterable_type=types.DailyHolderCount, 404 | limit=limit, 405 | ): 406 | yield holder 407 | 408 | def get_token_holders_count_history_raw( 409 | self, 410 | request: types.GetTokenHoldersCountRequest, 411 | ) -> types.GetTokenHoldersCountReply: 412 | reply = self.provider.call_method( 413 | rpc="ankr_getTokenHoldersCount", 414 | request=request, 415 | reply=types.GetTokenHoldersCountReply, 416 | ) 417 | return reply 418 | 419 | def get_token_holders_count( 420 | self, 421 | request: types.GetTokenHoldersCountRequest, 422 | ) -> types.DailyHolderCount: 423 | request.pageSize = 1 424 | reply = self.provider.call_method( 425 | rpc="ankr_getTokenHoldersCount", 426 | request=request, 427 | reply=types.GetTokenHoldersCountReply, 428 | ) 429 | if len(reply.holderCountHistory) < 1: 430 | raise APIError("no token holders count found") 431 | return reply.holderCountHistory[0] 432 | 433 | def get_token_holders_count_raw( 434 | self, 435 | request: types.GetTokenHoldersCountRequest, 436 | ) -> types.GetTokenHoldersCountReply: 437 | reply = self.provider.call_method( 438 | rpc="ankr_getTokenHoldersCount", 439 | request=request, 440 | reply=types.GetTokenHoldersCountReply, 441 | ) 442 | return reply 443 | 444 | def get_token_price(self, request: types.GetTokenPriceRequest) -> str: 445 | reply = self.provider.call_method( 446 | rpc="ankr_getTokenPrice", 447 | request=request, 448 | reply=types.GetTokenPriceReply, 449 | ) 450 | return reply.usdPrice 451 | 452 | def get_token_price_raw( 453 | self, request: types.GetTokenPriceRequest 454 | ) -> types.GetTokenPriceReply: 455 | reply = self.provider.call_method( 456 | rpc="ankr_getTokenPrice", 457 | request=request, 458 | reply=types.GetTokenPriceReply, 459 | ) 460 | return reply 461 | 462 | def get_token_transfers( 463 | self, request: types.GetTransfersRequest, limit: Optional[int] = None 464 | ) -> Iterable[types.TokenTransfer]: 465 | for transfer in self.provider.call_method_paginated( 466 | rpc="ankr_getTokenTransfers", 467 | request=request, 468 | reply=types.GetTokenTransfersReply, 469 | iterable_name="transfers", 470 | iterable_type=types.TokenTransfer, 471 | limit=limit, 472 | ): 473 | yield transfer 474 | 475 | def get_token_transfers_raw( 476 | self, request: types.GetTransfersRequest 477 | ) -> types.GetTokenTransfersReply: 478 | reply = self.provider.call_method( 479 | rpc="ankr_getTokenTransfers", 480 | request=request, 481 | reply=types.GetTokenTransfersReply, 482 | ) 483 | return reply 484 | 485 | 486 | class AnkrNFTAPI(AnkrMultichainAPI): 487 | def get_nfts( 488 | self, request: types.GetNFTsByOwnerRequest, limit: Optional[int] = None 489 | ) -> Iterable[types.Nft]: 490 | for nft in self.provider.call_method_paginated( 491 | rpc="ankr_getNFTsByOwner", 492 | request=request, 493 | reply=types.GetNFTsByOwnerReply, 494 | iterable_name="assets", 495 | iterable_type=types.Nft, 496 | limit=limit, 497 | ): 498 | yield nft 499 | 500 | def get_nfts_raw( 501 | self, request: types.GetNFTsByOwnerRequest 502 | ) -> types.GetNFTsByOwnerReply: 503 | reply = self.provider.call_method( 504 | rpc="ankr_getNFTsByOwner", 505 | request=request, 506 | reply=types.GetNFTsByOwnerReply, 507 | ) 508 | return reply 509 | 510 | def get_nft_metadata( 511 | self, request: types.GetNFTMetadataRequest 512 | ) -> types.GetNFTMetadataReply: 513 | reply = self.provider.call_method( 514 | rpc="ankr_getNFTMetadata", 515 | request=request, 516 | reply=types.GetNFTMetadataReply, 517 | ) 518 | return reply 519 | 520 | def get_nft_metadata_raw( 521 | self, request: types.GetNFTMetadataRequest 522 | ) -> types.GetNFTMetadataReply: 523 | reply = self.provider.call_method( 524 | rpc="ankr_getNFTMetadata", 525 | request=request, 526 | reply=types.GetNFTMetadataReply, 527 | ) 528 | return reply 529 | 530 | def get_nft_holders( 531 | self, 532 | request: types.GetNFTHoldersRequest, 533 | limit: Optional[int] = None, 534 | ) -> Iterable[str]: 535 | for holder in self.provider.call_method_paginated( 536 | rpc="ankr_getNFTHolders", 537 | request=request, 538 | reply=types.GetNFTHoldersReply, 539 | iterable_name="holders", 540 | iterable_type=str, 541 | limit=limit, 542 | ): 543 | yield holder 544 | 545 | def get_nft_holders_raw( 546 | self, 547 | request: types.GetNFTHoldersRequest, 548 | ) -> types.GetNFTHoldersReply: 549 | reply = self.provider.call_method( 550 | rpc="ankr_getNFTHolders", 551 | request=request, 552 | reply=types.GetNFTHoldersReply, 553 | ) 554 | return reply 555 | 556 | def get_nft_transfers( 557 | self, request: types.GetTransfersRequest, limit: Optional[int] = None 558 | ) -> Iterable[types.NftTransfer]: 559 | for transfer in self.provider.call_method_paginated( 560 | rpc="ankr_getNftTransfers", 561 | request=request, 562 | reply=types.GetNftTransfersReply, 563 | iterable_name="transfers", 564 | iterable_type=types.NftTransfer, 565 | limit=limit, 566 | ): 567 | yield transfer 568 | 569 | def get_nft_transfers_raw( 570 | self, request: types.GetTransfersRequest 571 | ) -> types.GetNftTransfersReply: 572 | reply = self.provider.call_method( 573 | rpc="ankr_getNftTransfers", 574 | request=request, 575 | reply=types.GetNftTransfersReply, 576 | ) 577 | return reply 578 | 579 | 580 | class AnkrAdvancedAPI(AnkrEarlyAccessAPI, AnkrQueryAPI, AnkrTokenAPI, AnkrNFTAPI): 581 | ... 582 | -------------------------------------------------------------------------------- /ankr/exceptions.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from web3.types import RPCError 4 | 5 | 6 | class APIError(Exception): 7 | def __init__(self, error: RPCError | str) -> None: 8 | super().__init__(f"failed to handle request, {error}") 9 | -------------------------------------------------------------------------------- /ankr/providers.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Any, Iterable, List, Optional, Type, Union 4 | 5 | from eth_typing import URI 6 | from typing_extensions import Protocol 7 | from web3 import HTTPProvider 8 | from web3.types import RPCEndpoint, RPCResponse 9 | from ankr.exceptions import APIError 10 | 11 | 12 | class MultichainHTTPProvider(HTTPProvider): 13 | def __init__( 14 | self, 15 | api_key: str, 16 | endpoint_uri: Optional[Union[URI, str]] = None, 17 | request_kwargs: Optional[Any] = None, 18 | session: Optional[Any] = None, 19 | ) -> None: 20 | endpoint_uri = endpoint_uri or "https://rpc.ankr.com/multichain/" 21 | super().__init__(endpoint_uri + api_key, request_kwargs, session) 22 | 23 | def clean_nones(self, value): 24 | """ 25 | Recursively remove all None values from dictionaries and lists, and returns 26 | the result as a new dictionary or list. 27 | """ 28 | if isinstance(value, list): 29 | return [self.clean_nones(x) for x in value if x is not None] 30 | elif isinstance(value, dict): 31 | return { 32 | key: self.clean_nones(val) 33 | for key, val in value.items() 34 | if val is not None 35 | } 36 | else: 37 | return value 38 | 39 | def make_request(self, method: RPCEndpoint, params: Any) -> RPCResponse: 40 | response = super().make_request( 41 | method, self.clean_nones(params.to_dict().copy()) 42 | ) 43 | if response.get("error"): 44 | raise APIError(response["error"]) 45 | if "result" not in response: 46 | raise APIError("returned no result") 47 | return response 48 | 49 | def call_method( 50 | self, 51 | rpc: str, 52 | request: Any, 53 | reply: Any, 54 | ) -> Any: 55 | response = self.make_request(RPCEndpoint(rpc), request) 56 | reply = reply.from_dict(**response["result"]) 57 | return reply 58 | 59 | def call_method_paginated( 60 | self, 61 | *, 62 | rpc: str, 63 | request: Any, 64 | reply: Any, 65 | iterable_name: str, 66 | iterable_type: Type[Any], 67 | limit: Optional[int] = None, 68 | ) -> Iterable[Any]: 69 | response = self.make_request(RPCEndpoint(rpc), request) 70 | reply = reply.from_dict(**response["result"]) 71 | 72 | items: List[Any] = getattr(reply, iterable_name) or [] 73 | 74 | if limit: 75 | if limit <= len(items): 76 | yield from items[:limit] 77 | return 78 | limit -= len(items) 79 | 80 | yield from items 81 | 82 | if reply.nextPageToken: 83 | request.pageToken = reply.nextPageToken 84 | yield from self.call_method_paginated( 85 | rpc=RPCEndpoint(rpc), 86 | request=request, 87 | reply=reply, 88 | iterable_name=iterable_name, 89 | iterable_type=iterable_type, 90 | limit=limit, 91 | ) 92 | 93 | 94 | class TProviderConstructor(Protocol): 95 | def __call__( 96 | self, api_key: Optional[str] = None, request_kwargs: Optional[Any] = None 97 | ) -> HTTPProvider: 98 | ... 99 | 100 | 101 | def http_provider_constructor(url: str) -> TProviderConstructor: 102 | def init( 103 | api_key: Optional[str] = None, 104 | request_kwargs: Optional[Any] = None, 105 | ) -> HTTPProvider: 106 | if api_key is None: 107 | api_key = "" 108 | return HTTPProvider(f"{url}/{api_key}", request_kwargs) 109 | 110 | return init 111 | 112 | 113 | # EVM 114 | ArbitrumHTTPProvider = http_provider_constructor("https://rpc.ankr.com/arbitrum") 115 | AvalancheHTTPProvider = http_provider_constructor("https://rpc.ankr.com/avalanche") 116 | BscHTTPProvider = http_provider_constructor("https://rpc.ankr.com/bsc") 117 | CeloHTTPProvider = http_provider_constructor("https://rpc.ankr.com/celo") 118 | EthHTTPProvider = http_provider_constructor("https://rpc.ankr.com/eth") 119 | FantomHTTPProvider = http_provider_constructor("https://rpc.ankr.com/fantom") 120 | GnosisHTTPProvider = http_provider_constructor("https://rpc.ankr.com/gnosis") 121 | HarmonyHTTPProvider = http_provider_constructor("https://rpc.ankr.com/harmony") 122 | IotexHTTPProvider = http_provider_constructor("https://rpc.ankr.com/iotex") 123 | MoonbeamHTTPProvider = http_provider_constructor("https://rpc.ankr.com/moonbeam") 124 | NervosHTTPProvider = http_provider_constructor("https://rpc.ankr.com/nervos") 125 | OptimismHTTPProvider = http_provider_constructor("https://rpc.ankr.com/optimism") 126 | PolygonHTTPProvider = http_provider_constructor("https://rpc.ankr.com/polygon") 127 | SyscoinHTTPProvider = http_provider_constructor("https://rpc.ankr.com/syscoin") 128 | 129 | # Non-EVM 130 | NearHTTPProvider = http_provider_constructor("https://rpc.ankr.com/near") 131 | SolanaHTTPProvider = http_provider_constructor("https://rpc.ankr.com/solana") 132 | -------------------------------------------------------------------------------- /ankr/types.py: -------------------------------------------------------------------------------- 1 | # THIS FILE IS AUTOGENERATED 2 | # TO FIX ISSUE RELATED TO THIS FILE 3 | # PLEASE FILE AN ISSUE ON https://github.com/Ankr-network/ankr-python-sdk/issues 4 | 5 | from __future__ import annotations 6 | from enum import Enum 7 | from typing import Literal, List, Dict 8 | 9 | 10 | class SyncStatus: 11 | def __init__(self, lag: str, status: str, timestamp: float): 12 | self.lag = lag 13 | self.status = status 14 | self.timestamp = timestamp 15 | 16 | @classmethod 17 | def from_dict(cls, **data): 18 | return cls( 19 | lag=data.get("lag"), 20 | status=data.get("status"), 21 | timestamp=data.get("timestamp"), 22 | ) 23 | 24 | 25 | class MethodInput: 26 | def __init__(self, name: str, size: float, type: str, valueDecoded: str): 27 | self.name = name 28 | self.size = size 29 | self.type = type 30 | self.valueDecoded = valueDecoded 31 | 32 | @classmethod 33 | def from_dict(cls, **data): 34 | return cls( 35 | name=data.get("name"), 36 | size=data.get("size"), 37 | type=data.get("type"), 38 | valueDecoded=data.get("valueDecoded"), 39 | ) 40 | 41 | 42 | class Method: 43 | def __init__( 44 | self, 45 | id: str, 46 | inputs: List[MethodInput], 47 | name: str, 48 | signature: str, 49 | string: str, 50 | verified: bool, 51 | ): 52 | self.id = id 53 | self.inputs = inputs 54 | self.name = name 55 | self.signature = signature 56 | self.string = string 57 | self.verified = verified 58 | 59 | @classmethod 60 | def from_dict(cls, **data): 61 | return cls( 62 | id=data.get("id"), 63 | inputs=[ 64 | MethodInput.from_dict(**methodinput_data) 65 | for methodinput_data in data.get("inputs", []) 66 | ], 67 | name=data.get("name"), 68 | signature=data.get("signature"), 69 | string=data.get("string"), 70 | verified=data.get("verified"), 71 | ) 72 | 73 | 74 | class EventInput: 75 | def __init__( 76 | self, indexed: bool, name: str, size: float, type: str, valueDecoded: str 77 | ): 78 | self.indexed = indexed 79 | self.name = name 80 | self.size = size 81 | self.type = type 82 | self.valueDecoded = valueDecoded 83 | 84 | @classmethod 85 | def from_dict(cls, **data): 86 | return cls( 87 | indexed=data.get("indexed"), 88 | name=data.get("name"), 89 | size=data.get("size"), 90 | type=data.get("type"), 91 | valueDecoded=data.get("valueDecoded"), 92 | ) 93 | 94 | 95 | class Event: 96 | def __init__( 97 | self, 98 | anonymous: bool, 99 | id: str, 100 | inputs: List[EventInput], 101 | name: str, 102 | signature: str, 103 | string: str, 104 | verified: bool, 105 | ): 106 | self.anonymous = anonymous 107 | self.id = id 108 | self.inputs = inputs 109 | self.name = name 110 | self.signature = signature 111 | self.string = string 112 | self.verified = verified 113 | 114 | @classmethod 115 | def from_dict(cls, **data): 116 | return cls( 117 | anonymous=data.get("anonymous"), 118 | id=data.get("id"), 119 | inputs=[ 120 | EventInput.from_dict(**eventinput_data) 121 | for eventinput_data in data.get("inputs", []) 122 | ], 123 | name=data.get("name"), 124 | signature=data.get("signature"), 125 | string=data.get("string"), 126 | verified=data.get("verified"), 127 | ) 128 | 129 | 130 | class Log: 131 | def __init__( 132 | self, 133 | address: str, 134 | blockHash: str, 135 | blockNumber: str, 136 | blockchain: Blockchain, 137 | data: str, 138 | logIndex: str, 139 | removed: bool, 140 | topics: List[str], 141 | transactionHash: str, 142 | transactionIndex: str, 143 | event: Event = None, 144 | ): 145 | self.address = address 146 | self.blockHash = blockHash 147 | self.blockNumber = blockNumber 148 | self.blockchain = blockchain 149 | self.data = data 150 | self.logIndex = logIndex 151 | self.removed = removed 152 | self.topics = topics 153 | self.transactionHash = transactionHash 154 | self.transactionIndex = transactionIndex 155 | self.event = event 156 | 157 | @classmethod 158 | def from_dict(cls, **data): 159 | return cls( 160 | address=data.get("address"), 161 | blockHash=data.get("blockHash"), 162 | blockNumber=data.get("blockNumber"), 163 | blockchain=Blockchain(data.get("blockchain")), 164 | data=data.get("data"), 165 | logIndex=data.get("logIndex"), 166 | removed=data.get("removed"), 167 | topics=data.get("topics"), 168 | transactionHash=data.get("transactionHash"), 169 | transactionIndex=data.get("transactionIndex"), 170 | event=Event.from_dict(**data.get("event")) 171 | if data.get("event") is not None 172 | else None, 173 | ) 174 | 175 | 176 | class Transaction: 177 | def __init__( 178 | self, 179 | blockHash: str, 180 | blockNumber: str, 181 | from_: str, 182 | transactionIndex: str, 183 | value: str, 184 | gasPrice: str = None, 185 | gas: str = None, 186 | contractAddress: str = None, 187 | cumulativeGasUsed: str = None, 188 | input: str = None, 189 | v: str = None, 190 | r: str = None, 191 | s: str = None, 192 | method: Method = None, 193 | to: str = None, 194 | nonce: str = None, 195 | gasUsed: str = None, 196 | logs: List[Log] = None, 197 | hash: str = None, 198 | status: str = None, 199 | blockchain: str = None, 200 | timestamp: str = None, 201 | type: str = None, 202 | ): 203 | self.blockHash = blockHash 204 | self.blockNumber = blockNumber 205 | self.from_ = from_ 206 | self.transactionIndex = transactionIndex 207 | self.value = value 208 | self.gasPrice = gasPrice 209 | self.gas = gas 210 | self.contractAddress = contractAddress 211 | self.cumulativeGasUsed = cumulativeGasUsed 212 | self.input = input 213 | self.v = v 214 | self.r = r 215 | self.s = s 216 | self.method = method 217 | self.to = to 218 | self.nonce = nonce 219 | self.gasUsed = gasUsed 220 | self.logs = logs 221 | self.hash = hash 222 | self.status = status 223 | self.blockchain = blockchain 224 | self.timestamp = timestamp 225 | self.type = type 226 | 227 | @classmethod 228 | def from_dict(cls, **data): 229 | return cls( 230 | blockHash=data.get("blockHash"), 231 | blockNumber=data.get("blockNumber"), 232 | from_=data.get("from"), 233 | transactionIndex=data.get("transactionIndex"), 234 | value=data.get("value"), 235 | gasPrice=data.get("gasPrice"), 236 | gas=data.get("gas"), 237 | contractAddress=data.get("contractAddress"), 238 | cumulativeGasUsed=data.get("cumulativeGasUsed"), 239 | input=data.get("input"), 240 | v=data.get("v"), 241 | r=data.get("r"), 242 | s=data.get("s"), 243 | method=Method.from_dict(**data.get("method")) 244 | if data.get("method") is not None 245 | else None, 246 | to=data.get("to"), 247 | nonce=data.get("nonce"), 248 | gasUsed=data.get("gasUsed"), 249 | logs=[Log.from_dict(**log_data) for log_data in data.get("logs", [])], 250 | hash=data.get("hash"), 251 | status=data.get("status"), 252 | blockchain=data.get("blockchain"), 253 | timestamp=data.get("timestamp"), 254 | type=data.get("type"), 255 | ) 256 | 257 | 258 | class Block: 259 | def __init__( 260 | self, 261 | difficulty: str, 262 | extraData: str, 263 | gasLimit: str, 264 | gasUsed: str, 265 | hash: str, 266 | logsBloom: str, 267 | miner: str, 268 | mixHash: str, 269 | nonce: str, 270 | number: str, 271 | parentHash: str, 272 | receiptsRoot: str, 273 | sha3Uncles: str, 274 | size: str, 275 | stateRoot: str, 276 | timestamp: str, 277 | totalDifficulty: str, 278 | transactions: List[Transaction], 279 | transactionsRoot: str, 280 | uncles: List[str], 281 | blockchain: str = None, 282 | ): 283 | self.difficulty = difficulty 284 | self.extraData = extraData 285 | self.gasLimit = gasLimit 286 | self.gasUsed = gasUsed 287 | self.hash = hash 288 | self.logsBloom = logsBloom 289 | self.miner = miner 290 | self.mixHash = mixHash 291 | self.nonce = nonce 292 | self.number = number 293 | self.parentHash = parentHash 294 | self.receiptsRoot = receiptsRoot 295 | self.sha3Uncles = sha3Uncles 296 | self.size = size 297 | self.stateRoot = stateRoot 298 | self.timestamp = timestamp 299 | self.totalDifficulty = totalDifficulty 300 | self.transactions = transactions 301 | self.transactionsRoot = transactionsRoot 302 | self.uncles = uncles 303 | self.blockchain = blockchain 304 | 305 | @classmethod 306 | def from_dict(cls, **data): 307 | return cls( 308 | difficulty=data.get("difficulty"), 309 | extraData=data.get("extraData"), 310 | gasLimit=data.get("gasLimit"), 311 | gasUsed=data.get("gasUsed"), 312 | hash=data.get("hash"), 313 | logsBloom=data.get("logsBloom"), 314 | miner=data.get("miner"), 315 | mixHash=data.get("mixHash"), 316 | nonce=data.get("nonce"), 317 | number=data.get("number"), 318 | parentHash=data.get("parentHash"), 319 | receiptsRoot=data.get("receiptsRoot"), 320 | sha3Uncles=data.get("sha3Uncles"), 321 | size=data.get("size"), 322 | stateRoot=data.get("stateRoot"), 323 | timestamp=data.get("timestamp"), 324 | totalDifficulty=data.get("totalDifficulty"), 325 | transactions=[ 326 | Transaction.from_dict(**transaction_data) 327 | for transaction_data in data.get("transactions", []) 328 | ], 329 | transactionsRoot=data.get("transactionsRoot"), 330 | uncles=data.get("uncles"), 331 | blockchain=data.get("blockchain"), 332 | ) 333 | 334 | 335 | class GetBlocksReply: 336 | def __init__(self, blocks: List[Block], syncStatus: SyncStatus = None): 337 | self.blocks = blocks 338 | self.syncStatus = syncStatus 339 | 340 | @classmethod 341 | def from_dict(cls, **data): 342 | return cls( 343 | blocks=[ 344 | Block.from_dict(**block_data) for block_data in data.get("blocks", []) 345 | ], 346 | syncStatus=SyncStatus.from_dict(**data.get("syncStatus")) 347 | if data.get("syncStatus") is not None 348 | else None, 349 | ) 350 | 351 | 352 | class GetBlocksRequest: 353 | def __init__( 354 | self, 355 | blockchain: Blockchain, 356 | fromBlock: float 357 | | Literal[Literal["latest"]] 358 | | Literal[Literal["earliest"]] = None, 359 | toBlock: float 360 | | Literal[Literal["latest"]] 361 | | Literal[Literal["earliest"]] = None, 362 | descOrder: bool = None, 363 | includeLogs: bool = None, 364 | includeTxs: bool = None, 365 | decodeLogs: bool = None, 366 | decodeTxData: bool = None, 367 | syncCheck: bool = None, 368 | ): 369 | self.blockchain = blockchain 370 | self.fromBlock = fromBlock 371 | self.toBlock = toBlock 372 | self.descOrder = descOrder 373 | self.includeLogs = includeLogs 374 | self.includeTxs = includeTxs 375 | self.decodeLogs = decodeLogs 376 | self.decodeTxData = decodeTxData 377 | self.syncCheck = syncCheck 378 | 379 | def to_dict(self): 380 | if isinstance(self.blockchain, str): 381 | blockchain_value = self.blockchain 382 | elif isinstance(self.blockchain, list): 383 | blockchain_value = [ 384 | block.value if isinstance(block, Blockchain) else block 385 | for block in self.blockchain 386 | ] 387 | elif self.blockchain is not None: 388 | blockchain_value = self.blockchain.value 389 | else: 390 | blockchain_value = None 391 | return { 392 | "blockchain": blockchain_value, 393 | "fromBlock": self.fromBlock, 394 | "toBlock": self.toBlock, 395 | "descOrder": self.descOrder, 396 | "includeLogs": self.includeLogs, 397 | "includeTxs": self.includeTxs, 398 | "decodeLogs": self.decodeLogs, 399 | "decodeTxData": self.decodeTxData, 400 | "syncCheck": self.syncCheck, 401 | } 402 | 403 | 404 | class GetTransactionsByHashReply: 405 | def __init__(self, transactions: List[Transaction], syncStatus: SyncStatus = None): 406 | self.transactions = transactions 407 | self.syncStatus = syncStatus 408 | 409 | @classmethod 410 | def from_dict(cls, **data): 411 | return cls( 412 | transactions=[ 413 | Transaction.from_dict(**transaction_data) 414 | for transaction_data in data.get("transactions", []) 415 | ], 416 | syncStatus=SyncStatus.from_dict(**data.get("syncStatus")) 417 | if data.get("syncStatus") is not None 418 | else None, 419 | ) 420 | 421 | 422 | class GetTransactionsByHashRequest: 423 | def __init__( 424 | self, 425 | transactionHash: str, 426 | blockchain: Blockchain | List[Blockchain] = None, 427 | includeLogs: bool = None, 428 | decodeLogs: bool = None, 429 | decodeTxData: bool = None, 430 | syncCheck: bool = None, 431 | ): 432 | self.transactionHash = transactionHash 433 | self.blockchain = blockchain 434 | self.includeLogs = includeLogs 435 | self.decodeLogs = decodeLogs 436 | self.decodeTxData = decodeTxData 437 | self.syncCheck = syncCheck 438 | 439 | def to_dict(self): 440 | if isinstance(self.blockchain, str): 441 | blockchain_value = self.blockchain 442 | elif isinstance(self.blockchain, list): 443 | blockchain_value = [ 444 | block.value if isinstance(block, Blockchain) else block 445 | for block in self.blockchain 446 | ] 447 | elif self.blockchain is not None: 448 | blockchain_value = self.blockchain.value 449 | else: 450 | blockchain_value = None 451 | return { 452 | "transactionHash": self.transactionHash, 453 | "blockchain": blockchain_value, 454 | "includeLogs": self.includeLogs, 455 | "decodeLogs": self.decodeLogs, 456 | "decodeTxData": self.decodeTxData, 457 | "syncCheck": self.syncCheck, 458 | } 459 | 460 | 461 | class GetTransactionsByAddressReply: 462 | def __init__( 463 | self, 464 | nextPageToken: str, 465 | transactions: List[Transaction], 466 | syncStatus: SyncStatus = None, 467 | ): 468 | self.nextPageToken = nextPageToken 469 | self.transactions = transactions 470 | self.syncStatus = syncStatus 471 | 472 | @classmethod 473 | def from_dict(cls, **data): 474 | return cls( 475 | nextPageToken=data.get("nextPageToken"), 476 | transactions=[ 477 | Transaction.from_dict(**transaction_data) 478 | for transaction_data in data.get("transactions", []) 479 | ], 480 | syncStatus=SyncStatus.from_dict(**data.get("syncStatus")) 481 | if data.get("syncStatus") is not None 482 | else None, 483 | ) 484 | 485 | 486 | class GetTransactionsByAddressRequest: 487 | def __init__( 488 | self, 489 | address: List[str], 490 | blockchain: Blockchain | List[Blockchain], 491 | fromBlock: float 492 | | Literal[Literal["latest"]] 493 | | Literal[Literal["earliest"]] = None, 494 | toBlock: float 495 | | Literal[Literal["latest"]] 496 | | Literal[Literal["earliest"]] = None, 497 | fromTimestamp: float 498 | | Literal[Literal["latest"]] 499 | | Literal[Literal["earliest"]] = None, 500 | toTimestamp: float 501 | | Literal[Literal["latest"]] 502 | | Literal[Literal["earliest"]] = None, 503 | pageToken: str = None, 504 | pageSize: float = None, 505 | descOrder: bool = None, 506 | includeLogs: bool = None, 507 | syncCheck: bool = None, 508 | ): 509 | self.address = address 510 | self.blockchain = blockchain 511 | self.fromBlock = fromBlock 512 | self.toBlock = toBlock 513 | self.fromTimestamp = fromTimestamp 514 | self.toTimestamp = toTimestamp 515 | self.pageToken = pageToken 516 | self.pageSize = pageSize 517 | self.descOrder = descOrder 518 | self.includeLogs = includeLogs 519 | self.syncCheck = syncCheck 520 | 521 | def to_dict(self): 522 | if isinstance(self.blockchain, str): 523 | blockchain_value = self.blockchain 524 | elif isinstance(self.blockchain, list): 525 | blockchain_value = [ 526 | block.value if isinstance(block, Blockchain) else block 527 | for block in self.blockchain 528 | ] 529 | elif self.blockchain is not None: 530 | blockchain_value = self.blockchain.value 531 | else: 532 | blockchain_value = None 533 | return { 534 | "address": self.address, 535 | "blockchain": blockchain_value, 536 | "fromBlock": self.fromBlock, 537 | "toBlock": self.toBlock, 538 | "fromTimestamp": self.fromTimestamp, 539 | "toTimestamp": self.toTimestamp, 540 | "pageToken": self.pageToken, 541 | "pageSize": self.pageSize, 542 | "descOrder": self.descOrder, 543 | "includeLogs": self.includeLogs, 544 | "syncCheck": self.syncCheck, 545 | } 546 | 547 | 548 | class GetLogsReply: 549 | def __init__( 550 | self, logs: List[Log], nextPageToken: str = None, syncStatus: SyncStatus = None 551 | ): 552 | self.logs = logs 553 | self.nextPageToken = nextPageToken 554 | self.syncStatus = syncStatus 555 | 556 | @classmethod 557 | def from_dict(cls, **data): 558 | return cls( 559 | logs=[Log.from_dict(**log_data) for log_data in data.get("logs", [])], 560 | nextPageToken=data.get("nextPageToken"), 561 | syncStatus=SyncStatus.from_dict(**data.get("syncStatus")) 562 | if data.get("syncStatus") is not None 563 | else None, 564 | ) 565 | 566 | 567 | class GetLogsRequest: 568 | def __init__( 569 | self, 570 | blockchain: Blockchain | List[Blockchain], 571 | fromBlock: float 572 | | Literal[Literal["latest"]] 573 | | Literal[Literal["earliest"]] = None, 574 | toBlock: float 575 | | Literal[Literal["latest"]] 576 | | Literal[Literal["earliest"]] = None, 577 | fromTimestamp: float 578 | | Literal[Literal["latest"]] 579 | | Literal[Literal["earliest"]] = None, 580 | toTimestamp: float 581 | | Literal[Literal["latest"]] 582 | | Literal[Literal["earliest"]] = None, 583 | address: List[str] = None, 584 | topics: List[str | List[str]] = None, 585 | pageToken: str = None, 586 | pageSize: float = None, 587 | descOrder: bool = None, 588 | decodeLogs: bool = None, 589 | syncCheck: bool = None, 590 | ): 591 | self.blockchain = blockchain 592 | self.fromBlock = fromBlock 593 | self.toBlock = toBlock 594 | self.fromTimestamp = fromTimestamp 595 | self.toTimestamp = toTimestamp 596 | self.address = address 597 | self.topics = topics 598 | self.pageToken = pageToken 599 | self.pageSize = pageSize 600 | self.descOrder = descOrder 601 | self.decodeLogs = decodeLogs 602 | self.syncCheck = syncCheck 603 | 604 | def to_dict(self): 605 | if isinstance(self.blockchain, str): 606 | blockchain_value = self.blockchain 607 | elif isinstance(self.blockchain, list): 608 | blockchain_value = [ 609 | block.value if isinstance(block, Blockchain) else block 610 | for block in self.blockchain 611 | ] 612 | elif self.blockchain is not None: 613 | blockchain_value = self.blockchain.value 614 | else: 615 | blockchain_value = None 616 | return { 617 | "blockchain": blockchain_value, 618 | "fromBlock": self.fromBlock, 619 | "toBlock": self.toBlock, 620 | "fromTimestamp": self.fromTimestamp, 621 | "toTimestamp": self.toTimestamp, 622 | "address": self.address, 623 | "topics": self.topics, 624 | "pageToken": self.pageToken, 625 | "pageSize": self.pageSize, 626 | "descOrder": self.descOrder, 627 | "decodeLogs": self.decodeLogs, 628 | "syncCheck": self.syncCheck, 629 | } 630 | 631 | 632 | class BlockchainStats: 633 | def __init__( 634 | self, 635 | blockTimeMs: float, 636 | blockchain: str, 637 | latestBlockNumber: float, 638 | nativeCoinUsdPrice: str, 639 | totalEventsCount: float, 640 | totalTransactionsCount: float, 641 | ): 642 | self.blockTimeMs = blockTimeMs 643 | self.blockchain = blockchain 644 | self.latestBlockNumber = latestBlockNumber 645 | self.nativeCoinUsdPrice = nativeCoinUsdPrice 646 | self.totalEventsCount = totalEventsCount 647 | self.totalTransactionsCount = totalTransactionsCount 648 | 649 | @classmethod 650 | def from_dict(cls, **data): 651 | return cls( 652 | blockTimeMs=data.get("blockTimeMs"), 653 | blockchain=data.get("blockchain"), 654 | latestBlockNumber=data.get("latestBlockNumber"), 655 | nativeCoinUsdPrice=data.get("nativeCoinUsdPrice"), 656 | totalEventsCount=data.get("totalEventsCount"), 657 | totalTransactionsCount=data.get("totalTransactionsCount"), 658 | ) 659 | 660 | 661 | class GetBlockchainStatsReply: 662 | def __init__(self, stats: List[BlockchainStats], syncStatus: SyncStatus = None): 663 | self.stats = stats 664 | self.syncStatus = syncStatus 665 | 666 | @classmethod 667 | def from_dict(cls, **data): 668 | return cls( 669 | stats=[ 670 | BlockchainStats.from_dict(**blockchainstats_data) 671 | for blockchainstats_data in data.get("stats", []) 672 | ], 673 | syncStatus=SyncStatus.from_dict(**data.get("syncStatus")) 674 | if data.get("syncStatus") is not None 675 | else None, 676 | ) 677 | 678 | 679 | class GetBlockchainStatsRequest: 680 | def __init__( 681 | self, blockchain: Blockchain | List[Blockchain] = None, syncCheck: bool = None 682 | ): 683 | self.blockchain = blockchain 684 | self.syncCheck = syncCheck 685 | 686 | def to_dict(self): 687 | if isinstance(self.blockchain, str): 688 | blockchain_value = self.blockchain 689 | elif isinstance(self.blockchain, list): 690 | blockchain_value = [ 691 | block.value if isinstance(block, Blockchain) else block 692 | for block in self.blockchain 693 | ] 694 | elif self.blockchain is not None: 695 | blockchain_value = self.blockchain.value 696 | else: 697 | blockchain_value = None 698 | return { 699 | "blockchain": blockchain_value, 700 | "syncCheck": self.syncCheck, 701 | } 702 | 703 | 704 | class GetInteractionsReply: 705 | def __init__(self, blockchains: List[str], syncStatus: SyncStatus = None): 706 | self.blockchains = blockchains 707 | self.syncStatus = syncStatus 708 | 709 | @classmethod 710 | def from_dict(cls, **data): 711 | return cls( 712 | blockchains=data.get("blockchains"), 713 | syncStatus=SyncStatus.from_dict(**data.get("syncStatus")) 714 | if data.get("syncStatus") is not None 715 | else None, 716 | ) 717 | 718 | 719 | class GetInteractionsRequest: 720 | def __init__(self, address: str, syncCheck: bool = None): 721 | self.address = address 722 | self.syncCheck = syncCheck 723 | 724 | def to_dict(self): 725 | return { 726 | "address": self.address, 727 | "syncCheck": self.syncCheck, 728 | } 729 | 730 | 731 | class Balance: 732 | def __init__( 733 | self, 734 | balance: str, 735 | balanceRawInteger: str, 736 | balanceUsd: str, 737 | blockchain: Blockchain, 738 | holderAddress: str, 739 | thumbnail: str, 740 | tokenDecimals: float, 741 | tokenName: str, 742 | tokenPrice: str, 743 | tokenSymbol: str, 744 | tokenType: str, 745 | contractAddress: str = None, 746 | ): 747 | self.balance = balance 748 | self.balanceRawInteger = balanceRawInteger 749 | self.balanceUsd = balanceUsd 750 | self.blockchain = blockchain 751 | self.holderAddress = holderAddress 752 | self.thumbnail = thumbnail 753 | self.tokenDecimals = tokenDecimals 754 | self.tokenName = tokenName 755 | self.tokenPrice = tokenPrice 756 | self.tokenSymbol = tokenSymbol 757 | self.tokenType = tokenType 758 | self.contractAddress = contractAddress 759 | 760 | @classmethod 761 | def from_dict(cls, **data): 762 | return cls( 763 | balance=data.get("balance"), 764 | balanceRawInteger=data.get("balanceRawInteger"), 765 | balanceUsd=data.get("balanceUsd"), 766 | blockchain=Blockchain(data.get("blockchain")), 767 | holderAddress=data.get("holderAddress"), 768 | thumbnail=data.get("thumbnail"), 769 | tokenDecimals=data.get("tokenDecimals"), 770 | tokenName=data.get("tokenName"), 771 | tokenPrice=data.get("tokenPrice"), 772 | tokenSymbol=data.get("tokenSymbol"), 773 | tokenType=data.get("tokenType"), 774 | contractAddress=data.get("contractAddress"), 775 | ) 776 | 777 | 778 | class GetAccountBalanceReply: 779 | def __init__( 780 | self, 781 | assets: List[Balance], 782 | totalBalanceUsd: str, 783 | totalCount: float, 784 | nextPageToken: str = None, 785 | syncStatus: SyncStatus = None, 786 | ): 787 | self.assets = assets 788 | self.totalBalanceUsd = totalBalanceUsd 789 | self.totalCount = totalCount 790 | self.nextPageToken = nextPageToken 791 | self.syncStatus = syncStatus 792 | 793 | @classmethod 794 | def from_dict(cls, **data): 795 | return cls( 796 | assets=[ 797 | Balance.from_dict(**balance_data) 798 | for balance_data in data.get("assets", []) 799 | ], 800 | totalBalanceUsd=data.get("totalBalanceUsd"), 801 | totalCount=data.get("totalCount"), 802 | nextPageToken=data.get("nextPageToken"), 803 | syncStatus=SyncStatus.from_dict(**data.get("syncStatus")) 804 | if data.get("syncStatus") is not None 805 | else None, 806 | ) 807 | 808 | 809 | class GetAccountBalanceRequest: 810 | def __init__( 811 | self, 812 | walletAddress: str, 813 | blockchain: Blockchain | List[Blockchain] = None, 814 | onlyWhitelisted: bool = None, 815 | nativeFirst: bool = None, 816 | pageToken: str = None, 817 | pageSize: float = None, 818 | syncCheck: bool = None, 819 | ): 820 | self.walletAddress = walletAddress 821 | self.blockchain = blockchain 822 | self.onlyWhitelisted = onlyWhitelisted 823 | self.nativeFirst = nativeFirst 824 | self.pageToken = pageToken 825 | self.pageSize = pageSize 826 | self.syncCheck = syncCheck 827 | 828 | def to_dict(self): 829 | if isinstance(self.blockchain, str): 830 | blockchain_value = self.blockchain 831 | elif isinstance(self.blockchain, list): 832 | blockchain_value = [ 833 | block.value if isinstance(block, Blockchain) else block 834 | for block in self.blockchain 835 | ] 836 | elif self.blockchain is not None: 837 | blockchain_value = self.blockchain.value 838 | else: 839 | blockchain_value = None 840 | return { 841 | "walletAddress": self.walletAddress, 842 | "blockchain": blockchain_value, 843 | "onlyWhitelisted": self.onlyWhitelisted, 844 | "nativeFirst": self.nativeFirst, 845 | "pageToken": self.pageToken, 846 | "pageSize": self.pageSize, 847 | "syncCheck": self.syncCheck, 848 | } 849 | 850 | 851 | class GetTokenPriceReply: 852 | def __init__( 853 | self, 854 | blockchain: Blockchain, 855 | usdPrice: str, 856 | contractAddress: str = None, 857 | syncStatus: SyncStatus = None, 858 | ): 859 | self.blockchain = blockchain 860 | self.usdPrice = usdPrice 861 | self.contractAddress = contractAddress 862 | self.syncStatus = syncStatus 863 | 864 | @classmethod 865 | def from_dict(cls, **data): 866 | return cls( 867 | blockchain=Blockchain(data.get("blockchain")), 868 | usdPrice=data.get("usdPrice"), 869 | contractAddress=data.get("contractAddress"), 870 | syncStatus=SyncStatus.from_dict(**data.get("syncStatus")) 871 | if data.get("syncStatus") is not None 872 | else None, 873 | ) 874 | 875 | 876 | class GetTokenPriceRequest: 877 | def __init__( 878 | self, 879 | blockchain: Blockchain, 880 | contractAddress: str = None, 881 | syncCheck: bool = None, 882 | ): 883 | self.blockchain = blockchain 884 | self.contractAddress = contractAddress 885 | self.syncCheck = syncCheck 886 | 887 | def to_dict(self): 888 | if isinstance(self.blockchain, str): 889 | blockchain_value = self.blockchain 890 | elif isinstance(self.blockchain, list): 891 | blockchain_value = [ 892 | block.value if isinstance(block, Blockchain) else block 893 | for block in self.blockchain 894 | ] 895 | elif self.blockchain is not None: 896 | blockchain_value = self.blockchain.value 897 | else: 898 | blockchain_value = None 899 | return { 900 | "blockchain": blockchain_value, 901 | "contractAddress": self.contractAddress, 902 | "syncCheck": self.syncCheck, 903 | } 904 | 905 | 906 | class HolderBalance: 907 | def __init__(self, balance: str, balanceRawInteger: str, holderAddress: str): 908 | self.balance = balance 909 | self.balanceRawInteger = balanceRawInteger 910 | self.holderAddress = holderAddress 911 | 912 | @classmethod 913 | def from_dict(cls, **data): 914 | return cls( 915 | balance=data.get("balance"), 916 | balanceRawInteger=data.get("balanceRawInteger"), 917 | holderAddress=data.get("holderAddress"), 918 | ) 919 | 920 | 921 | class GetTokenHoldersReply: 922 | def __init__( 923 | self, 924 | blockchain: Blockchain, 925 | contractAddress: str, 926 | holders: List[HolderBalance], 927 | holdersCount: float, 928 | nextPageToken: str, 929 | tokenDecimals: float, 930 | syncStatus: SyncStatus = None, 931 | ): 932 | self.blockchain = blockchain 933 | self.contractAddress = contractAddress 934 | self.holders = holders 935 | self.holdersCount = holdersCount 936 | self.nextPageToken = nextPageToken 937 | self.tokenDecimals = tokenDecimals 938 | self.syncStatus = syncStatus 939 | 940 | @classmethod 941 | def from_dict(cls, **data): 942 | return cls( 943 | blockchain=Blockchain(data.get("blockchain")), 944 | contractAddress=data.get("contractAddress"), 945 | holders=[ 946 | HolderBalance.from_dict(**holderbalance_data) 947 | for holderbalance_data in data.get("holders", []) 948 | ], 949 | holdersCount=data.get("holdersCount"), 950 | nextPageToken=data.get("nextPageToken"), 951 | tokenDecimals=data.get("tokenDecimals"), 952 | syncStatus=SyncStatus.from_dict(**data.get("syncStatus")) 953 | if data.get("syncStatus") is not None 954 | else None, 955 | ) 956 | 957 | 958 | class GetTokenHoldersRequest: 959 | def __init__( 960 | self, 961 | blockchain: Blockchain, 962 | contractAddress: str, 963 | pageToken: str = None, 964 | pageSize: float = None, 965 | syncCheck: bool = None, 966 | ): 967 | self.blockchain = blockchain 968 | self.contractAddress = contractAddress 969 | self.pageToken = pageToken 970 | self.pageSize = pageSize 971 | self.syncCheck = syncCheck 972 | 973 | def to_dict(self): 974 | if isinstance(self.blockchain, str): 975 | blockchain_value = self.blockchain 976 | elif isinstance(self.blockchain, list): 977 | blockchain_value = [ 978 | block.value if isinstance(block, Blockchain) else block 979 | for block in self.blockchain 980 | ] 981 | elif self.blockchain is not None: 982 | blockchain_value = self.blockchain.value 983 | else: 984 | blockchain_value = None 985 | return { 986 | "blockchain": blockchain_value, 987 | "contractAddress": self.contractAddress, 988 | "pageToken": self.pageToken, 989 | "pageSize": self.pageSize, 990 | "syncCheck": self.syncCheck, 991 | } 992 | 993 | 994 | class DailyHolderCount: 995 | def __init__( 996 | self, 997 | holderCount: float, 998 | lastUpdatedAt: str, 999 | totalAmount: str, 1000 | totalAmountRawInteger: str, 1001 | ): 1002 | self.holderCount = holderCount 1003 | self.lastUpdatedAt = lastUpdatedAt 1004 | self.totalAmount = totalAmount 1005 | self.totalAmountRawInteger = totalAmountRawInteger 1006 | 1007 | @classmethod 1008 | def from_dict(cls, **data): 1009 | return cls( 1010 | holderCount=data.get("holderCount"), 1011 | lastUpdatedAt=data.get("lastUpdatedAt"), 1012 | totalAmount=data.get("totalAmount"), 1013 | totalAmountRawInteger=data.get("totalAmountRawInteger"), 1014 | ) 1015 | 1016 | 1017 | class GetTokenHoldersCountReply: 1018 | def __init__( 1019 | self, 1020 | blockchain: Blockchain, 1021 | contractAddress: str, 1022 | holderCountHistory: List[DailyHolderCount], 1023 | latestHoldersCount: float, 1024 | nextPageToken: str, 1025 | tokenDecimals: float, 1026 | syncStatus: SyncStatus = None, 1027 | ): 1028 | self.blockchain = blockchain 1029 | self.contractAddress = contractAddress 1030 | self.holderCountHistory = holderCountHistory 1031 | self.latestHoldersCount = latestHoldersCount 1032 | self.nextPageToken = nextPageToken 1033 | self.tokenDecimals = tokenDecimals 1034 | self.syncStatus = syncStatus 1035 | 1036 | @classmethod 1037 | def from_dict(cls, **data): 1038 | return cls( 1039 | blockchain=Blockchain(data.get("blockchain")), 1040 | contractAddress=data.get("contractAddress"), 1041 | holderCountHistory=[ 1042 | DailyHolderCount.from_dict(**dailyholdercount_data) 1043 | for dailyholdercount_data in data.get("holderCountHistory", []) 1044 | ], 1045 | latestHoldersCount=data.get("latestHoldersCount"), 1046 | nextPageToken=data.get("nextPageToken"), 1047 | tokenDecimals=data.get("tokenDecimals"), 1048 | syncStatus=SyncStatus.from_dict(**data.get("syncStatus")) 1049 | if data.get("syncStatus") is not None 1050 | else None, 1051 | ) 1052 | 1053 | 1054 | class GetTokenHoldersCountRequest: 1055 | def __init__( 1056 | self, 1057 | blockchain: Blockchain, 1058 | contractAddress: str, 1059 | pageToken: str = None, 1060 | pageSize: float = None, 1061 | syncCheck: bool = None, 1062 | ): 1063 | self.blockchain = blockchain 1064 | self.contractAddress = contractAddress 1065 | self.pageToken = pageToken 1066 | self.pageSize = pageSize 1067 | self.syncCheck = syncCheck 1068 | 1069 | def to_dict(self): 1070 | if isinstance(self.blockchain, str): 1071 | blockchain_value = self.blockchain 1072 | elif isinstance(self.blockchain, list): 1073 | blockchain_value = [ 1074 | block.value if isinstance(block, Blockchain) else block 1075 | for block in self.blockchain 1076 | ] 1077 | elif self.blockchain is not None: 1078 | blockchain_value = self.blockchain.value 1079 | else: 1080 | blockchain_value = None 1081 | return { 1082 | "blockchain": blockchain_value, 1083 | "contractAddress": self.contractAddress, 1084 | "pageToken": self.pageToken, 1085 | "pageSize": self.pageSize, 1086 | "syncCheck": self.syncCheck, 1087 | } 1088 | 1089 | 1090 | class CurrencyDetailsExtended: 1091 | def __init__( 1092 | self, 1093 | blockchain: Blockchain, 1094 | decimals: float, 1095 | name: str, 1096 | symbol: str, 1097 | thumbnail: str, 1098 | address: str = None, 1099 | ): 1100 | self.blockchain = blockchain 1101 | self.decimals = decimals 1102 | self.name = name 1103 | self.symbol = symbol 1104 | self.thumbnail = thumbnail 1105 | self.address = address 1106 | 1107 | @classmethod 1108 | def from_dict(cls, **data): 1109 | return cls( 1110 | blockchain=Blockchain(data.get("blockchain")), 1111 | decimals=data.get("decimals"), 1112 | name=data.get("name"), 1113 | symbol=data.get("symbol"), 1114 | thumbnail=data.get("thumbnail"), 1115 | address=data.get("address"), 1116 | ) 1117 | 1118 | 1119 | class GetCurrenciesReply: 1120 | def __init__( 1121 | self, currencies: List[CurrencyDetailsExtended], syncStatus: SyncStatus = None 1122 | ): 1123 | self.currencies = currencies 1124 | self.syncStatus = syncStatus 1125 | 1126 | @classmethod 1127 | def from_dict(cls, **data): 1128 | return cls( 1129 | currencies=[ 1130 | CurrencyDetailsExtended.from_dict(**currencydetailsextended_data) 1131 | for currencydetailsextended_data in data.get("currencies", []) 1132 | ], 1133 | syncStatus=SyncStatus.from_dict(**data.get("syncStatus")) 1134 | if data.get("syncStatus") is not None 1135 | else None, 1136 | ) 1137 | 1138 | 1139 | class GetCurrenciesRequest: 1140 | def __init__(self, blockchain: Blockchain, syncCheck: bool = None): 1141 | self.blockchain = blockchain 1142 | self.syncCheck = syncCheck 1143 | 1144 | def to_dict(self): 1145 | if isinstance(self.blockchain, str): 1146 | blockchain_value = self.blockchain 1147 | elif isinstance(self.blockchain, list): 1148 | blockchain_value = [ 1149 | block.value if isinstance(block, Blockchain) else block 1150 | for block in self.blockchain 1151 | ] 1152 | elif self.blockchain is not None: 1153 | blockchain_value = self.blockchain.value 1154 | else: 1155 | blockchain_value = None 1156 | return { 1157 | "blockchain": blockchain_value, 1158 | "syncCheck": self.syncCheck, 1159 | } 1160 | 1161 | 1162 | class TokenTransfer: 1163 | def __init__( 1164 | self, 1165 | blockHeight: float, 1166 | blockchain: str, 1167 | thumbnail: str, 1168 | timestamp: float, 1169 | tokenDecimals: float, 1170 | tokenName: str, 1171 | tokenSymbol: str, 1172 | transactionHash: str, 1173 | value: str, 1174 | valueRawInteger: str, 1175 | fromAddress: str = None, 1176 | contractAddress: str = None, 1177 | toAddress: str = None, 1178 | direction: str = None, 1179 | ): 1180 | self.blockHeight = blockHeight 1181 | self.blockchain = blockchain 1182 | self.thumbnail = thumbnail 1183 | self.timestamp = timestamp 1184 | self.tokenDecimals = tokenDecimals 1185 | self.tokenName = tokenName 1186 | self.tokenSymbol = tokenSymbol 1187 | self.transactionHash = transactionHash 1188 | self.value = value 1189 | self.valueRawInteger = valueRawInteger 1190 | self.fromAddress = fromAddress 1191 | self.contractAddress = contractAddress 1192 | self.toAddress = toAddress 1193 | self.direction = direction 1194 | 1195 | @classmethod 1196 | def from_dict(cls, **data): 1197 | return cls( 1198 | blockHeight=data.get("blockHeight"), 1199 | blockchain=data.get("blockchain"), 1200 | thumbnail=data.get("thumbnail"), 1201 | timestamp=data.get("timestamp"), 1202 | tokenDecimals=data.get("tokenDecimals"), 1203 | tokenName=data.get("tokenName"), 1204 | tokenSymbol=data.get("tokenSymbol"), 1205 | transactionHash=data.get("transactionHash"), 1206 | value=data.get("value"), 1207 | valueRawInteger=data.get("valueRawInteger"), 1208 | fromAddress=data.get("fromAddress"), 1209 | contractAddress=data.get("contractAddress"), 1210 | toAddress=data.get("toAddress"), 1211 | direction=data.get("direction"), 1212 | ) 1213 | 1214 | 1215 | class GetTokenTransfersReply: 1216 | def __init__( 1217 | self, 1218 | transfers: List[TokenTransfer], 1219 | nextPageToken: str = None, 1220 | syncStatus: SyncStatus = None, 1221 | ): 1222 | self.transfers = transfers 1223 | self.nextPageToken = nextPageToken 1224 | self.syncStatus = syncStatus 1225 | 1226 | @classmethod 1227 | def from_dict(cls, **data): 1228 | return cls( 1229 | transfers=[ 1230 | TokenTransfer.from_dict(**tokentransfer_data) 1231 | for tokentransfer_data in data.get("transfers", []) 1232 | ], 1233 | nextPageToken=data.get("nextPageToken"), 1234 | syncStatus=SyncStatus.from_dict(**data.get("syncStatus")) 1235 | if data.get("syncStatus") is not None 1236 | else None, 1237 | ) 1238 | 1239 | 1240 | class GetTransfersRequest: 1241 | def __init__( 1242 | self, 1243 | blockchain: Blockchain | List[Blockchain], 1244 | fromBlock: float 1245 | | Literal[Literal["latest"]] 1246 | | Literal[Literal["earliest"]] = None, 1247 | toBlock: float 1248 | | Literal[Literal["latest"]] 1249 | | Literal[Literal["earliest"]] = None, 1250 | fromTimestamp: float 1251 | | Literal[Literal["latest"]] 1252 | | Literal[Literal["earliest"]] = None, 1253 | toTimestamp: float 1254 | | Literal[Literal["latest"]] 1255 | | Literal[Literal["earliest"]] = None, 1256 | address: List[str] = None, 1257 | pageToken: str = None, 1258 | pageSize: float = None, 1259 | descOrder: bool = None, 1260 | syncCheck: bool = None, 1261 | ): 1262 | self.blockchain = blockchain 1263 | self.fromBlock = fromBlock 1264 | self.toBlock = toBlock 1265 | self.fromTimestamp = fromTimestamp 1266 | self.toTimestamp = toTimestamp 1267 | self.address = address 1268 | self.pageToken = pageToken 1269 | self.pageSize = pageSize 1270 | self.descOrder = descOrder 1271 | self.syncCheck = syncCheck 1272 | 1273 | def to_dict(self): 1274 | if isinstance(self.blockchain, str): 1275 | blockchain_value = self.blockchain 1276 | elif isinstance(self.blockchain, list): 1277 | blockchain_value = [ 1278 | block.value if isinstance(block, Blockchain) else block 1279 | for block in self.blockchain 1280 | ] 1281 | elif self.blockchain is not None: 1282 | blockchain_value = self.blockchain.value 1283 | else: 1284 | blockchain_value = None 1285 | return { 1286 | "blockchain": blockchain_value, 1287 | "fromBlock": self.fromBlock, 1288 | "toBlock": self.toBlock, 1289 | "fromTimestamp": self.fromTimestamp, 1290 | "toTimestamp": self.toTimestamp, 1291 | "address": self.address, 1292 | "pageToken": self.pageToken, 1293 | "pageSize": self.pageSize, 1294 | "descOrder": self.descOrder, 1295 | "syncCheck": self.syncCheck, 1296 | } 1297 | 1298 | 1299 | class Trait: 1300 | def __init__(self, trait_type: str, value: str): 1301 | self.trait_type = trait_type 1302 | self.value = value 1303 | 1304 | @classmethod 1305 | def from_dict(cls, **data): 1306 | return cls( 1307 | trait_type=data.get("trait_type"), 1308 | value=data.get("value"), 1309 | ) 1310 | 1311 | 1312 | class Nft: 1313 | def __init__( 1314 | self, 1315 | blockchain: Blockchain, 1316 | collectionName: str, 1317 | contractAddress: str, 1318 | contractType: Literal[ 1319 | Literal["ERC721"], Literal["ERC1155"], Literal["UNDEFINED"] 1320 | ], 1321 | imageUrl: str, 1322 | name: str, 1323 | symbol: str, 1324 | tokenId: str, 1325 | tokenUrl: str, 1326 | quantity: str = None, 1327 | traits: List[Trait] = None, 1328 | ): 1329 | self.blockchain = blockchain 1330 | self.collectionName = collectionName 1331 | self.contractAddress = contractAddress 1332 | self.contractType = contractType 1333 | self.imageUrl = imageUrl 1334 | self.name = name 1335 | self.symbol = symbol 1336 | self.tokenId = tokenId 1337 | self.tokenUrl = tokenUrl 1338 | self.quantity = quantity 1339 | self.traits = traits 1340 | 1341 | @classmethod 1342 | def from_dict(cls, **data): 1343 | return cls( 1344 | blockchain=Blockchain(data.get("blockchain")), 1345 | collectionName=data.get("collectionName"), 1346 | contractAddress=data.get("contractAddress"), 1347 | contractType=data.get("contractType"), 1348 | imageUrl=data.get("imageUrl"), 1349 | name=data.get("name"), 1350 | symbol=data.get("symbol"), 1351 | tokenId=data.get("tokenId"), 1352 | tokenUrl=data.get("tokenUrl"), 1353 | quantity=data.get("quantity"), 1354 | traits=[ 1355 | Trait.from_dict(**trait_data) for trait_data in data.get("traits", []) 1356 | ], 1357 | ) 1358 | 1359 | 1360 | class GetNFTsByOwnerReply: 1361 | def __init__( 1362 | self, 1363 | assets: List[Nft], 1364 | nextPageToken: str, 1365 | owner: str, 1366 | syncStatus: SyncStatus = None, 1367 | ): 1368 | self.assets = assets 1369 | self.nextPageToken = nextPageToken 1370 | self.owner = owner 1371 | self.syncStatus = syncStatus 1372 | 1373 | @classmethod 1374 | def from_dict(cls, **data): 1375 | return cls( 1376 | assets=[Nft.from_dict(**nft_data) for nft_data in data.get("assets", [])], 1377 | nextPageToken=data.get("nextPageToken"), 1378 | owner=data.get("owner"), 1379 | syncStatus=SyncStatus.from_dict(**data.get("syncStatus")) 1380 | if data.get("syncStatus") is not None 1381 | else None, 1382 | ) 1383 | 1384 | 1385 | class GetNFTsByOwnerRequest: 1386 | def __init__( 1387 | self, 1388 | walletAddress: str, 1389 | blockchain: Blockchain | List[Blockchain] = None, 1390 | filter: List[Dict[str, List[str]]] = None, 1391 | pageToken: str = None, 1392 | pageSize: float = None, 1393 | syncCheck: bool = None, 1394 | ): 1395 | self.walletAddress = walletAddress 1396 | self.blockchain = blockchain 1397 | self.filter = filter 1398 | self.pageToken = pageToken 1399 | self.pageSize = pageSize 1400 | self.syncCheck = syncCheck 1401 | 1402 | def to_dict(self): 1403 | if isinstance(self.blockchain, str): 1404 | blockchain_value = self.blockchain 1405 | elif isinstance(self.blockchain, list): 1406 | blockchain_value = [ 1407 | block.value if isinstance(block, Blockchain) else block 1408 | for block in self.blockchain 1409 | ] 1410 | elif self.blockchain is not None: 1411 | blockchain_value = self.blockchain.value 1412 | else: 1413 | blockchain_value = None 1414 | return { 1415 | "walletAddress": self.walletAddress, 1416 | "blockchain": blockchain_value, 1417 | "filter": self.filter, 1418 | "pageToken": self.pageToken, 1419 | "pageSize": self.pageSize, 1420 | "syncCheck": self.syncCheck, 1421 | } 1422 | 1423 | 1424 | class NftAttributes: 1425 | def __init__( 1426 | self, 1427 | contractType: Literal[ 1428 | Literal["ERC721"], Literal["ERC1155"], Literal["UNDEFINED"] 1429 | ], 1430 | description: str, 1431 | imageUrl: str, 1432 | name: str, 1433 | tokenUrl: str, 1434 | traits: List[Trait] = None, 1435 | ): 1436 | self.contractType = contractType 1437 | self.description = description 1438 | self.imageUrl = imageUrl 1439 | self.name = name 1440 | self.tokenUrl = tokenUrl 1441 | self.traits = traits 1442 | 1443 | @classmethod 1444 | def from_dict(cls, **data): 1445 | return cls( 1446 | contractType=data.get("contractType"), 1447 | description=data.get("description"), 1448 | imageUrl=data.get("imageUrl"), 1449 | name=data.get("name"), 1450 | tokenUrl=data.get("tokenUrl"), 1451 | traits=[ 1452 | Trait.from_dict(**trait_data) for trait_data in data.get("traits", []) 1453 | ], 1454 | ) 1455 | 1456 | 1457 | class NftMetadata: 1458 | def __init__( 1459 | self, 1460 | blockchain: Blockchain, 1461 | collectionName: str, 1462 | collectionSymbol: str, 1463 | contractAddress: str, 1464 | contractType: Literal[ 1465 | Literal["ERC721"], Literal["ERC1155"], Literal["UNDEFINED"] 1466 | ], 1467 | tokenId: str, 1468 | ): 1469 | self.blockchain = blockchain 1470 | self.collectionName = collectionName 1471 | self.collectionSymbol = collectionSymbol 1472 | self.contractAddress = contractAddress 1473 | self.contractType = contractType 1474 | self.tokenId = tokenId 1475 | 1476 | @classmethod 1477 | def from_dict(cls, **data): 1478 | return cls( 1479 | blockchain=Blockchain(data.get("blockchain")), 1480 | collectionName=data.get("collectionName"), 1481 | collectionSymbol=data.get("collectionSymbol"), 1482 | contractAddress=data.get("contractAddress"), 1483 | contractType=data.get("contractType"), 1484 | tokenId=data.get("tokenId"), 1485 | ) 1486 | 1487 | 1488 | class GetNFTMetadataReply: 1489 | def __init__( 1490 | self, 1491 | metadata: NftMetadata = None, 1492 | attributes: NftAttributes = None, 1493 | syncStatus: SyncStatus = None, 1494 | ): 1495 | self.metadata = metadata 1496 | self.attributes = attributes 1497 | self.syncStatus = syncStatus 1498 | 1499 | @classmethod 1500 | def from_dict(cls, **data): 1501 | return cls( 1502 | metadata=NftMetadata.from_dict(**data.get("metadata")) 1503 | if data.get("metadata") is not None 1504 | else None, 1505 | attributes=NftAttributes.from_dict(**data.get("attributes")) 1506 | if data.get("attributes") is not None 1507 | else None, 1508 | syncStatus=SyncStatus.from_dict(**data.get("syncStatus")) 1509 | if data.get("syncStatus") is not None 1510 | else None, 1511 | ) 1512 | 1513 | 1514 | class GetNFTMetadataRequest: 1515 | def __init__( 1516 | self, 1517 | blockchain: Blockchain, 1518 | contractAddress: str, 1519 | forceFetch: bool, 1520 | tokenId: str, 1521 | syncCheck: bool = None, 1522 | ): 1523 | self.blockchain = blockchain 1524 | self.contractAddress = contractAddress 1525 | self.forceFetch = forceFetch 1526 | self.tokenId = tokenId 1527 | self.syncCheck = syncCheck 1528 | 1529 | def to_dict(self): 1530 | if isinstance(self.blockchain, str): 1531 | blockchain_value = self.blockchain 1532 | elif isinstance(self.blockchain, list): 1533 | blockchain_value = [ 1534 | block.value if isinstance(block, Blockchain) else block 1535 | for block in self.blockchain 1536 | ] 1537 | elif self.blockchain is not None: 1538 | blockchain_value = self.blockchain.value 1539 | else: 1540 | blockchain_value = None 1541 | return { 1542 | "blockchain": blockchain_value, 1543 | "contractAddress": self.contractAddress, 1544 | "forceFetch": self.forceFetch, 1545 | "tokenId": self.tokenId, 1546 | "syncCheck": self.syncCheck, 1547 | } 1548 | 1549 | 1550 | class GetNFTHoldersReply: 1551 | def __init__( 1552 | self, holders: List[str], nextPageToken: str, syncStatus: SyncStatus = None 1553 | ): 1554 | self.holders = holders 1555 | self.nextPageToken = nextPageToken 1556 | self.syncStatus = syncStatus 1557 | 1558 | @classmethod 1559 | def from_dict(cls, **data): 1560 | return cls( 1561 | holders=data.get("holders"), 1562 | nextPageToken=data.get("nextPageToken"), 1563 | syncStatus=SyncStatus.from_dict(**data.get("syncStatus")) 1564 | if data.get("syncStatus") is not None 1565 | else None, 1566 | ) 1567 | 1568 | 1569 | class GetNFTHoldersRequest: 1570 | def __init__( 1571 | self, 1572 | blockchain: Blockchain, 1573 | contractAddress: str, 1574 | pageToken: str = None, 1575 | pageSize: float = None, 1576 | syncCheck: bool = None, 1577 | ): 1578 | self.blockchain = blockchain 1579 | self.contractAddress = contractAddress 1580 | self.pageToken = pageToken 1581 | self.pageSize = pageSize 1582 | self.syncCheck = syncCheck 1583 | 1584 | def to_dict(self): 1585 | if isinstance(self.blockchain, str): 1586 | blockchain_value = self.blockchain 1587 | elif isinstance(self.blockchain, list): 1588 | blockchain_value = [ 1589 | block.value if isinstance(block, Blockchain) else block 1590 | for block in self.blockchain 1591 | ] 1592 | elif self.blockchain is not None: 1593 | blockchain_value = self.blockchain.value 1594 | else: 1595 | blockchain_value = None 1596 | return { 1597 | "blockchain": blockchain_value, 1598 | "contractAddress": self.contractAddress, 1599 | "pageToken": self.pageToken, 1600 | "pageSize": self.pageSize, 1601 | "syncCheck": self.syncCheck, 1602 | } 1603 | 1604 | 1605 | class NftTransfer: 1606 | def __init__( 1607 | self, 1608 | blockHeight: float, 1609 | blockchain: Blockchain, 1610 | collectionName: str, 1611 | collectionSymbol: str, 1612 | fromAddress: str, 1613 | imageUrl: str, 1614 | name: str, 1615 | timestamp: float, 1616 | toAddress: str, 1617 | transactionHash: str, 1618 | type: Literal[Literal["ERC721"], Literal["ERC1155"], Literal["UNDEFINED"]], 1619 | value: str, 1620 | tokenId: str = None, 1621 | contractAddress: str = None, 1622 | ): 1623 | self.blockHeight = blockHeight 1624 | self.blockchain = blockchain 1625 | self.collectionName = collectionName 1626 | self.collectionSymbol = collectionSymbol 1627 | self.fromAddress = fromAddress 1628 | self.imageUrl = imageUrl 1629 | self.name = name 1630 | self.timestamp = timestamp 1631 | self.toAddress = toAddress 1632 | self.transactionHash = transactionHash 1633 | self.type = type 1634 | self.value = value 1635 | self.tokenId = tokenId 1636 | self.contractAddress = contractAddress 1637 | 1638 | @classmethod 1639 | def from_dict(cls, **data): 1640 | return cls( 1641 | blockHeight=data.get("blockHeight"), 1642 | blockchain=Blockchain(data.get("blockchain")), 1643 | collectionName=data.get("collectionName"), 1644 | collectionSymbol=data.get("collectionSymbol"), 1645 | fromAddress=data.get("fromAddress"), 1646 | imageUrl=data.get("imageUrl"), 1647 | name=data.get("name"), 1648 | timestamp=data.get("timestamp"), 1649 | toAddress=data.get("toAddress"), 1650 | transactionHash=data.get("transactionHash"), 1651 | type=data.get("type"), 1652 | value=data.get("value"), 1653 | tokenId=data.get("tokenId"), 1654 | contractAddress=data.get("contractAddress"), 1655 | ) 1656 | 1657 | 1658 | class GetNftTransfersReply: 1659 | def __init__( 1660 | self, 1661 | transfers: List[NftTransfer], 1662 | nextPageToken: str = None, 1663 | syncStatus: SyncStatus = None, 1664 | ): 1665 | self.transfers = transfers 1666 | self.nextPageToken = nextPageToken 1667 | self.syncStatus = syncStatus 1668 | 1669 | @classmethod 1670 | def from_dict(cls, **data): 1671 | return cls( 1672 | transfers=[ 1673 | NftTransfer.from_dict(**nfttransfer_data) 1674 | for nfttransfer_data in data.get("transfers", []) 1675 | ], 1676 | nextPageToken=data.get("nextPageToken"), 1677 | syncStatus=SyncStatus.from_dict(**data.get("syncStatus")) 1678 | if data.get("syncStatus") is not None 1679 | else None, 1680 | ) 1681 | 1682 | 1683 | class GetTokenAllowancesRequest: 1684 | def __init__( 1685 | self, 1686 | blockchain: Blockchain | List[Blockchain], 1687 | walletAddress: str, 1688 | spenderAddress: str = None, 1689 | contractAddress: str = None, 1690 | ): 1691 | self.blockchain = blockchain 1692 | self.walletAddress = walletAddress 1693 | self.spenderAddress = spenderAddress 1694 | self.contractAddress = contractAddress 1695 | 1696 | def to_dict(self): 1697 | if isinstance(self.blockchain, str): 1698 | blockchain_value = self.blockchain 1699 | elif isinstance(self.blockchain, list): 1700 | blockchain_value = [ 1701 | block.value if isinstance(block, Blockchain) else block 1702 | for block in self.blockchain 1703 | ] 1704 | elif self.blockchain is not None: 1705 | blockchain_value = self.blockchain.value 1706 | else: 1707 | blockchain_value = None 1708 | return { 1709 | "blockchain": blockchain_value, 1710 | "walletAddress": self.walletAddress, 1711 | "spenderAddress": self.spenderAddress, 1712 | "contractAddress": self.contractAddress, 1713 | } 1714 | 1715 | 1716 | class ERC20TokenAllowance: 1717 | def __init__( 1718 | self, 1719 | blockHeight: float, 1720 | thumbnail: str, 1721 | timestamp: float, 1722 | value: str = None, 1723 | tokenDecimals: float = None, 1724 | walletAddress: str = None, 1725 | contractAddress: str = None, 1726 | transactionHash: str = None, 1727 | blockchain: str = None, 1728 | tokenName: str = None, 1729 | tokenSymbol: str = None, 1730 | spenderAddress: str = None, 1731 | rawLog: Log = None, 1732 | ): 1733 | self.blockHeight = blockHeight 1734 | self.thumbnail = thumbnail 1735 | self.timestamp = timestamp 1736 | self.value = value 1737 | self.tokenDecimals = tokenDecimals 1738 | self.walletAddress = walletAddress 1739 | self.contractAddress = contractAddress 1740 | self.transactionHash = transactionHash 1741 | self.blockchain = blockchain 1742 | self.tokenName = tokenName 1743 | self.tokenSymbol = tokenSymbol 1744 | self.spenderAddress = spenderAddress 1745 | self.rawLog = rawLog 1746 | 1747 | @classmethod 1748 | def from_dict(cls, **data): 1749 | return cls( 1750 | blockHeight=data.get("blockHeight"), 1751 | thumbnail=data.get("thumbnail"), 1752 | timestamp=data.get("timestamp"), 1753 | value=data.get("value"), 1754 | tokenDecimals=data.get("tokenDecimals"), 1755 | walletAddress=data.get("walletAddress"), 1756 | contractAddress=data.get("contractAddress"), 1757 | transactionHash=data.get("transactionHash"), 1758 | blockchain=data.get("blockchain"), 1759 | tokenName=data.get("tokenName"), 1760 | tokenSymbol=data.get("tokenSymbol"), 1761 | spenderAddress=data.get("spenderAddress"), 1762 | rawLog=Log.from_dict(**data.get("rawLog")) 1763 | if data.get("rawLog") is not None 1764 | else None, 1765 | ) 1766 | 1767 | 1768 | class GetTokenAllowancesReply: 1769 | def __init__(self, allowances: List[ERC20TokenAllowance]): 1770 | self.allowances = allowances 1771 | 1772 | @classmethod 1773 | def from_dict(cls, **data): 1774 | return cls( 1775 | allowances=[ 1776 | ERC20TokenAllowance.from_dict(**erc20tokenallowance_data) 1777 | for erc20tokenallowance_data in data.get("allowances", []) 1778 | ], 1779 | ) 1780 | 1781 | 1782 | class GetTokenPriceHistoryRequest: 1783 | def __init__( 1784 | self, 1785 | blockchain: Blockchain, 1786 | contractAddress: str, 1787 | fromTimestamp: float 1788 | | Literal[Literal["latest"]] 1789 | | Literal[Literal["earliest"]] = None, 1790 | toTimestamp: float 1791 | | Literal[Literal["latest"]] 1792 | | Literal[Literal["earliest"]] = None, 1793 | interval: float = None, 1794 | limit: float = None, 1795 | syncCheck: bool = None, 1796 | ): 1797 | self.blockchain = blockchain 1798 | self.contractAddress = contractAddress 1799 | self.fromTimestamp = fromTimestamp 1800 | self.toTimestamp = toTimestamp 1801 | self.interval = interval 1802 | self.limit = limit 1803 | self.syncCheck = syncCheck 1804 | 1805 | def to_dict(self): 1806 | if isinstance(self.blockchain, str): 1807 | blockchain_value = self.blockchain 1808 | elif isinstance(self.blockchain, list): 1809 | blockchain_value = [ 1810 | block.value if isinstance(block, Blockchain) else block 1811 | for block in self.blockchain 1812 | ] 1813 | elif self.blockchain is not None: 1814 | blockchain_value = self.blockchain.value 1815 | else: 1816 | blockchain_value = None 1817 | return { 1818 | "blockchain": blockchain_value, 1819 | "contractAddress": self.contractAddress, 1820 | "fromTimestamp": self.fromTimestamp, 1821 | "toTimestamp": self.toTimestamp, 1822 | "interval": self.interval, 1823 | "limit": self.limit, 1824 | "syncCheck": self.syncCheck, 1825 | } 1826 | 1827 | 1828 | class Quote: 1829 | def __init__(self, blockHeight: float, timestamp: float, usdPrice: str): 1830 | self.blockHeight = blockHeight 1831 | self.timestamp = timestamp 1832 | self.usdPrice = usdPrice 1833 | 1834 | @classmethod 1835 | def from_dict(cls, **data): 1836 | return cls( 1837 | blockHeight=data.get("blockHeight"), 1838 | timestamp=data.get("timestamp"), 1839 | usdPrice=data.get("usdPrice"), 1840 | ) 1841 | 1842 | 1843 | class GetTokenPriceHistoryReply: 1844 | def __init__(self, quotes: List[Quote], syncStatus: SyncStatus = None): 1845 | self.quotes = quotes 1846 | self.syncStatus = syncStatus 1847 | 1848 | @classmethod 1849 | def from_dict(cls, **data): 1850 | return cls( 1851 | quotes=[ 1852 | Quote.from_dict(**quote_data) for quote_data in data.get("quotes", []) 1853 | ], 1854 | syncStatus=SyncStatus.from_dict(**data.get("syncStatus")) 1855 | if data.get("syncStatus") is not None 1856 | else None, 1857 | ) 1858 | 1859 | 1860 | class ExplainTokenPriceRequest: 1861 | def __init__( 1862 | self, 1863 | blockHeight: float | Literal[Literal["latest"]] | Literal[Literal["earliest"]], 1864 | blockchain: Blockchain, 1865 | tokenAddress: str, 1866 | ): 1867 | self.blockHeight = blockHeight 1868 | self.blockchain = blockchain 1869 | self.tokenAddress = tokenAddress 1870 | 1871 | def to_dict(self): 1872 | if isinstance(self.blockchain, str): 1873 | blockchain_value = self.blockchain 1874 | elif isinstance(self.blockchain, list): 1875 | blockchain_value = [ 1876 | block.value if isinstance(block, Blockchain) else block 1877 | for block in self.blockchain 1878 | ] 1879 | elif self.blockchain is not None: 1880 | blockchain_value = self.blockchain.value 1881 | else: 1882 | blockchain_value = None 1883 | return { 1884 | "blockHeight": self.blockHeight, 1885 | "blockchain": blockchain_value, 1886 | "tokenAddress": self.tokenAddress, 1887 | } 1888 | 1889 | 1890 | class PriceEstimate: 1891 | def __init__(self, price: str, strategy: str): 1892 | self.price = price 1893 | self.strategy = strategy 1894 | 1895 | @classmethod 1896 | def from_dict(cls, **data): 1897 | return cls( 1898 | price=data.get("price"), 1899 | strategy=data.get("strategy"), 1900 | ) 1901 | 1902 | 1903 | class ExplainTokenPriceLPDetails: 1904 | def __init__( 1905 | self, 1906 | address: str, 1907 | lastUpdatedBlock: float, 1908 | price: str, 1909 | reserve0: str, 1910 | reserve1: str, 1911 | token0: str, 1912 | token1: str, 1913 | ): 1914 | self.address = address 1915 | self.lastUpdatedBlock = lastUpdatedBlock 1916 | self.price = price 1917 | self.reserve0 = reserve0 1918 | self.reserve1 = reserve1 1919 | self.token0 = token0 1920 | self.token1 = token1 1921 | 1922 | @classmethod 1923 | def from_dict(cls, **data): 1924 | return cls( 1925 | address=data.get("address"), 1926 | lastUpdatedBlock=data.get("lastUpdatedBlock"), 1927 | price=data.get("price"), 1928 | reserve0=data.get("reserve0"), 1929 | reserve1=data.get("reserve1"), 1930 | token0=data.get("token0"), 1931 | token1=data.get("token1"), 1932 | ) 1933 | 1934 | 1935 | class ExplainTokenPriceTokenDetails: 1936 | def __init__(self, contractAddress: str, decimals: float, name: str, symbol: str): 1937 | self.contractAddress = contractAddress 1938 | self.decimals = decimals 1939 | self.name = name 1940 | self.symbol = symbol 1941 | 1942 | @classmethod 1943 | def from_dict(cls, **data): 1944 | return cls( 1945 | contractAddress=data.get("contractAddress"), 1946 | decimals=data.get("decimals"), 1947 | name=data.get("name"), 1948 | symbol=data.get("symbol"), 1949 | ) 1950 | 1951 | 1952 | class ExplainTokenPriceSinglePair: 1953 | def __init__( 1954 | self, 1955 | liquidity_pools: List[ExplainTokenPriceLPDetails], 1956 | priceEstimates: List[PriceEstimate], 1957 | token0: ExplainTokenPriceTokenDetails, 1958 | token1: ExplainTokenPriceTokenDetails, 1959 | ): 1960 | self.liquidity_pools = liquidity_pools 1961 | self.priceEstimates = priceEstimates 1962 | self.token0 = token0 1963 | self.token1 = token1 1964 | 1965 | @classmethod 1966 | def from_dict(cls, **data): 1967 | return cls( 1968 | liquidity_pools=[ 1969 | ExplainTokenPriceLPDetails.from_dict(**explaintokenpricelpdetails_data) 1970 | for explaintokenpricelpdetails_data in data.get("liquidity_pools", []) 1971 | ], 1972 | priceEstimates=[ 1973 | PriceEstimate.from_dict(**priceestimate_data) 1974 | for priceestimate_data in data.get("priceEstimates", []) 1975 | ], 1976 | token0=ExplainTokenPriceTokenDetails.from_dict(**data.get("token0")) 1977 | if data.get("token0") is not None 1978 | else None, 1979 | token1=ExplainTokenPriceTokenDetails.from_dict(**data.get("token1")) 1980 | if data.get("token1") is not None 1981 | else None, 1982 | ) 1983 | 1984 | 1985 | class ExplainTokenPriceReply: 1986 | def __init__( 1987 | self, 1988 | blockchain: str, 1989 | pairs: List[ExplainTokenPriceSinglePair], 1990 | priceEstimates: List[PriceEstimate], 1991 | tokenAddress: str, 1992 | ): 1993 | self.blockchain = blockchain 1994 | self.pairs = pairs 1995 | self.priceEstimates = priceEstimates 1996 | self.tokenAddress = tokenAddress 1997 | 1998 | @classmethod 1999 | def from_dict(cls, **data): 2000 | return cls( 2001 | blockchain=data.get("blockchain"), 2002 | pairs=[ 2003 | ExplainTokenPriceSinglePair.from_dict( 2004 | **explaintokenpricesinglepair_data 2005 | ) 2006 | for explaintokenpricesinglepair_data in data.get("pairs", []) 2007 | ], 2008 | priceEstimates=[ 2009 | PriceEstimate.from_dict(**priceestimate_data) 2010 | for priceestimate_data in data.get("priceEstimates", []) 2011 | ], 2012 | tokenAddress=data.get("tokenAddress"), 2013 | ) 2014 | 2015 | 2016 | class GetInternalTransactionsByParentHashRequest: 2017 | def __init__( 2018 | self, 2019 | blockchain: Blockchain, 2020 | onlyWithValue: bool, 2021 | parentTransactionHash: str, 2022 | syncCheck: bool = None, 2023 | ): 2024 | self.blockchain = blockchain 2025 | self.onlyWithValue = onlyWithValue 2026 | self.parentTransactionHash = parentTransactionHash 2027 | self.syncCheck = syncCheck 2028 | 2029 | def to_dict(self): 2030 | if isinstance(self.blockchain, str): 2031 | blockchain_value = self.blockchain 2032 | elif isinstance(self.blockchain, list): 2033 | blockchain_value = [ 2034 | block.value if isinstance(block, Blockchain) else block 2035 | for block in self.blockchain 2036 | ] 2037 | elif self.blockchain is not None: 2038 | blockchain_value = self.blockchain.value 2039 | else: 2040 | blockchain_value = None 2041 | return { 2042 | "blockchain": blockchain_value, 2043 | "onlyWithValue": self.onlyWithValue, 2044 | "parentTransactionHash": self.parentTransactionHash, 2045 | "syncCheck": self.syncCheck, 2046 | } 2047 | 2048 | 2049 | class GetInternalTransactionsByBlockNumberRequest: 2050 | def __init__( 2051 | self, 2052 | blockNumber: float, 2053 | blockchain: Blockchain, 2054 | onlyWithValue: bool, 2055 | syncCheck: bool = None, 2056 | ): 2057 | self.blockNumber = blockNumber 2058 | self.blockchain = blockchain 2059 | self.onlyWithValue = onlyWithValue 2060 | self.syncCheck = syncCheck 2061 | 2062 | def to_dict(self): 2063 | if isinstance(self.blockchain, str): 2064 | blockchain_value = self.blockchain 2065 | elif isinstance(self.blockchain, list): 2066 | blockchain_value = [ 2067 | block.value if isinstance(block, Blockchain) else block 2068 | for block in self.blockchain 2069 | ] 2070 | elif self.blockchain is not None: 2071 | blockchain_value = self.blockchain.value 2072 | else: 2073 | blockchain_value = None 2074 | return { 2075 | "blockNumber": self.blockNumber, 2076 | "blockchain": blockchain_value, 2077 | "onlyWithValue": self.onlyWithValue, 2078 | "syncCheck": self.syncCheck, 2079 | } 2080 | 2081 | 2082 | class InternalTransaction: 2083 | def __init__( 2084 | self, 2085 | blockHash: str, 2086 | blockHeight: float, 2087 | blockchain: Blockchain, 2088 | callType: str, 2089 | fromAddress: str, 2090 | gas: float, 2091 | gasUsed: float, 2092 | input: str, 2093 | output: str, 2094 | timestamp: str, 2095 | toAddress: str, 2096 | transactionHash: str, 2097 | transactionIndex: float, 2098 | value: str, 2099 | callPath: str = None, 2100 | callStack: List[float] = None, 2101 | error: str = None, 2102 | contractAddress: str = None, 2103 | ): 2104 | self.blockHash = blockHash 2105 | self.blockHeight = blockHeight 2106 | self.blockchain = blockchain 2107 | self.callType = callType 2108 | self.fromAddress = fromAddress 2109 | self.gas = gas 2110 | self.gasUsed = gasUsed 2111 | self.input = input 2112 | self.output = output 2113 | self.timestamp = timestamp 2114 | self.toAddress = toAddress 2115 | self.transactionHash = transactionHash 2116 | self.transactionIndex = transactionIndex 2117 | self.value = value 2118 | self.callPath = callPath 2119 | self.callStack = callStack 2120 | self.error = error 2121 | self.contractAddress = contractAddress 2122 | 2123 | @classmethod 2124 | def from_dict(cls, **data): 2125 | return cls( 2126 | blockHash=data.get("blockHash"), 2127 | blockHeight=data.get("blockHeight"), 2128 | blockchain=Blockchain(data.get("blockchain")), 2129 | callType=data.get("callType"), 2130 | fromAddress=data.get("fromAddress"), 2131 | gas=data.get("gas"), 2132 | gasUsed=data.get("gasUsed"), 2133 | input=data.get("input"), 2134 | output=data.get("output"), 2135 | timestamp=data.get("timestamp"), 2136 | toAddress=data.get("toAddress"), 2137 | transactionHash=data.get("transactionHash"), 2138 | transactionIndex=data.get("transactionIndex"), 2139 | value=data.get("value"), 2140 | callPath=data.get("callPath"), 2141 | callStack=data.get("callStack"), 2142 | error=data.get("error"), 2143 | contractAddress=data.get("contractAddress"), 2144 | ) 2145 | 2146 | 2147 | class GetInternalTransactionsReply: 2148 | def __init__( 2149 | self, internalTransactions: List[InternalTransaction], nextPageToken: str = None 2150 | ): 2151 | self.internalTransactions = internalTransactions 2152 | self.nextPageToken = nextPageToken 2153 | 2154 | @classmethod 2155 | def from_dict(cls, **data): 2156 | return cls( 2157 | internalTransactions=[ 2158 | InternalTransaction.from_dict(**internaltransaction_data) 2159 | for internaltransaction_data in data.get("internalTransactions", []) 2160 | ], 2161 | nextPageToken=data.get("nextPageToken"), 2162 | ) 2163 | 2164 | 2165 | class GetAccountBalanceHistoricalRequest: 2166 | def __init__( 2167 | self, 2168 | walletAddress: str, 2169 | blockchain: Blockchain | List[Blockchain] = None, 2170 | onlyWhitelisted: bool = None, 2171 | nativeFirst: bool = None, 2172 | pageToken: str = None, 2173 | pageSize: float = None, 2174 | blockHeight: float 2175 | | Literal[Literal["latest"]] 2176 | | Literal[Literal["earliest"]] = None, 2177 | syncCheck: bool = None, 2178 | ): 2179 | self.walletAddress = walletAddress 2180 | self.blockchain = blockchain 2181 | self.onlyWhitelisted = onlyWhitelisted 2182 | self.nativeFirst = nativeFirst 2183 | self.pageToken = pageToken 2184 | self.pageSize = pageSize 2185 | self.blockHeight = blockHeight 2186 | self.syncCheck = syncCheck 2187 | 2188 | def to_dict(self): 2189 | if isinstance(self.blockchain, str): 2190 | blockchain_value = self.blockchain 2191 | elif isinstance(self.blockchain, list): 2192 | blockchain_value = [ 2193 | block.value if isinstance(block, Blockchain) else block 2194 | for block in self.blockchain 2195 | ] 2196 | elif self.blockchain is not None: 2197 | blockchain_value = self.blockchain.value 2198 | else: 2199 | blockchain_value = None 2200 | return { 2201 | "walletAddress": self.walletAddress, 2202 | "blockchain": blockchain_value, 2203 | "onlyWhitelisted": self.onlyWhitelisted, 2204 | "nativeFirst": self.nativeFirst, 2205 | "pageToken": self.pageToken, 2206 | "pageSize": self.pageSize, 2207 | "blockHeight": self.blockHeight, 2208 | "syncCheck": self.syncCheck, 2209 | } 2210 | 2211 | 2212 | class GetAccountBalanceHistoricalReply: 2213 | def __init__( 2214 | self, 2215 | assets: List[Balance], 2216 | totalBalanceUsd: str, 2217 | totalCount: float, 2218 | nextPageToken: str = None, 2219 | syncStatus: SyncStatus = None, 2220 | blockHeight: float 2221 | | Literal[Literal["latest"]] 2222 | | Literal[Literal["earliest"]] = None, 2223 | ): 2224 | self.assets = assets 2225 | self.totalBalanceUsd = totalBalanceUsd 2226 | self.totalCount = totalCount 2227 | self.nextPageToken = nextPageToken 2228 | self.syncStatus = syncStatus 2229 | self.blockHeight = blockHeight 2230 | 2231 | @classmethod 2232 | def from_dict(cls, **data): 2233 | return cls( 2234 | assets=[ 2235 | Balance.from_dict(**balance_data) 2236 | for balance_data in data.get("assets", []) 2237 | ], 2238 | totalBalanceUsd=data.get("totalBalanceUsd"), 2239 | totalCount=data.get("totalCount"), 2240 | nextPageToken=data.get("nextPageToken"), 2241 | syncStatus=SyncStatus.from_dict(**data.get("syncStatus")) 2242 | if data.get("syncStatus") is not None 2243 | else None, 2244 | blockHeight=data.get("blockHeight"), 2245 | ) 2246 | 2247 | 2248 | class Blockchain(Enum): 2249 | Arbitrum = "arbitrum" 2250 | Avalanche = "avalanche" 2251 | Avalanche_fuji = "avalanche_fuji" 2252 | Base = "base" 2253 | Base_sepolia = "base_sepolia" 2254 | Bsc = "bsc" 2255 | Eth = "eth" 2256 | Eth_holesky = "eth_holesky" 2257 | Eth_sepolia = "eth_sepolia" 2258 | Fantom = "fantom" 2259 | Flare = "flare" 2260 | Gnosis = "gnosis" 2261 | Incentiv_devnet = "incentiv_devnet" 2262 | Linea = "linea" 2263 | Neura_devnet = "neura_devnet" 2264 | Neura_testnet_v1 = "neura_testnet_v1" 2265 | Optimism = "optimism" 2266 | Optimism_testnet = "optimism_testnet" 2267 | Polygon = "polygon" 2268 | Polygon_amoy = "polygon_amoy" 2269 | Polygon_zkevm = "polygon_zkevm" 2270 | Rollux = "rollux" 2271 | Scroll = "scroll" 2272 | Syscoin = "syscoin" 2273 | Telos = "telos" 2274 | Xai = "xai" 2275 | Xlayer = "xlayer" 2276 | -------------------------------------------------------------------------------- /ankr/web3.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from typing import Any, Dict, Optional, Sequence, Type, Union, cast 4 | 5 | from ens import ENS 6 | from web3 import Web3 7 | from web3._utils.empty import empty 8 | from web3.eth import Eth 9 | from web3.middleware import geth_poa_middleware 10 | from web3.module import Module 11 | 12 | from ankr.advanced_apis import ( 13 | AnkrNFTAPI, 14 | AnkrQueryAPI, 15 | AnkrTokenAPI, 16 | AnkrEarlyAccessAPI, 17 | ) 18 | from ankr.providers import ( 19 | ArbitrumHTTPProvider, 20 | AvalancheHTTPProvider, 21 | BscHTTPProvider, 22 | CeloHTTPProvider, 23 | EthHTTPProvider, 24 | FantomHTTPProvider, 25 | GnosisHTTPProvider, 26 | HarmonyHTTPProvider, 27 | IotexHTTPProvider, 28 | MoonbeamHTTPProvider, 29 | NervosHTTPProvider, 30 | OptimismHTTPProvider, 31 | PolygonHTTPProvider, 32 | SyscoinHTTPProvider, 33 | TProviderConstructor, 34 | ) 35 | 36 | 37 | class _Web3NamedMeta(type): 38 | def __new__(metacls, name, bases, namespace, **kw): # type: ignore 39 | return super().__new__(metacls, "Web3", bases, namespace, **kw) 40 | 41 | 42 | class AnkrWeb3(Web3, metaclass=_Web3NamedMeta): 43 | query: AnkrQueryAPI 44 | token: AnkrTokenAPI 45 | nft: AnkrNFTAPI 46 | 47 | eth: Eth 48 | arbitrum: Eth 49 | avalanche: Eth 50 | bsc: Eth 51 | celo: Eth 52 | fantom: Eth 53 | gnosis: Eth 54 | harmony: Eth 55 | iotex: Eth 56 | moonbeam: Eth 57 | nervos: Eth 58 | optimism: Eth 59 | polygon: Eth 60 | syscoin: Eth 61 | 62 | def __init__( 63 | self, 64 | api_key: str, 65 | request_kwargs: Optional[Any] = None, 66 | middlewares: Optional[Sequence[Any]] = None, 67 | modules: Optional[Dict[str, Union[Type[Module], Sequence[Any]]]] = None, 68 | external_modules: Optional[ 69 | Dict[str, Union[Type[Module], Sequence[Any]]] 70 | ] = None, 71 | ens: ENS = cast(ENS, empty), 72 | ) -> None: 73 | self.__api_key = api_key 74 | self.__request_kwargs = request_kwargs 75 | self.__middlewares = middlewares 76 | self.__modules = modules 77 | self.__external_modules = external_modules 78 | self.__ens = ens 79 | 80 | self.early = AnkrEarlyAccessAPI(api_key) 81 | self.query = AnkrQueryAPI(api_key) 82 | self.token = AnkrTokenAPI(api_key) 83 | self.nft = AnkrNFTAPI(api_key) 84 | 85 | eth_provider = EthHTTPProvider(api_key, request_kwargs) 86 | super().__init__(eth_provider, middlewares, modules, external_modules, ens) 87 | self.arbitrum = self.__new_evm_chain(ArbitrumHTTPProvider) 88 | self.avalanche = self.__new_evm_chain(AvalancheHTTPProvider) 89 | self.bsc = self.__new_evm_chain(BscHTTPProvider) 90 | self.celo = self.__new_evm_chain(CeloHTTPProvider) 91 | self.fantom = self.__new_evm_chain(FantomHTTPProvider) 92 | self.gnosis = self.__new_evm_chain(GnosisHTTPProvider) 93 | self.harmony = self.__new_evm_chain(HarmonyHTTPProvider) 94 | self.iotex = self.__new_evm_chain(IotexHTTPProvider) 95 | self.moonbeam = self.__new_evm_chain(MoonbeamHTTPProvider) 96 | self.nervos = self.__new_evm_chain(NervosHTTPProvider) 97 | self.optimism = self.__new_evm_chain(OptimismHTTPProvider) 98 | self.polygon = self.__new_evm_chain(PolygonHTTPProvider) 99 | self.syscoin = self.__new_evm_chain(SyscoinHTTPProvider) 100 | 101 | def __new_evm_chain(self, provider: TProviderConstructor) -> Eth: 102 | w3 = Web3( 103 | provider(self.__api_key, self.__request_kwargs), 104 | self.__middlewares, 105 | self.__modules, 106 | self.__external_modules, 107 | self.__ens, 108 | ) 109 | w3.middleware_onion.inject(geth_poa_middleware, layer=0) 110 | return w3.eth 111 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "ankr-sdk" 3 | version = "1.0.2" 4 | description = "Compact Python library for interacting with Ankr's Advanced APIs." 5 | authors = [ 6 | "Roman Fasakhov ", 7 | "Yurii Momotenko =1.0.0"] 37 | build-backend = "poetry.core.masonry.api" 38 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [mypy] 2 | ignore_missing_imports = True 3 | warn_no_return = False 4 | check_untyped_defs = True 5 | warn_unused_ignores = True 6 | disallow_untyped_defs = True 7 | allow_redefinition = True 8 | follow_imports = skip 9 | exclude = env|venv|venv.*|tests|test_* 10 | 11 | [flake8] 12 | max-complexity = 8 13 | max-annotations-complexity = 4 14 | max-line-length = 120 15 | max-function-length = 100 16 | exclude = env,venv,pytest.ini 17 | per-file-ignores = 18 | __init__.py: F401 19 | 20 | [tool:pytest] 21 | markers = 22 | webtest: mark a test as a webtest. -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Ankr-network/ankr-python-sdk/a7234efd453c2cc19f16ba8ac39e028a99aac9ca/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | from ankr import AnkrAdvancedAPI 6 | 7 | 8 | @pytest.fixture 9 | def api_key() -> str: 10 | key = os.environ.get("ANKR_API_KEY") 11 | assert key 12 | return key 13 | 14 | 15 | @pytest.fixture 16 | def client(api_key: str) -> AnkrAdvancedAPI: 17 | return AnkrAdvancedAPI(api_key) 18 | -------------------------------------------------------------------------------- /tests/test_client.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import datetime 4 | 5 | import pytest 6 | 7 | from ankr.advanced_apis import AnkrAdvancedAPI 8 | from ankr.types import ( 9 | GetLogsRequest, 10 | Blockchain, 11 | GetBlocksRequest, 12 | GetNFTsByOwnerRequest, 13 | GetNFTMetadataRequest, 14 | GetNFTHoldersRequest, 15 | GetTransactionsByHashRequest, 16 | GetTokenHoldersRequest, 17 | GetTokenHoldersCountRequest, 18 | GetAccountBalanceRequest, 19 | GetTokenPriceRequest, 20 | ) 21 | 22 | 23 | def test_client_api_key() -> None: 24 | assert ( 25 | AnkrAdvancedAPI("my-test-api-key").provider.endpoint_uri 26 | == "https://rpc.ankr.com/multichain/my-test-api-key" 27 | ) 28 | 29 | 30 | @pytest.mark.webtest 31 | def test_get_logs(client: AnkrAdvancedAPI) -> None: 32 | logs = list( 33 | client.get_logs( 34 | request=GetLogsRequest( 35 | blockchain=Blockchain.Eth, 36 | fromBlock=14350001, 37 | toBlock=14350010, 38 | address=["0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"], 39 | topics=[ 40 | [], 41 | [ 42 | "0x000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff" 43 | ], 44 | ], 45 | decodeLogs=True, 46 | ) 47 | ) 48 | ) 49 | 50 | assert len(logs) == 18 51 | assert logs[0].address == "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" 52 | assert logs[0].event 53 | assert logs[0].event.name == "Transfer" 54 | 55 | 56 | @pytest.mark.webtest 57 | def test_get_blocks(client: AnkrAdvancedAPI) -> None: 58 | blocks = client.get_blocks( 59 | request=GetBlocksRequest( 60 | blockchain=Blockchain.Eth, 61 | fromBlock=14500001, 62 | toBlock=14500001, 63 | descOrder=True, 64 | includeLogs=True, 65 | includeTxs=True, 66 | decodeLogs=True, 67 | ) 68 | ) 69 | 70 | assert len(blocks) == 1 71 | assert blocks[0].transactions 72 | assert len(blocks[0].transactions) == 99 73 | assert blocks[0].transactions[6].logs 74 | assert len(blocks[0].transactions[6].logs) == 1 75 | 76 | 77 | @pytest.mark.webtest 78 | def test_get_nfts(client: AnkrAdvancedAPI) -> None: 79 | nfts = list( 80 | client.get_nfts( 81 | request=GetNFTsByOwnerRequest( 82 | blockchain=Blockchain.Eth, 83 | walletAddress="0x0E11A192d574b342C51be9e306694C41547185DD", 84 | filter=[ 85 | {"0x700b4b9f39bb1faf5d0d16a20488f2733550bff4": []}, 86 | {"0xd8682bfa6918b0174f287b888e765b9a1b4dc9c3": ["8937"]}, 87 | ], 88 | ), 89 | ) 90 | ) 91 | 92 | assert len(nfts) > 0 93 | assert nfts[0].blockchain == Blockchain.Eth 94 | assert nfts[0].traits 95 | assert len(nfts[0].traits) > 0 96 | 97 | 98 | @pytest.mark.webtest 99 | def test_get_nft_metadata(client: AnkrAdvancedAPI) -> None: 100 | reply = client.get_nft_metadata( 101 | request=GetNFTMetadataRequest( 102 | blockchain=Blockchain.Eth, 103 | contractAddress="0x4100670ee2f8aef6c47a4ed13c7f246e621228ec", 104 | tokenId="4", 105 | forceFetch=False, 106 | ) 107 | ) 108 | 109 | assert reply.metadata 110 | assert reply.metadata.blockchain == Blockchain.Eth 111 | assert reply.metadata.contractType == "ERC1155" 112 | assert reply.attributes 113 | assert reply.attributes.name == "Overleveraged" 114 | 115 | 116 | @pytest.mark.webtest 117 | def test_get_nft_holders(client: AnkrAdvancedAPI) -> None: 118 | holders = list( 119 | client.get_nft_holders( 120 | request=GetNFTHoldersRequest( 121 | blockchain=Blockchain.Eth, 122 | contractAddress="0x4100670ee2f8aef6c47a4ed13c7f246e621228ec", 123 | ), 124 | limit=10, 125 | ) 126 | ) 127 | 128 | assert holders 129 | assert len(holders) == 10 130 | 131 | 132 | @pytest.mark.webtest 133 | def test_get_transactions(client: AnkrAdvancedAPI) -> None: 134 | tx = client.get_transaction( 135 | request=GetTransactionsByHashRequest( 136 | transactionHash="0x82c13aaac6f0b6471afb94a3a64ae89d45baa3608ad397621dbb0d847f51196f", 137 | includeLogs=True, 138 | decodeLogs=True, 139 | decodeTxData=True, 140 | ) 141 | ) 142 | 143 | assert tx 144 | assert ( 145 | tx.hash == "0x82c13aaac6f0b6471afb94a3a64ae89d45baa3608ad397621dbb0d847f51196f" 146 | ) 147 | assert tx.to == "0x98767abab06e45a181ab73ae4cd0fecd0fbd0cd0" 148 | assert tx.from_ == "0x64aa6f93e0e1f49ff4958990c40d4bf17dafc0eb" 149 | assert tx.logs 150 | assert tx.logs[0].event 151 | assert tx.logs[0].event.name == "Transfer" 152 | 153 | 154 | @pytest.mark.webtest 155 | def test_get_token_holders(client: AnkrAdvancedAPI) -> None: 156 | holders = list( 157 | client.get_token_holders( 158 | request=GetTokenHoldersRequest( 159 | blockchain=Blockchain.Bsc, 160 | contractAddress="0xf307910A4c7bbc79691fD374889b36d8531B08e3", 161 | ), 162 | limit=10, 163 | ) 164 | ) 165 | 166 | assert len(holders) == 10 167 | assert holders[0].holderAddress.startswith("0x") 168 | assert "." in holders[0].balance 169 | assert holders[0].balanceRawInteger.isnumeric() 170 | 171 | 172 | @pytest.mark.webtest 173 | def test_get_token_holders_pagination(client: AnkrAdvancedAPI) -> None: 174 | holders = list( 175 | client.get_token_holders( 176 | request=GetTokenHoldersRequest( 177 | blockchain=Blockchain.Bsc, 178 | contractAddress="0xf307910A4c7bbc79691fD374889b36d8531B08e3", 179 | ), 180 | limit=None, 181 | ) 182 | ) 183 | 184 | assert len(holders) > 1000 185 | assert holders[0].holderAddress.startswith("0x") 186 | assert "." in holders[0].balance 187 | assert holders[0].balanceRawInteger.isnumeric() 188 | 189 | 190 | @pytest.mark.webtest 191 | def test_get_token_holders_count_history(client: AnkrAdvancedAPI) -> None: 192 | daily_holders_counts = list( 193 | client.get_token_holders_count_history( 194 | request=GetTokenHoldersCountRequest( 195 | blockchain=Blockchain.Bsc, 196 | contractAddress="0xf307910A4c7bbc79691fD374889b36d8531B08e3", 197 | ), 198 | limit=10, 199 | ) 200 | ) 201 | 202 | assert len(daily_holders_counts) == 10 203 | assert daily_holders_counts[0].holderCount > 0 204 | datetime.datetime.strptime( 205 | daily_holders_counts[0].lastUpdatedAt, "%Y-%m-%dT%H:%M:%SZ" 206 | ) 207 | 208 | 209 | @pytest.mark.webtest 210 | def test_get_token_holders_count(client: AnkrAdvancedAPI) -> None: 211 | holders_count = client.get_token_holders_count( 212 | request=GetTokenHoldersCountRequest( 213 | blockchain=Blockchain.Bsc, 214 | contractAddress="0xf307910A4c7bbc79691fD374889b36d8531B08e3", 215 | ) 216 | ) 217 | 218 | assert holders_count 219 | assert holders_count.holderCount > 0 220 | datetime.datetime.strptime(holders_count.lastUpdatedAt, "%Y-%m-%dT%H:%M:%SZ") 221 | 222 | 223 | @pytest.mark.webtest 224 | def test_get_account_balance(client: AnkrAdvancedAPI) -> None: 225 | assets = list( 226 | client.get_account_balance( 227 | request=GetAccountBalanceRequest( 228 | walletAddress="0x77A859A53D4de24bBC0CC80dD93Fbe391Df45527", 229 | blockchain=[Blockchain.Eth, Blockchain.Bsc], 230 | ) 231 | ) 232 | ) 233 | 234 | assert assets 235 | assert len(assets) > 0 236 | 237 | 238 | @pytest.mark.webtest 239 | def test_get_token_price(client: AnkrAdvancedAPI) -> None: 240 | price = client.get_token_price( 241 | request=GetTokenPriceRequest( 242 | contractAddress="0x8290333cef9e6d528dd5618fb97a76f268f3edd4", 243 | blockchain=Blockchain.Eth, 244 | ) 245 | ) 246 | 247 | assert price 248 | assert float(price) > 0 249 | 250 | 251 | @pytest.mark.webtest 252 | def test_get_token_price__no_price(client: AnkrAdvancedAPI) -> None: 253 | price = client.get_token_price( 254 | request=GetTokenPriceRequest( 255 | contractAddress="0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", 256 | blockchain=Blockchain.Eth, 257 | ) 258 | ) 259 | 260 | assert price == "0" 261 | -------------------------------------------------------------------------------- /tests/test_providers.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from ankr.providers import ( 4 | ArbitrumHTTPProvider, 5 | AvalancheHTTPProvider, 6 | BscHTTPProvider, 7 | CeloHTTPProvider, 8 | EthHTTPProvider, 9 | FantomHTTPProvider, 10 | GnosisHTTPProvider, 11 | HarmonyHTTPProvider, 12 | IotexHTTPProvider, 13 | MoonbeamHTTPProvider, 14 | MultichainHTTPProvider, 15 | NearHTTPProvider, 16 | NervosHTTPProvider, 17 | OptimismHTTPProvider, 18 | PolygonHTTPProvider, 19 | SolanaHTTPProvider, 20 | SyscoinHTTPProvider, 21 | ) 22 | 23 | 24 | def test_provider_api_key() -> None: 25 | assert ( 26 | MultichainHTTPProvider("my-test-api-key").endpoint_uri 27 | == "https://rpc.ankr.com/multichain/my-test-api-key" 28 | ) 29 | 30 | 31 | def test_chain_providers() -> None: 32 | assert ArbitrumHTTPProvider().endpoint_uri == "https://rpc.ankr.com/arbitrum/" 33 | assert AvalancheHTTPProvider().endpoint_uri == "https://rpc.ankr.com/avalanche/" 34 | assert BscHTTPProvider().endpoint_uri == "https://rpc.ankr.com/bsc/" 35 | assert CeloHTTPProvider().endpoint_uri == "https://rpc.ankr.com/celo/" 36 | assert EthHTTPProvider().endpoint_uri == "https://rpc.ankr.com/eth/" 37 | assert FantomHTTPProvider().endpoint_uri == "https://rpc.ankr.com/fantom/" 38 | assert GnosisHTTPProvider().endpoint_uri == "https://rpc.ankr.com/gnosis/" 39 | assert HarmonyHTTPProvider().endpoint_uri == "https://rpc.ankr.com/harmony/" 40 | assert IotexHTTPProvider().endpoint_uri == "https://rpc.ankr.com/iotex/" 41 | assert MoonbeamHTTPProvider().endpoint_uri == "https://rpc.ankr.com/moonbeam/" 42 | assert NearHTTPProvider().endpoint_uri == "https://rpc.ankr.com/near/" 43 | assert NervosHTTPProvider().endpoint_uri == "https://rpc.ankr.com/nervos/" 44 | assert OptimismHTTPProvider().endpoint_uri == "https://rpc.ankr.com/optimism/" 45 | assert PolygonHTTPProvider().endpoint_uri == "https://rpc.ankr.com/polygon/" 46 | assert SolanaHTTPProvider().endpoint_uri == "https://rpc.ankr.com/solana/" 47 | assert SyscoinHTTPProvider().endpoint_uri == "https://rpc.ankr.com/syscoin/" 48 | 49 | assert ( 50 | ArbitrumHTTPProvider(api_key="123").endpoint_uri 51 | == "https://rpc.ankr.com/arbitrum/123" 52 | ) 53 | -------------------------------------------------------------------------------- /tests/test_web3.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import pytest 4 | 5 | from ankr.web3 import AnkrWeb3 6 | 7 | 8 | @pytest.mark.webtest 9 | @pytest.mark.parametrize( 10 | "blockchain", 11 | [ 12 | "eth", 13 | "arbitrum", 14 | "avalanche", 15 | "bsc", 16 | "celo", 17 | "fantom", 18 | "gnosis", 19 | "harmony", 20 | "iotex", 21 | "moonbeam", 22 | "nervos", 23 | "optimism", 24 | "polygon", 25 | "syscoin", 26 | ], 27 | ) 28 | def test_ankr_web3(blockchain: str, api_key: str) -> None: 29 | w3 = AnkrWeb3(api_key) 30 | 31 | block = getattr(w3, blockchain).get_block("latest") 32 | 33 | assert block 34 | assert block.get("number") 35 | --------------------------------------------------------------------------------