tonutils 2.0.1b3__py3-none-any.whl → 2.0.1b5__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 (31) hide show
  1. tonutils/__meta__.py +1 -1
  2. tonutils/cli.py +111 -0
  3. tonutils/clients/__init__.py +4 -4
  4. tonutils/clients/adnl/__init__.py +4 -4
  5. tonutils/clients/adnl/balancer.py +58 -58
  6. tonutils/clients/adnl/client.py +20 -20
  7. tonutils/clients/adnl/provider/config.py +13 -8
  8. tonutils/clients/adnl/provider/provider.py +39 -42
  9. tonutils/clients/adnl/provider/transport.py +30 -25
  10. tonutils/clients/base.py +5 -1
  11. tonutils/exceptions.py +41 -31
  12. tonutils/tonconnect/__init__.py +0 -0
  13. tonutils/tools/__init__.py +6 -0
  14. tonutils/tools/block_scanner/__init__.py +16 -0
  15. tonutils/tools/block_scanner/annotations.py +23 -0
  16. tonutils/tools/block_scanner/dispatcher.py +141 -0
  17. tonutils/tools/block_scanner/events.py +31 -0
  18. tonutils/tools/block_scanner/scanner.py +313 -0
  19. tonutils/tools/block_scanner/traversal.py +97 -0
  20. tonutils/tools/block_scanner/where.py +53 -0
  21. tonutils/tools/status_monitor/__init__.py +3 -0
  22. tonutils/tools/status_monitor/console.py +157 -0
  23. tonutils/tools/status_monitor/models.py +27 -0
  24. tonutils/tools/status_monitor/monitor.py +295 -0
  25. tonutils/types.py +12 -4
  26. {tonutils-2.0.1b3.dist-info → tonutils-2.0.1b5.dist-info}/METADATA +2 -5
  27. {tonutils-2.0.1b3.dist-info → tonutils-2.0.1b5.dist-info}/RECORD +31 -16
  28. tonutils-2.0.1b5.dist-info/entry_points.txt +2 -0
  29. {tonutils-2.0.1b3.dist-info → tonutils-2.0.1b5.dist-info}/WHEEL +0 -0
  30. {tonutils-2.0.1b3.dist-info → tonutils-2.0.1b5.dist-info}/licenses/LICENSE +0 -0
  31. {tonutils-2.0.1b3.dist-info → tonutils-2.0.1b5.dist-info}/top_level.txt +0 -0
@@ -96,7 +96,11 @@ class AdnlProvider:
96
96
 
97
97
  @property
98
98
  def is_connected(self) -> bool:
99
- """Check whether the underlying transport is connected."""
99
+ """
100
+ Whether the underlying transport is currently connected.
101
+
102
+ :return: True if connected, False otherwise
103
+ """
100
104
  return self.transport.is_connected
101
105
 
102
106
  @property
@@ -129,9 +133,9 @@ class AdnlProvider:
129
133
  @property
130
134
  def last_ping_ms(self) -> t.Optional[int]:
131
135
  """
132
- Round-trip time of the last ping in milliseconds.
136
+ Round-trip time of the last successful ping.
133
137
 
134
- :return: Ping RTT in milliseconds or None if unknown
138
+ :return: Ping RTT in milliseconds, or None if unknown
135
139
  """
136
140
  if self.last_ping_rtt is None:
137
141
  return None
@@ -165,10 +169,7 @@ class AdnlProvider:
165
169
  return
166
170
 
167
171
  self.loop = asyncio.get_running_loop()
168
- try:
169
- await self.transport.connect()
170
- except Exception as exc:
171
- raise ProviderError(f"adnl connect failed ({self.node.endpoint})") from exc
172
+ await self.transport.connect()
172
173
 
173
174
  tasks = [self.reader.start(), self.pinger.start(), self.updater.start()]
174
175
  await asyncio.gather(*tasks, return_exceptions=True)
@@ -239,19 +240,13 @@ class AdnlProvider:
239
240
  raise ProviderTimeoutError(
240
241
  timeout=self.request_timeout,
241
242
  endpoint=self.node.endpoint,
242
- operation="adnl query",
243
- ) from exc
244
- except asyncio.CancelledError as exc:
245
- raise ProviderError(
246
- f"adnl provider closed ({self.node.endpoint})"
243
+ operation="request",
247
244
  ) from exc
248
245
 
249
246
  if not isinstance(resp, dict):
250
- raise ProviderError(
251
- f"adnl invalid response type ({self.node.endpoint})"
252
- )
253
-
247
+ raise ProviderError(f"invalid response type: {type(resp).__name__}")
254
248
  return resp
249
+
255
250
  finally:
256
251
  if query_id_key in self.pending:
257
252
  del self.pending[query_id_key]
@@ -419,7 +414,7 @@ class AdnlProvider:
419
414
  self,
420
415
  workchain: WorkchainID,
421
416
  shard: int,
422
- seqno: int = -1,
417
+ seqno: t.Optional[int] = None,
423
418
  lt: t.Optional[int] = None,
424
419
  utime: t.Optional[int] = None,
425
420
  *,
@@ -430,42 +425,45 @@ class AdnlProvider:
430
425
 
431
426
  :param workchain: Workchain identifier
432
427
  :param shard: Shard identifier
433
- :param seqno: Block seqno or -1 to ignore
428
+ :param seqno: Block sequence number
434
429
  :param lt: Logical time filter
435
430
  :param utime: UNIX time filter
436
431
  :param priority: Whether to use priority slot in the limiter
437
432
  :return: Tuple of BlockIdExt and deserialized Block
438
433
  """
439
434
  mode = 0
440
- if seqno != -1:
435
+ block_seqno = 0
436
+
437
+ if seqno is not None:
441
438
  mode = 1
439
+ block_seqno = seqno
442
440
  if lt is not None:
443
441
  mode = 2
444
442
  if utime is not None:
445
443
  mode = 4
446
444
 
447
445
  data = {
446
+ "mode": mode,
448
447
  "id": {
449
- "workchain": workchain.value,
448
+ "workchain": workchain,
450
449
  "shard": shard,
451
- "seqno": seqno,
450
+ "seqno": block_seqno,
452
451
  },
453
452
  "lt": lt,
454
- "mode": mode,
455
453
  "utime": utime,
456
454
  }
455
+
457
456
  result = await self.send_liteserver_query(
458
- method="lookupBlock",
457
+ "lookupBlock",
459
458
  data=data,
460
459
  priority=priority,
461
460
  )
462
461
 
463
- cell = Cell.one_from_boc(result["header_proof"])
462
+ block_id = BlockIdExt.from_dict(result["id"])
463
+ header_proof = Cell.one_from_boc(result["header_proof"])
464
+ block = Block.deserialize(header_proof[0].begin_parse())
464
465
 
465
- return (
466
- BlockIdExt.from_dict(result["id"]),
467
- Block.deserialize(cell[0].begin_parse()),
468
- )
466
+ return block_id, block
469
467
 
470
468
  async def get_block_header(
471
469
  self,
@@ -480,23 +478,25 @@ class AdnlProvider:
480
478
  :param priority: Whether to use priority slot in the limiter
481
479
  :return: Tuple of BlockIdExt and deserialized Block
482
480
  """
481
+ data = {"id": block.to_dict(), "mode": 0}
482
+
483
483
  result = await self.send_liteserver_query(
484
- method="getBlockHeader",
485
- data={"id": block.to_dict(), "mode": 0},
484
+ "getBlockHeader",
485
+ data=data,
486
486
  priority=priority,
487
487
  )
488
488
 
489
- cell = Cell.one_from_boc(result["header_proof"])
490
- return (
491
- BlockIdExt.from_dict(result["id"]),
492
- Block.deserialize(cell[0].begin_parse()),
493
- )
489
+ block_id = BlockIdExt.from_dict(result["id"])
490
+ header_proof = Cell.one_from_boc(result["header_proof"])
491
+ block_obj = Block.deserialize(header_proof[0].begin_parse())
492
+
493
+ return block_id, block_obj
494
494
 
495
495
  async def get_block_transactions_ext(
496
496
  self,
497
497
  block: BlockIdExt,
498
- *,
499
498
  count: int = 1024,
499
+ *,
500
500
  priority: bool = False,
501
501
  ) -> t.List[Transaction]:
502
502
  """
@@ -508,7 +508,6 @@ class AdnlProvider:
508
508
  :return: List of deserialized Transaction objects
509
509
  """
510
510
  mode = 39
511
-
512
511
  result = await self.send_liteserver_query(
513
512
  method="listBlockTransactionsExt",
514
513
  data={
@@ -632,9 +631,7 @@ class AdnlProvider:
632
631
 
633
632
  exit_code = result.get("exit_code")
634
633
  if exit_code is None:
635
- raise ProviderError(
636
- f"runSmcMethod: missing `exit_code` in response ({self.node.endpoint})"
637
- )
634
+ raise ProviderError("runSmcMethod: missing exit_code in response")
638
635
 
639
636
  if exit_code != 0:
640
637
  raise RunGetMethodError(
@@ -735,7 +732,7 @@ class AdnlProvider:
735
732
  """
736
733
  if count > 16:
737
734
  raise ClientError(
738
- "`get_raw_transactions` supports up to 16 transactions per request"
735
+ "get_transactions supports up to 16 transactions per request"
739
736
  )
740
737
 
741
738
  data = {
@@ -759,7 +756,7 @@ class AdnlProvider:
759
756
  curr_hash = cell.get_hash(0).hex()
760
757
  if curr_hash != prev_tr_hash:
761
758
  raise ClientError(
762
- "Transaction hash mismatch in `raw_get_transactions`: "
759
+ f"transaction hash mismatch: "
763
760
  f"expected {prev_tr_hash}, got {curr_hash}"
764
761
  )
765
762
 
@@ -16,7 +16,7 @@ from pytoniq_core.crypto.ciphers import (
16
16
  )
17
17
 
18
18
  from tonutils.clients.adnl.provider.models import LiteServer
19
- from tonutils.exceptions import TransportError
19
+ from tonutils.exceptions import TransportError, NotConnectedError
20
20
 
21
21
 
22
22
  class AdnlTcpTransport:
@@ -41,6 +41,7 @@ class AdnlTcpTransport:
41
41
  :param node: Liteserver configuration with host, port, and public key
42
42
  :param connect_timeout: Timeout in seconds for connection
43
43
  """
44
+ self.node = node
44
45
  self.server = Server(
45
46
  host=node.host,
46
47
  port=node.port,
@@ -67,6 +68,14 @@ class AdnlTcpTransport:
67
68
  """Check if the transport is currently connected."""
68
69
  return self._connected
69
70
 
71
+ def _error(self, operation: str, reason: str) -> TransportError:
72
+ """Create TransportError with endpoint context."""
73
+ return TransportError(
74
+ endpoint=self.node.endpoint,
75
+ operation=operation,
76
+ reason=reason,
77
+ )
78
+
70
79
  @staticmethod
71
80
  def _build_frame(data: bytes) -> bytes:
72
81
  """
@@ -125,12 +134,12 @@ class AdnlTcpTransport:
125
134
  async def _flush(self) -> None:
126
135
  """Flush the TCP write buffer."""
127
136
  if self.writer is None:
128
- raise TransportError("transport state: writer is not initialized")
137
+ raise self._error("send", "writer not initialized")
129
138
  try:
130
139
  await self.writer.drain()
131
140
  except ConnectionError as exc:
132
141
  await self.close()
133
- raise TransportError(f"transport io: drain failed") from exc
142
+ raise self._error("send", "connection lost") from exc
134
143
 
135
144
  def encrypt_frame(self, data: bytes) -> bytes:
136
145
  """
@@ -139,9 +148,7 @@ class AdnlTcpTransport:
139
148
  :param data: Plaintext frame bytes
140
149
  """
141
150
  if self.enc_cipher is None:
142
- raise TransportError(
143
- "transport cipher: encryption cipher is not initialized"
144
- )
151
+ raise self._error("encrypt", "cipher not initialized")
145
152
  return aes_ctr_encrypt(self.enc_cipher, data)
146
153
 
147
154
  def decrypt_frame(self, data: bytes) -> bytes:
@@ -151,15 +158,13 @@ class AdnlTcpTransport:
151
158
  :param data: Encrypted frame bytes
152
159
  """
153
160
  if self.dec_cipher is None:
154
- raise TransportError(
155
- "transport cipher: decryption cipher is not initialized"
156
- )
161
+ raise self._error("decrypt", "cipher not initialized")
157
162
  return aes_ctr_decrypt(self.dec_cipher, data)
158
163
 
159
164
  async def connect(self) -> None:
160
165
  """Establish encrypted connection to the liteserver."""
161
166
  if self._connected:
162
- raise TransportError("transport state: already connected")
167
+ raise self._error("connect", "already connected")
163
168
 
164
169
  self.loop = asyncio.get_running_loop()
165
170
 
@@ -169,14 +174,14 @@ class AdnlTcpTransport:
169
174
  timeout=self.connect_timeout,
170
175
  )
171
176
  except asyncio.TimeoutError as exc:
172
- raise TransportError(
173
- f"transport connect: timeout {self.connect_timeout}s"
177
+ raise self._error(
178
+ "connect", f"timeout after {self.connect_timeout}s"
174
179
  ) from exc
175
180
  except OSError as exc:
176
- raise TransportError(f"transport connect: failed") from exc
181
+ raise self._error("connect", "connection refused") from exc
177
182
 
178
183
  if self.writer is None or self.reader is None:
179
- raise TransportError(f"transport connect: failed to initialize streams")
184
+ raise self._error("connect", "stream init failed")
180
185
 
181
186
  try:
182
187
  handshake = self._build_handshake()
@@ -189,13 +194,12 @@ class AdnlTcpTransport:
189
194
  timeout=self.connect_timeout,
190
195
  )
191
196
  except asyncio.IncompleteReadError as exc:
192
- raise TransportError(
193
- f"transport handshake: remote closed connection"
194
- ) from exc
197
+ raise self._error("handshake", "remote closed") from exc
195
198
  except asyncio.TimeoutError as exc:
196
- raise TransportError(
197
- f"transport handshake: timeout {self.connect_timeout}s"
199
+ raise self._error(
200
+ "handshake", f"timeout after {self.connect_timeout}s"
198
201
  ) from exc
202
+
199
203
  self._connected = True
200
204
  self._reader_task = asyncio.create_task(
201
205
  self.frame_reader_loop(),
@@ -214,7 +218,7 @@ class AdnlTcpTransport:
214
218
  :param payload: Raw ADNL packet bytes
215
219
  """
216
220
  if not self._connected or self.writer is None:
217
- raise TransportError("transport state: not connected")
221
+ raise NotConnectedError()
218
222
 
219
223
  packet = self._build_frame(payload)
220
224
  encrypted = self.encrypt_frame(packet)
@@ -229,7 +233,7 @@ class AdnlTcpTransport:
229
233
  Blocks until a complete packet is available from the background reader.
230
234
  """
231
235
  if not self._connected:
232
- raise TransportError("transport state: not connected")
236
+ raise NotConnectedError()
233
237
  return await self._incoming.get()
234
238
 
235
239
  async def read_frame(self, discard: bool = False) -> t.Optional[bytes]:
@@ -244,24 +248,24 @@ class AdnlTcpTransport:
244
248
  :param discard: If True, validates but returns None (used for handshake ack)
245
249
  """
246
250
  if self.reader is None:
247
- raise TransportError("transport state: reader is not initialized")
251
+ raise self._error("recv", "reader not initialized")
248
252
 
249
253
  length_enc = await self.reader.readexactly(4)
250
254
  length_dec = self.decrypt_frame(length_enc)
251
255
  data_len = int.from_bytes(length_dec, "little")
252
256
 
253
257
  if data_len <= 0:
254
- raise TransportError(f"transport frame: non-positive length {data_len}")
258
+ raise self._error("recv", f"invalid frame length: {data_len}")
255
259
 
256
260
  data_enc = await self.reader.readexactly(data_len)
257
261
  data = self.decrypt_frame(data_enc)
258
262
 
259
263
  if len(data) < 32:
260
- raise TransportError("transport frame: frame is too short")
264
+ raise self._error("recv", f"frame too short: {len(data)} bytes")
261
265
 
262
266
  payload, checksum = data[:-32], data[-32:]
263
267
  if hashlib.sha256(payload).digest() != checksum:
264
- raise TransportError("transport frame: checksum mismatch")
268
+ raise self._error("recv", "checksum mismatch")
265
269
 
266
270
  if discard:
267
271
  return None
@@ -278,6 +282,7 @@ class AdnlTcpTransport:
278
282
  except asyncio.CancelledError:
279
283
  pass
280
284
  except (
285
+ TransportError,
281
286
  asyncio.IncompleteReadError,
282
287
  ConnectionAbortedError,
283
288
  ConnectionError,
tonutils/clients/base.py CHANGED
@@ -272,7 +272,11 @@ class BaseClient(abc.ABC):
272
272
 
273
273
  blen = len(domain) * 8
274
274
  rlen = t.cast(int, res[0])
275
- cell = t.cast(Cell, res[1])
275
+
276
+ cell = res[1]
277
+
278
+ if cell is None:
279
+ return None
276
280
 
277
281
  if rlen % 8 != 0 or rlen > blen:
278
282
  raise ValueError(f"Invalid resolved length: result {rlen}, bytes {blen}.")
tonutils/exceptions.py CHANGED
@@ -23,7 +23,30 @@ class TonutilsError(Exception):
23
23
 
24
24
 
25
25
  class TransportError(TonutilsError):
26
- """Raise on transport-level failures (I/O, handshake, crypto, socket)."""
26
+ """Transport-level failure with structured context.
27
+
28
+ Covers: TCP connect, ADNL handshake, send/recv, crypto failures.
29
+
30
+ :param endpoint: Server address as "host:port"
31
+ :param operation: What was attempted ("connect", "handshake", "send", "recv")
32
+ :param reason: Why it failed ("timeout 2.0s", "connection refused", etc.)
33
+ """
34
+
35
+ endpoint: str
36
+ operation: str
37
+ reason: str
38
+
39
+ def __init__(
40
+ self,
41
+ *,
42
+ endpoint: str,
43
+ operation: str,
44
+ reason: str,
45
+ ) -> None:
46
+ self.endpoint = endpoint
47
+ self.operation = operation
48
+ self.reason = reason
49
+ super().__init__(f"{operation} failed at {endpoint}: {reason}")
27
50
 
28
51
 
29
52
  class ProviderError(TonutilsError):
@@ -39,23 +62,24 @@ class BalancerError(TonutilsError):
39
62
 
40
63
 
41
64
  class NotConnectedError(TonutilsError, RuntimeError):
42
- """Raise when an operation requires an active connection.
65
+ """Raise when an operation requires an active connection."""
43
66
 
44
- Typically means the underlying client/provider is not connected yet or was closed.
45
- """
67
+ endpoint: t.Optional[str]
46
68
 
47
- def __init__(self) -> None:
48
- super().__init__("not connected. Use `await connect()` or `async with ...`.")
69
+ def __init__(self, endpoint: t.Optional[str] = None) -> None:
70
+ self.endpoint = endpoint
71
+ if endpoint:
72
+ super().__init__(f"not connected to {endpoint}")
73
+ else:
74
+ super().__init__("not connected")
49
75
 
50
76
 
51
77
  class ProviderTimeoutError(ProviderError, asyncio.TimeoutError):
52
78
  """Raise when a provider operation exceeds its timeout.
53
79
 
54
- Used for both ADNL and HTTP providers.
55
-
56
80
  :param timeout: Timeout in seconds.
57
81
  :param endpoint: Endpoint identifier (URL or host:port).
58
- :param operation: Operation label (e.g. "adnl query", "http request").
82
+ :param operation: Operation label (e.g. "request", "connect").
59
83
  """
60
84
 
61
85
  timeout: float
@@ -63,19 +87,15 @@ class ProviderTimeoutError(ProviderError, asyncio.TimeoutError):
63
87
  operation: str
64
88
 
65
89
  def __init__(self, *, timeout: float, endpoint: str, operation: str) -> None:
66
- self.timeout = float(timeout)
90
+ self.timeout = timeout
67
91
  self.endpoint = endpoint
68
92
  self.operation = operation
69
- super().__init__(f"{operation} timed out after {timeout}s: {endpoint}")
93
+ super().__init__(f"{operation} timed out after {timeout}s at {endpoint}")
70
94
 
71
95
 
72
96
  class ProviderResponseError(ProviderError):
73
97
  """Raise when a backend returns an error response.
74
98
 
75
- This is a normalized provider error for:
76
- - HTTP status codes (e.g. 429/5xx)
77
- - lite-server numeric error codes
78
-
79
99
  :param code: Backend code (HTTP status or lite-server code).
80
100
  :param message: Backend error description.
81
101
  :param endpoint: Endpoint identifier (URL or host:port).
@@ -86,7 +106,7 @@ class ProviderResponseError(ProviderError):
86
106
  endpoint: str
87
107
 
88
108
  def __init__(self, *, code: int, message: str, endpoint: str) -> None:
89
- self.code = int(code)
109
+ self.code = code
90
110
  self.message = message
91
111
  self.endpoint = endpoint
92
112
  super().__init__(f"request failed with code {code} at {endpoint}: {message}")
@@ -111,13 +131,10 @@ class RetryLimitError(ProviderError):
111
131
  max_attempts: int,
112
132
  last_error: ProviderError,
113
133
  ) -> None:
114
- self.attempts = int(attempts)
115
- self.max_attempts = int(max_attempts)
134
+ self.attempts = attempts
135
+ self.max_attempts = max_attempts
116
136
  self.last_error = last_error
117
- super().__init__(
118
- f"retry exhausted ({self.attempts}/{self.max_attempts}). "
119
- f"Last error: {last_error}"
120
- )
137
+ super().__init__(f"retry exhausted ({attempts}/{max_attempts}): {last_error}")
121
138
 
122
139
 
123
140
  class ContractError(ClientError):
@@ -133,7 +150,6 @@ class ContractError(ClientError):
133
150
  def __init__(self, target: t.Any, message: str) -> None:
134
151
  self.target = target
135
152
  self.message = message
136
-
137
153
  name = (
138
154
  target.__name__ if isinstance(target, type) else target.__class__.__name__
139
155
  )
@@ -143,10 +159,6 @@ class ContractError(ClientError):
143
159
  class StateNotLoadedError(ContractError):
144
160
  """Raise when a contract wrapper requires state that is not loaded.
145
161
 
146
- Typical cases:
147
- - state_info not fetched
148
- - state_data not decoded/available
149
-
150
162
  :param contract: Contract instance related to the failure.
151
163
  :param missing: Missing field name (e.g. "state_info", "state_data").
152
164
  """
@@ -174,9 +186,9 @@ class RunGetMethodError(ClientError):
174
186
  def __init__(self, *, address: str, method_name: str, exit_code: int) -> None:
175
187
  self.address = address
176
188
  self.method_name = method_name
177
- self.exit_code = int(exit_code)
189
+ self.exit_code = exit_code
178
190
  super().__init__(
179
- f"get-method `{method_name}` failed for {address} (exit code {self.exit_code})."
191
+ f"get-method '{method_name}' failed for {address} with exit code {exit_code}"
180
192
  )
181
193
 
182
194
 
@@ -198,5 +210,3 @@ CDN_CHALLENGE_MARKERS: t.Dict[str, str] = {
198
210
  "503 service unavailable": "Service temporarily unavailable (proxy or CDN).",
199
211
  "ddos": "Possible DDoS protection or mitigation page.",
200
212
  }
201
- """Markers for detecting CDN / proxy challenge and anti-DDoS responses,
202
- used for error normalization and default retry policies."""
File without changes
@@ -0,0 +1,6 @@
1
+ from . import block_scanner, status_monitor
2
+
3
+ __all__ = [
4
+ "block_scanner",
5
+ "status_monitor",
6
+ ]
@@ -0,0 +1,16 @@
1
+ from .events import (
2
+ BlockEvent,
3
+ TransactionEvent,
4
+ TransactionsEvent,
5
+ )
6
+ from .scanner import BlockScanner
7
+ from .where import Where
8
+
9
+
10
+ __all__ = [
11
+ "BlockScanner",
12
+ "BlockEvent",
13
+ "TransactionEvent",
14
+ "TransactionsEvent",
15
+ "Where",
16
+ ]
@@ -0,0 +1,23 @@
1
+ import typing as t
2
+
3
+ from tonutils.tools.block_scanner.events import (
4
+ BlockEvent,
5
+ EventBase,
6
+ TransactionEvent,
7
+ TransactionsEvent,
8
+ )
9
+
10
+ TEvent = t.TypeVar("TEvent", bound=EventBase)
11
+
12
+ Handler = t.Callable[[TEvent], t.Awaitable[None]]
13
+ Where = t.Callable[[TEvent], t.Union[bool, t.Awaitable[bool]]]
14
+
15
+ BlockWhere = t.Callable[[BlockEvent], t.Union[bool, t.Awaitable[bool]]]
16
+ TransactionWhere = t.Callable[[TransactionEvent], t.Union[bool, t.Awaitable[bool]]]
17
+ TransactionsWhere = t.Callable[[TransactionsEvent], t.Union[bool, t.Awaitable[bool]]]
18
+
19
+ AnyHandler = t.Callable[[EventBase], t.Awaitable[None]]
20
+ AnyWhere = t.Callable[[EventBase], t.Union[bool, t.Awaitable[bool]]]
21
+
22
+ HandlerEntry = t.Tuple[AnyHandler, t.Optional[AnyWhere]]
23
+ Decorator = t.Callable[[Handler[TEvent]], Handler[TEvent]]