web3 7.7.0__py3-none-any.whl → 7.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. ens/async_ens.py +1 -1
  2. ens/ens.py +1 -1
  3. web3/_utils/contract_sources/contract_data/ambiguous_function_contract.py +3 -3
  4. web3/_utils/contract_sources/contract_data/arrays_contract.py +3 -3
  5. web3/_utils/contract_sources/contract_data/bytes_contracts.py +5 -5
  6. web3/_utils/contract_sources/contract_data/constructor_contracts.py +7 -7
  7. web3/_utils/contract_sources/contract_data/contract_caller_tester.py +3 -3
  8. web3/_utils/contract_sources/contract_data/emitter_contract.py +3 -3
  9. web3/_utils/contract_sources/contract_data/event_contracts.py +7 -7
  10. web3/_utils/contract_sources/contract_data/extended_resolver.py +3 -3
  11. web3/_utils/contract_sources/contract_data/fallback_function_contract.py +3 -3
  12. web3/_utils/contract_sources/contract_data/function_name_tester_contract.py +3 -3
  13. web3/_utils/contract_sources/contract_data/math_contract.py +3 -3
  14. web3/_utils/contract_sources/contract_data/offchain_lookup.py +3 -3
  15. web3/_utils/contract_sources/contract_data/offchain_resolver.py +3 -3
  16. web3/_utils/contract_sources/contract_data/panic_errors_contract.py +3 -3
  17. web3/_utils/contract_sources/contract_data/payable_tester.py +3 -3
  18. web3/_utils/contract_sources/contract_data/receive_function_contracts.py +5 -5
  19. web3/_utils/contract_sources/contract_data/reflector_contracts.py +3 -3
  20. web3/_utils/contract_sources/contract_data/revert_contract.py +3 -3
  21. web3/_utils/contract_sources/contract_data/simple_resolver.py +3 -3
  22. web3/_utils/contract_sources/contract_data/storage_contract.py +3 -3
  23. web3/_utils/contract_sources/contract_data/string_contract.py +3 -3
  24. web3/_utils/contract_sources/contract_data/tuple_contracts.py +5 -5
  25. web3/_utils/error_formatters_utils.py +1 -1
  26. web3/_utils/events.py +1 -1
  27. web3/_utils/formatters.py +28 -0
  28. web3/_utils/http_session_manager.py +19 -1
  29. web3/_utils/method_formatters.py +121 -20
  30. web3/_utils/module_testing/eth_module.py +102 -2
  31. web3/_utils/module_testing/module_testing_utils.py +0 -42
  32. web3/_utils/module_testing/persistent_connection_provider.py +39 -1
  33. web3/_utils/rpc_abi.py +1 -0
  34. web3/_utils/validation.py +191 -0
  35. web3/beacon/api_endpoints.py +10 -0
  36. web3/beacon/async_beacon.py +47 -0
  37. web3/beacon/beacon.py +45 -0
  38. web3/contract/async_contract.py +2 -206
  39. web3/contract/base_contract.py +208 -12
  40. web3/contract/contract.py +2 -205
  41. web3/datastructures.py +4 -0
  42. web3/eth/async_eth.py +19 -0
  43. web3/eth/eth.py +15 -0
  44. web3/gas_strategies/time_based.py +1 -1
  45. web3/manager.py +47 -194
  46. web3/middleware/base.py +14 -6
  47. web3/providers/async_base.py +5 -4
  48. web3/providers/base.py +3 -3
  49. web3/providers/persistent/persistent.py +29 -3
  50. web3/providers/persistent/request_processor.py +11 -1
  51. web3/providers/persistent/subscription_manager.py +116 -46
  52. web3/providers/persistent/websocket.py +8 -3
  53. web3/providers/rpc/async_rpc.py +8 -3
  54. web3/providers/rpc/rpc.py +8 -3
  55. web3/types.py +36 -13
  56. web3/utils/subscriptions.py +5 -1
  57. {web3-7.7.0.dist-info → web3-7.9.0.dist-info}/LICENSE +1 -1
  58. {web3-7.7.0.dist-info → web3-7.9.0.dist-info}/METADATA +11 -7
  59. {web3-7.7.0.dist-info → web3-7.9.0.dist-info}/RECORD +61 -61
  60. {web3-7.7.0.dist-info → web3-7.9.0.dist-info}/WHEEL +1 -1
  61. {web3-7.7.0.dist-info → web3-7.9.0.dist-info}/top_level.txt +0 -0
web3/middleware/base.py CHANGED
@@ -62,15 +62,19 @@ class Web3Middleware:
62
62
  ) -> "MakeBatchRequestFn":
63
63
  def middleware(
64
64
  requests_info: List[Tuple["RPCEndpoint", Any]]
65
- ) -> List["RPCResponse"]:
65
+ ) -> Union[List["RPCResponse"], "RPCResponse"]:
66
66
  req_processed = [
67
67
  self.request_processor(method, params)
68
68
  for (method, params) in requests_info
69
69
  ]
70
- responses = make_batch_request(req_processed)
70
+ response = make_batch_request(req_processed)
71
+ if not isinstance(response, list):
72
+ # RPC errors return only one response with the error object
73
+ return response
74
+
71
75
  methods, _params = zip(*req_processed)
72
76
  formatted_responses = [
73
- self.response_processor(m, r) for m, r in zip(methods, responses)
77
+ self.response_processor(m, r) for m, r in zip(methods, response)
74
78
  ]
75
79
  return formatted_responses
76
80
 
@@ -103,16 +107,20 @@ class Web3Middleware:
103
107
  ) -> "AsyncMakeBatchRequestFn":
104
108
  async def middleware(
105
109
  requests_info: List[Tuple["RPCEndpoint", Any]]
106
- ) -> List["RPCResponse"]:
110
+ ) -> Union[List["RPCResponse"], "RPCResponse"]:
107
111
  req_processed = [
108
112
  await self.async_request_processor(method, params)
109
113
  for (method, params) in requests_info
110
114
  ]
111
- responses = await make_batch_request(req_processed)
115
+ response = await make_batch_request(req_processed)
116
+ if not isinstance(response, list):
117
+ # RPC errors return only one response with the error object
118
+ return response
119
+
112
120
  methods, _params = zip(*req_processed)
113
121
  formatted_responses = [
114
122
  await self.async_response_processor(m, r)
115
- for m, r in zip(methods, responses)
123
+ for m, r in zip(methods, response)
116
124
  ]
117
125
  return formatted_responses
118
126
 
@@ -77,7 +77,8 @@ class AsyncBaseProvider:
77
77
 
78
78
  _is_batching: bool = False
79
79
  _batch_request_func_cache: Tuple[
80
- Tuple[Middleware, ...], Callable[..., Coroutine[Any, Any, List[RPCResponse]]]
80
+ Tuple[Middleware, ...],
81
+ Callable[..., Coroutine[Any, Any, Union[List[RPCResponse], RPCResponse]]],
81
82
  ] = (None, None)
82
83
 
83
84
  is_async = True
@@ -119,7 +120,7 @@ class AsyncBaseProvider:
119
120
 
120
121
  async def batch_request_func(
121
122
  self, async_w3: "AsyncWeb3", middleware_onion: MiddlewareOnion
122
- ) -> Callable[..., Coroutine[Any, Any, List[RPCResponse]]]:
123
+ ) -> Callable[..., Coroutine[Any, Any, Union[List[RPCResponse], RPCResponse]]]:
123
124
  middleware: Tuple[Middleware, ...] = middleware_onion.as_tuple_of_middleware()
124
125
 
125
126
  cache_key = self._batch_request_func_cache[0]
@@ -141,8 +142,8 @@ class AsyncBaseProvider:
141
142
 
142
143
  async def make_batch_request(
143
144
  self, requests: List[Tuple[RPCEndpoint, Any]]
144
- ) -> List[RPCResponse]:
145
- raise NotImplementedError("Only AsyncHTTPProvider supports this method")
145
+ ) -> Union[List[RPCResponse], RPCResponse]:
146
+ raise NotImplementedError("Providers must implement this method")
146
147
 
147
148
  async def is_connected(self, show_traceback: bool = False) -> bool:
148
149
  raise NotImplementedError("Providers must implement this method")
web3/providers/base.py CHANGED
@@ -118,7 +118,7 @@ class JSONBaseProvider(BaseProvider):
118
118
 
119
119
  _is_batching: bool = False
120
120
  _batch_request_func_cache: Tuple[
121
- Tuple[Middleware, ...], Callable[..., List[RPCResponse]]
121
+ Tuple[Middleware, ...], Callable[..., Union[List[RPCResponse], RPCResponse]]
122
122
  ] = (None, None)
123
123
 
124
124
  def __init__(self, **kwargs: Any) -> None:
@@ -168,7 +168,7 @@ class JSONBaseProvider(BaseProvider):
168
168
 
169
169
  def batch_request_func(
170
170
  self, w3: "Web3", middleware_onion: MiddlewareOnion
171
- ) -> Callable[..., List[RPCResponse]]:
171
+ ) -> Callable[..., Union[List[RPCResponse], RPCResponse]]:
172
172
  middleware: Tuple[Middleware, ...] = middleware_onion.as_tuple_of_middleware()
173
173
 
174
174
  cache_key = self._batch_request_func_cache[0]
@@ -199,5 +199,5 @@ class JSONBaseProvider(BaseProvider):
199
199
 
200
200
  def make_batch_request(
201
201
  self, requests: List[Tuple[RPCEndpoint, Any]]
202
- ) -> List[RPCResponse]:
202
+ ) -> Union[List[RPCResponse], RPCResponse]:
203
203
  raise NotImplementedError("Providers must implement this method")
@@ -33,6 +33,9 @@ from web3._utils.caching.caching_utils import (
33
33
  async_handle_recv_caching,
34
34
  async_handle_send_caching,
35
35
  )
36
+ from web3._utils.validation import (
37
+ validate_rpc_response_and_raise_if_error,
38
+ )
36
39
  from web3.exceptions import (
37
40
  PersistentConnectionClosedOK,
38
41
  ProviderConnectionError,
@@ -302,6 +305,27 @@ class PersistentConnectionProvider(AsyncJSONBaseProvider, ABC):
302
305
  TaskNotRunning(message_listener_task)
303
306
  )
304
307
 
308
+ def _raise_stray_errors_from_cache(self) -> None:
309
+ """
310
+ Check the request response cache for any errors not tied to current requests
311
+ and raise them if found.
312
+ """
313
+ if not self._is_batching:
314
+ for (
315
+ response
316
+ ) in self._request_processor._request_response_cache._data.values():
317
+ request = (
318
+ self._request_processor._request_information_cache.get_cache_entry(
319
+ generate_cache_key(response["id"])
320
+ )
321
+ )
322
+ if "error" in response and request is None:
323
+ # if we find an error response in the cache without a corresponding
324
+ # request, raise the error
325
+ validate_rpc_response_and_raise_if_error(
326
+ response, None, logger=self.logger
327
+ )
328
+
305
329
  async def _message_listener(self) -> None:
306
330
  self.logger.info(
307
331
  f"{self.__class__.__qualname__} listener background task started. Storing "
@@ -327,6 +351,7 @@ class PersistentConnectionProvider(AsyncJSONBaseProvider, ABC):
327
351
  await self._request_processor.cache_raw_response(
328
352
  response, subscription=subscription
329
353
  )
354
+ self._raise_stray_errors_from_cache()
330
355
  except PersistentConnectionClosedOK as e:
331
356
  self.logger.info(
332
357
  "Message listener background task has ended gracefully: "
@@ -376,6 +401,10 @@ class PersistentConnectionProvider(AsyncJSONBaseProvider, ABC):
376
401
  request_cache_key = generate_cache_key(request_id)
377
402
 
378
403
  while True:
404
+ # check if an exception was recorded in the listener task and raise
405
+ # it in the main loop if so
406
+ self._handle_listener_task_exceptions()
407
+
379
408
  if request_cache_key in self._request_processor._request_response_cache:
380
409
  self.logger.debug(
381
410
  f"Popping response for id {request_id} from cache."
@@ -385,9 +414,6 @@ class PersistentConnectionProvider(AsyncJSONBaseProvider, ABC):
385
414
  )
386
415
  return popped_response
387
416
  else:
388
- # check if an exception was recorded in the listener task and raise
389
- # it in the main loop if so
390
- self._handle_listener_task_exceptions()
391
417
  await asyncio.sleep(0)
392
418
 
393
419
  try:
@@ -6,6 +6,7 @@ from typing import (
6
6
  Callable,
7
7
  Dict,
8
8
  Generic,
9
+ List,
9
10
  Optional,
10
11
  Tuple,
11
12
  TypeVar,
@@ -270,6 +271,15 @@ class RequestProcessor:
270
271
 
271
272
  # raw response cache
272
273
 
274
+ def _is_batch_response(
275
+ self, raw_response: Union[List[RPCResponse], RPCResponse]
276
+ ) -> bool:
277
+ return isinstance(raw_response, list) or (
278
+ isinstance(raw_response, dict)
279
+ and raw_response.get("id") is None
280
+ and self._provider._is_batching
281
+ )
282
+
273
283
  async def cache_raw_response(
274
284
  self, raw_response: Any, subscription: bool = False
275
285
  ) -> None:
@@ -296,7 +306,7 @@ class RequestProcessor:
296
306
  # otherwise, put it in the subscription response queue so a response
297
307
  # can be yielded by the message stream
298
308
  await self._subscription_response_queue.put(raw_response)
299
- elif isinstance(raw_response, list):
309
+ elif self._is_batch_response(raw_response):
300
310
  # Since only one batch should be in the cache at all times, we use a
301
311
  # constant cache key for the batch response.
302
312
  cache_key = generate_cache_key(BATCH_REQUEST_ID)
@@ -69,6 +69,23 @@ class SubscriptionManager:
69
69
  def _remove_subscription(self, subscription: EthSubscription[Any]) -> None:
70
70
  self._subscription_container.remove_subscription(subscription)
71
71
 
72
+ def _validate_and_normalize_label(self, subscription: EthSubscription[Any]) -> None:
73
+ if subscription.label == subscription._default_label:
74
+ # if no custom label was provided, generate a unique label
75
+ i = 2
76
+ while self.get_by_label(subscription._label) is not None:
77
+ subscription._label = f"{subscription._default_label}#{i}"
78
+ i += 1
79
+ else:
80
+ if (
81
+ subscription._label
82
+ in self._subscription_container.subscriptions_by_label
83
+ ):
84
+ raise Web3ValueError(
85
+ "Subscription label already exists. Subscriptions must have unique "
86
+ f"labels.\n label: {subscription._label}"
87
+ )
88
+
72
89
  @property
73
90
  def subscriptions(self) -> List[EthSubscription[Any]]:
74
91
  return self._subscription_container.subscriptions
@@ -100,16 +117,8 @@ class SubscriptionManager:
100
117
  :return:
101
118
  """
102
119
  if isinstance(subscriptions, EthSubscription):
103
- if (
104
- subscriptions.label
105
- in self._subscription_container.subscriptions_by_label
106
- ):
107
- raise Web3ValueError(
108
- "Subscription label already exists. Subscriptions must have "
109
- f"unique labels.\n label: {subscriptions.label}"
110
- )
111
-
112
120
  subscriptions.manager = self
121
+ self._validate_and_normalize_label(subscriptions)
113
122
  sub_id = await self._w3.eth._subscribe(*subscriptions.subscription_params)
114
123
  subscriptions._id = sub_id
115
124
  self._add_subscription(subscriptions)
@@ -124,34 +133,91 @@ class SubscriptionManager:
124
133
 
125
134
  sub_ids: List[HexStr] = []
126
135
  for sub in subscriptions:
127
- await self.subscribe(sub)
136
+ sub_ids.append(await self.subscribe(sub))
128
137
  return sub_ids
129
138
  raise Web3TypeError("Expected a Subscription or a sequence of Subscriptions.")
130
139
 
131
- async def unsubscribe(self, subscription: EthSubscription[Any]) -> bool:
140
+ @overload
141
+ async def unsubscribe(self, subscriptions: EthSubscription[Any]) -> bool:
142
+ ...
143
+
144
+ @overload
145
+ async def unsubscribe(self, subscriptions: HexStr) -> bool:
146
+ ...
147
+
148
+ @overload
149
+ async def unsubscribe(
150
+ self,
151
+ subscriptions: Sequence[Union[EthSubscription[Any], HexStr]],
152
+ ) -> bool:
153
+ ...
154
+
155
+ async def unsubscribe(
156
+ self,
157
+ subscriptions: Union[
158
+ EthSubscription[Any],
159
+ HexStr,
160
+ Sequence[Union[EthSubscription[Any], HexStr]],
161
+ ],
162
+ ) -> bool:
132
163
  """
133
- Used to unsubscribe from a subscription.
164
+ Used to unsubscribe from one or multiple subscriptions.
134
165
 
135
- :param subscription: The subscription to unsubscribe from.
136
- :type subscription: EthSubscription
137
- :return: ``True`` if unsubscribing was successful, ``False`` otherwise.
166
+ :param subscriptions: The subscription(s) to unsubscribe from.
167
+ :type subscriptions: Union[EthSubscription, Sequence[EthSubscription], HexStr,
168
+ Sequence[HexStr]]
169
+ :return: ``True`` if unsubscribing to all was successful, ``False`` otherwise
170
+ with a warning.
138
171
  :rtype: bool
139
172
  """
140
- if subscription not in self.subscriptions:
141
- raise Web3ValueError(
142
- "Subscription not found or is not being managed by the subscription "
143
- f"manager.\n label: {subscription.label}\n id: {subscription._id}"
144
- )
145
- if await self._w3.eth._unsubscribe(subscription.id):
146
- self._remove_subscription(subscription)
147
- self.logger.info(
148
- "Successfully unsubscribed from subscription:\n "
149
- f"label: {subscription.label}\n id: {subscription.id}"
150
- )
151
- if len(self._subscription_container.handler_subscriptions) == 0:
152
- queue = self._provider._request_processor._handler_subscription_queue
153
- await queue.put(SubscriptionProcessingFinished())
154
- return True
173
+ if isinstance(subscriptions, EthSubscription) or isinstance(subscriptions, str):
174
+ if isinstance(subscriptions, str):
175
+ subscription_id = subscriptions
176
+ subscriptions = self.get_by_id(subscription_id)
177
+ if subscriptions is None:
178
+ raise Web3ValueError(
179
+ "Subscription not found or is not being managed by the "
180
+ f"subscription manager.\n id: {subscription_id}"
181
+ )
182
+
183
+ if subscriptions not in self.subscriptions:
184
+ raise Web3ValueError(
185
+ "Subscription not found or is not being managed by the "
186
+ "subscription manager.\n "
187
+ f"label: {subscriptions.label}\n id: {subscriptions._id}"
188
+ )
189
+
190
+ if await self._w3.eth._unsubscribe(subscriptions.id):
191
+ self._remove_subscription(subscriptions)
192
+ self.logger.info(
193
+ "Successfully unsubscribed from subscription:\n "
194
+ f"label: {subscriptions.label}\n id: {subscriptions.id}"
195
+ )
196
+
197
+ if len(self._subscription_container.handler_subscriptions) == 0:
198
+ queue = (
199
+ self._provider._request_processor._handler_subscription_queue
200
+ )
201
+ await queue.put(SubscriptionProcessingFinished())
202
+ return True
203
+
204
+ elif isinstance(subscriptions, Sequence):
205
+ if len(subscriptions) == 0:
206
+ raise Web3ValueError("No subscriptions provided.")
207
+
208
+ unsubscribed: List[bool] = []
209
+ # re-create the subscription list to prevent modifying the original list
210
+ # in case ``subscription_manager.subscriptions`` was passed in directly
211
+ subs = [sub for sub in subscriptions]
212
+ for sub in subs:
213
+ if isinstance(sub, str):
214
+ sub = HexStr(sub)
215
+ unsubscribed.append(await self.unsubscribe(sub))
216
+ return all(unsubscribed)
217
+
218
+ self.logger.warning(
219
+ f"Failed to unsubscribe from subscription\n subscription={subscriptions}"
220
+ )
155
221
  return False
156
222
 
157
223
  async def unsubscribe_all(self) -> bool:
@@ -163,7 +229,9 @@ class SubscriptionManager:
163
229
  :rtype: bool
164
230
  """
165
231
  unsubscribed = [
166
- await self.unsubscribe(sub) for sub in self.subscriptions.copy()
232
+ await self.unsubscribe(sub)
233
+ # use copy to prevent modifying the list while iterating over it
234
+ for sub in self.subscriptions.copy()
167
235
  ]
168
236
  if all(unsubscribed):
169
237
  self.logger.info("Successfully unsubscribed from all subscriptions.")
@@ -186,15 +254,15 @@ class SubscriptionManager:
186
254
  :type run_forever: bool
187
255
  :return: None
188
256
  """
189
- if not self._subscription_container.handler_subscriptions:
257
+ if not self._subscription_container.handler_subscriptions and not run_forever:
190
258
  self.logger.warning(
191
259
  "No handler subscriptions found. Subscription handler did not run."
192
260
  )
193
261
  return
194
262
 
195
263
  queue = self._provider._request_processor._handler_subscription_queue
196
- try:
197
- while run_forever or self._subscription_container.handler_subscriptions:
264
+ while run_forever or self._subscription_container.handler_subscriptions:
265
+ try:
198
266
  response = cast(RPCResponse, await queue.get())
199
267
  formatted_sub_response = cast(
200
268
  FormattedEthSubscriptionResponse,
@@ -216,18 +284,20 @@ class SubscriptionManager:
216
284
  **sub._handler_context,
217
285
  )
218
286
  )
219
- except SubscriptionProcessingFinished:
220
- self.logger.info(
221
- "All handler subscriptions have been unsubscribed from. "
222
- "Stopping subscription handling."
223
- )
224
- except TaskNotRunning:
225
- await asyncio.sleep(0)
226
- self._provider._handle_listener_task_exceptions()
227
- self.logger.error(
228
- "Message listener background task for the provider has stopped "
229
- "unexpectedly. Stopping subscription handling."
230
- )
287
+ except SubscriptionProcessingFinished:
288
+ if not run_forever:
289
+ self.logger.info(
290
+ "All handler subscriptions have been unsubscribed from. "
291
+ "Stopping subscription handling."
292
+ )
293
+ break
294
+ except TaskNotRunning:
295
+ await asyncio.sleep(0)
296
+ self._provider._handle_listener_task_exceptions()
297
+ self.logger.error(
298
+ "Message listener background task for the provider has stopped "
299
+ "unexpectedly. Stopping subscription handling."
300
+ )
231
301
 
232
302
  # no active handler subscriptions, clear the handler subscription queue
233
303
  self._provider._request_processor._reset_handler_subscription_queue()
@@ -63,6 +63,8 @@ class WebSocketProvider(PersistentConnectionProvider):
63
63
  self,
64
64
  endpoint_uri: Optional[Union[URI, str]] = None,
65
65
  websocket_kwargs: Optional[Dict[str, Any]] = None,
66
+ # uses binary frames by default
67
+ use_text_frames: Optional[bool] = False,
66
68
  # `PersistentConnectionProvider` kwargs can be passed through
67
69
  **kwargs: Any,
68
70
  ) -> None:
@@ -71,6 +73,7 @@ class WebSocketProvider(PersistentConnectionProvider):
71
73
  URI(endpoint_uri) if endpoint_uri is not None else get_default_endpoint()
72
74
  )
73
75
  super().__init__(**kwargs)
76
+ self.use_text_frames = use_text_frames
74
77
  self._ws: Optional[WebSocketClientProtocol] = None
75
78
 
76
79
  if not any(
@@ -118,9 +121,11 @@ class WebSocketProvider(PersistentConnectionProvider):
118
121
  "Connection to websocket has not been initiated for the provider."
119
122
  )
120
123
 
121
- await asyncio.wait_for(
122
- self._ws.send(request_data), timeout=self.request_timeout
123
- )
124
+ payload: Union[bytes, str] = request_data
125
+ if self.use_text_frames:
126
+ payload = request_data.decode("utf-8")
127
+
128
+ await asyncio.wait_for(self._ws.send(payload), timeout=self.request_timeout)
124
129
 
125
130
  async def socket_recv(self) -> RPCResponse:
126
131
  raw_response = await self._ws.recv()
@@ -168,15 +168,20 @@ class AsyncHTTPProvider(AsyncJSONBaseProvider):
168
168
 
169
169
  async def make_batch_request(
170
170
  self, batch_requests: List[Tuple[RPCEndpoint, Any]]
171
- ) -> List[RPCResponse]:
171
+ ) -> Union[List[RPCResponse], RPCResponse]:
172
172
  self.logger.debug(f"Making batch request HTTP - uri: `{self.endpoint_uri}`")
173
173
  request_data = self.encode_batch_rpc_request(batch_requests)
174
174
  raw_response = await self._request_session_manager.async_make_post_request(
175
175
  self.endpoint_uri, request_data, **self.get_request_kwargs()
176
176
  )
177
177
  self.logger.debug("Received batch response HTTP.")
178
- responses_list = cast(List[RPCResponse], self.decode_rpc_response(raw_response))
179
- return sort_batch_response_by_response_ids(responses_list)
178
+ response = self.decode_rpc_response(raw_response)
179
+ if not isinstance(response, list):
180
+ # RPC errors return only one response with the error object
181
+ return response
182
+ return sort_batch_response_by_response_ids(
183
+ cast(List[RPCResponse], sort_batch_response_by_response_ids(response))
184
+ )
180
185
 
181
186
  async def disconnect(self) -> None:
182
187
  cache = self._request_session_manager.session_cache
web3/providers/rpc/rpc.py CHANGED
@@ -176,12 +176,17 @@ class HTTPProvider(JSONBaseProvider):
176
176
 
177
177
  def make_batch_request(
178
178
  self, batch_requests: List[Tuple[RPCEndpoint, Any]]
179
- ) -> List[RPCResponse]:
179
+ ) -> Union[List[RPCResponse], RPCResponse]:
180
180
  self.logger.debug(f"Making batch request HTTP, uri: `{self.endpoint_uri}`")
181
181
  request_data = self.encode_batch_rpc_request(batch_requests)
182
182
  raw_response = self._request_session_manager.make_post_request(
183
183
  self.endpoint_uri, request_data, **self.get_request_kwargs()
184
184
  )
185
185
  self.logger.debug("Received batch response HTTP.")
186
- responses_list = cast(List[RPCResponse], self.decode_rpc_response(raw_response))
187
- return sort_batch_response_by_response_ids(responses_list)
186
+ response = self.decode_rpc_response(raw_response)
187
+ if not isinstance(response, list):
188
+ # RPC errors return only one response with the error object
189
+ return response
190
+ return sort_batch_response_by_response_ids(
191
+ cast(List[RPCResponse], sort_batch_response_by_response_ids(response))
192
+ )
web3/types.py CHANGED
@@ -36,13 +36,9 @@ from web3._utils.compat import (
36
36
  )
37
37
 
38
38
  if TYPE_CHECKING:
39
- from web3.contract.async_contract import ( # noqa: F401
40
- AsyncContractEvent,
41
- AsyncContractFunction,
42
- )
43
- from web3.contract.contract import ( # noqa: F401
44
- ContractEvent,
45
- ContractFunction,
39
+ from web3.contract.base_contract import (
40
+ BaseContractEvent,
41
+ BaseContractFunction,
46
42
  )
47
43
  from web3.main import ( # noqa: F401
48
44
  AsyncWeb3,
@@ -201,10 +197,10 @@ class LogReceipt(TypedDict):
201
197
  blockNumber: BlockNumber
202
198
  data: HexBytes
203
199
  logIndex: int
200
+ removed: bool
204
201
  topics: Sequence[HexBytes]
205
202
  transactionHash: HexBytes
206
203
  transactionIndex: int
207
- removed: bool
208
204
 
209
205
 
210
206
  class SubscriptionResponse(TypedDict):
@@ -301,10 +297,13 @@ class CreateAccessListResponse(TypedDict):
301
297
 
302
298
 
303
299
  MakeRequestFn = Callable[[RPCEndpoint, Any], RPCResponse]
304
- MakeBatchRequestFn = Callable[[List[Tuple[RPCEndpoint, Any]]], List[RPCResponse]]
300
+ MakeBatchRequestFn = Callable[
301
+ [List[Tuple[RPCEndpoint, Any]]], Union[List[RPCResponse], RPCResponse]
302
+ ]
305
303
  AsyncMakeRequestFn = Callable[[RPCEndpoint, Any], Coroutine[Any, Any, RPCResponse]]
306
304
  AsyncMakeBatchRequestFn = Callable[
307
- [List[Tuple[RPCEndpoint, Any]]], Coroutine[Any, Any, List[RPCResponse]]
305
+ [List[Tuple[RPCEndpoint, Any]]],
306
+ Coroutine[Any, Any, Union[List[RPCResponse], RPCResponse]],
308
307
  ]
309
308
 
310
309
 
@@ -337,7 +336,7 @@ class StateOverrideParams(TypedDict, total=False):
337
336
  stateDiff: Optional[Dict[HexStr, HexStr]]
338
337
 
339
338
 
340
- StateOverride = Dict[ChecksumAddress, StateOverrideParams]
339
+ StateOverride = Dict[Union[str, Address, ChecksumAddress], StateOverrideParams]
341
340
 
342
341
 
343
342
  GasPriceStrategy = Union[
@@ -568,6 +567,30 @@ class OpcodeTrace(TypedDict, total=False):
568
567
  structLogs: List[StructLog]
569
568
 
570
569
 
570
+ class BlockStateCallV1(TypedDict):
571
+ blockOverrides: NotRequired[BlockData]
572
+ stateOverrides: NotRequired[StateOverride]
573
+ calls: Sequence[TxParams]
574
+
575
+
576
+ class SimulateV1Payload(TypedDict):
577
+ blockStateCalls: Sequence[BlockStateCallV1]
578
+ validation: NotRequired[bool]
579
+ traceTransfers: NotRequired[bool]
580
+
581
+
582
+ class SimulateV1CallResult(TypedDict):
583
+ returnData: HexBytes
584
+ logs: Sequence[LogReceipt]
585
+ gasUsed: int
586
+ status: int
587
+ error: NotRequired[RPCError]
588
+
589
+
590
+ class SimulateV1Result(BlockData):
591
+ calls: Sequence[SimulateV1CallResult]
592
+
593
+
571
594
  #
572
595
  # web3.geth types
573
596
  #
@@ -581,8 +604,8 @@ class GethWallet(TypedDict):
581
604
 
582
605
  # Contract types
583
606
 
584
- TContractFn = TypeVar("TContractFn", "ContractFunction", "AsyncContractFunction")
585
- TContractEvent = TypeVar("TContractEvent", "ContractEvent", "AsyncContractEvent")
607
+ TContractFn = TypeVar("TContractFn", bound="BaseContractFunction")
608
+ TContractEvent = TypeVar("TContractEvent", bound="BaseContractEvent")
586
609
 
587
610
 
588
611
  # Tracing types
@@ -114,6 +114,10 @@ class EthSubscription(Generic[TSubscriptionResult]):
114
114
  self._label = label
115
115
  self.handler_call_count = 0
116
116
 
117
+ @property
118
+ def _default_label(self) -> str:
119
+ return f"{self.__class__.__name__}{self.subscription_params}"
120
+
117
121
  @classmethod
118
122
  def _create_type_aware_subscription(
119
123
  cls,
@@ -170,7 +174,7 @@ class EthSubscription(Generic[TSubscriptionResult]):
170
174
  @property
171
175
  def label(self) -> str:
172
176
  if not self._label:
173
- self._label = f"{self.__class__.__name__}{self.subscription_params}"
177
+ self._label = self._default_label
174
178
  return self._label
175
179
 
176
180
  @property
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2016-2024 The Ethereum Foundation
3
+ Copyright (c) 2016-2025 The Ethereum Foundation
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal