1
1
import json
2
+ import os
2
3
from typing import Any , Dict , Optional
3
4
from urllib .parse import urljoin
4
5
6
+ import aiohttp
5
7
import requests
6
8
from eth_typing import ChecksumAddress
7
9
@@ -150,14 +152,26 @@ class BlockscoutClient:
150
152
EthereumNetwork .EXSAT_TESTNET : "https://scan-testnet.exsat.network/api/v1/graphql" ,
151
153
}
152
154
153
- def __init__ (self , network : EthereumNetwork ):
155
+ def __init__ (
156
+ self ,
157
+ network : EthereumNetwork ,
158
+ request_timeout : int = int (
159
+ os .environ .get ("BLOCKSCOUT_CLIENT_REQUEST_TIMEOUT" , 10 )
160
+ ),
161
+ max_requests : int = int (os .environ .get ("BLOCKSCOUT_CLIENT_MAX_REQUESTS" , 100 )),
162
+ ):
154
163
self .network = network
155
164
self .grahpql_url = self .NETWORK_WITH_URL .get (network , "" )
165
+ self .request_timeout = request_timeout
156
166
if not self .grahpql_url :
157
167
raise BlockScoutConfigurationProblem (
158
168
f"Network { network .name } - { network .value } not supported"
159
169
)
160
170
self .http_session = requests .Session ()
171
+ # Limit simultaneous connections to the same host.
172
+ self .async_session = aiohttp .ClientSession (
173
+ connector = aiohttp .TCPConnector (limit_per_host = max_requests )
174
+ )
161
175
162
176
def build_url (self , path : str ):
163
177
return urljoin (self .grahpql_url , path )
@@ -169,6 +183,18 @@ def _do_request(self, url: str, query: str) -> Optional[Dict[str, Any]]:
169
183
170
184
return response .json ()
171
185
186
+ async def _async_do_request (self , url : str , query : str ) -> Optional [Dict [str , Any ]]:
187
+ """
188
+ Asynchronous version of _do_request
189
+ """
190
+ async with self .async_session .post (
191
+ url , son = {"query" : query }, timeout = self .request_timeout
192
+ ) as response :
193
+ if not response .ok :
194
+ return None
195
+
196
+ return await response .json ()
197
+
172
198
def get_contract_metadata (
173
199
self , address : ChecksumAddress
174
200
) -> Optional [ContractMetadata ]:
@@ -185,3 +211,20 @@ def get_contract_metadata(
185
211
smart_contract ["name" ], json .loads (smart_contract ["abi" ]), False
186
212
)
187
213
return None
214
+
215
+ async def async_get_contract_metadata (
216
+ self , address : ChecksumAddress
217
+ ) -> Optional [ContractMetadata ]:
218
+ query = '{address(hash: "%s") { hash, smartContract {name, abi} }}' % address
219
+ result = await self ._async_do_request (self .grahpql_url , query )
220
+ if (
221
+ result
222
+ and "error" not in result
223
+ and result .get ("data" , {}).get ("address" , {})
224
+ and result ["data" ]["address" ]["smartContract" ]
225
+ ):
226
+ smart_contract = result ["data" ]["address" ]["smartContract" ]
227
+ return ContractMetadata (
228
+ smart_contract ["name" ], json .loads (smart_contract ["abi" ]), False
229
+ )
230
+ return None
0 commit comments