tonutils 2.0.1b5__py3-none-any.whl → 2.0.1b7__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 (58) hide show
  1. tonutils/__meta__.py +1 -1
  2. tonutils/clients/__init__.py +10 -10
  3. tonutils/clients/adnl/balancer.py +135 -361
  4. tonutils/clients/adnl/client.py +35 -208
  5. tonutils/clients/adnl/mixin.py +268 -0
  6. tonutils/clients/adnl/provider/config.py +22 -7
  7. tonutils/clients/adnl/provider/provider.py +61 -16
  8. tonutils/clients/adnl/provider/transport.py +13 -4
  9. tonutils/clients/adnl/provider/workers/pinger.py +1 -1
  10. tonutils/clients/adnl/utils.py +5 -5
  11. tonutils/clients/base.py +61 -95
  12. tonutils/clients/http/__init__.py +11 -11
  13. tonutils/clients/http/balancer.py +103 -100
  14. tonutils/clients/http/clients/__init__.py +10 -10
  15. tonutils/clients/http/clients/chainstack.py +3 -3
  16. tonutils/clients/http/clients/quicknode.py +2 -3
  17. tonutils/clients/http/clients/tatum.py +4 -3
  18. tonutils/clients/http/clients/tonapi.py +20 -33
  19. tonutils/clients/http/clients/toncenter.py +64 -55
  20. tonutils/clients/http/{providers → provider}/__init__.py +4 -1
  21. tonutils/clients/http/{providers → provider}/base.py +140 -61
  22. tonutils/clients/http/{providers/toncenter → provider}/models.py +44 -2
  23. tonutils/clients/http/{providers/tonapi/provider.py → provider/tonapi.py} +8 -13
  24. tonutils/clients/http/{providers/toncenter/provider.py → provider/toncenter.py} +25 -21
  25. tonutils/clients/limiter.py +61 -59
  26. tonutils/clients/protocol.py +8 -8
  27. tonutils/contracts/base.py +32 -32
  28. tonutils/contracts/protocol.py +9 -9
  29. tonutils/contracts/wallet/base.py +7 -8
  30. tonutils/contracts/wallet/messages.py +4 -8
  31. tonutils/contracts/wallet/versions/v5.py +2 -2
  32. tonutils/exceptions.py +29 -13
  33. tonutils/tonconnect/bridge/__init__.py +0 -0
  34. tonutils/tonconnect/events.py +0 -0
  35. tonutils/tonconnect/models/__init__.py +0 -0
  36. tonutils/tonconnect/storage.py +0 -0
  37. tonutils/tonconnect/tonconnect.py +0 -0
  38. tonutils/tools/block_scanner/__init__.py +2 -5
  39. tonutils/tools/block_scanner/events.py +48 -7
  40. tonutils/tools/block_scanner/scanner.py +316 -222
  41. tonutils/tools/block_scanner/storage.py +11 -0
  42. tonutils/tools/status_monitor/monitor.py +6 -6
  43. tonutils/types.py +2 -2
  44. tonutils/utils.py +0 -48
  45. {tonutils-2.0.1b5.dist-info → tonutils-2.0.1b7.dist-info}/METADATA +3 -18
  46. {tonutils-2.0.1b5.dist-info → tonutils-2.0.1b7.dist-info}/RECORD +50 -51
  47. {tonutils-2.0.1b5.dist-info → tonutils-2.0.1b7.dist-info}/WHEEL +1 -1
  48. tonutils/clients/http/providers/response.py +0 -85
  49. tonutils/clients/http/providers/tonapi/__init__.py +0 -3
  50. tonutils/clients/http/providers/tonapi/models.py +0 -47
  51. tonutils/clients/http/providers/toncenter/__init__.py +0 -3
  52. tonutils/tools/block_scanner/annotations.py +0 -23
  53. tonutils/tools/block_scanner/dispatcher.py +0 -141
  54. tonutils/tools/block_scanner/traversal.py +0 -97
  55. tonutils/tools/block_scanner/where.py +0 -53
  56. {tonutils-2.0.1b5.dist-info → tonutils-2.0.1b7.dist-info}/entry_points.txt +0 -0
  57. {tonutils-2.0.1b5.dist-info → tonutils-2.0.1b7.dist-info}/licenses/LICENSE +0 -0
  58. {tonutils-2.0.1b5.dist-info → tonutils-2.0.1b7.dist-info}/top_level.txt +0 -0
@@ -2,40 +2,37 @@ from __future__ import annotations
2
2
 
3
3
  import typing as t
4
4
 
5
- from pytoniq_core import Address, BlockIdExt, Block, Transaction
6
-
5
+ from tonutils.clients.adnl.mixin import LiteMixin
7
6
  from tonutils.clients.adnl.provider import AdnlProvider
8
7
  from tonutils.clients.adnl.provider.config import (
9
8
  get_mainnet_global_config,
10
9
  get_testnet_global_config,
10
+ load_global_config,
11
11
  )
12
12
  from tonutils.clients.adnl.provider.models import (
13
13
  LiteServer,
14
14
  GlobalConfig,
15
- MasterchainInfo,
16
15
  )
17
- from tonutils.clients.adnl.utils import decode_stack, encode_stack
18
16
  from tonutils.clients.base import BaseClient
19
17
  from tonutils.clients.limiter import RateLimiter
18
+ from tonutils.exceptions import NotConnectedError, ClientError
20
19
  from tonutils.types import (
21
20
  BinaryLike,
22
21
  ClientType,
23
- ContractStateInfo,
24
22
  NetworkGlobalID,
25
23
  RetryPolicy,
26
- WorkchainID,
27
24
  )
28
25
 
29
26
 
30
- class LiteClient(BaseClient):
27
+ class LiteClient(LiteMixin, BaseClient):
31
28
  """TON blockchain client for lite-server communication over ADNL provider."""
32
29
 
33
30
  TYPE = ClientType.ADNL
34
31
 
35
32
  def __init__(
36
33
  self,
37
- *,
38
34
  network: NetworkGlobalID = NetworkGlobalID.MAINNET,
35
+ *,
39
36
  ip: t.Union[str, int],
40
37
  port: int,
41
38
  public_key: BinaryLike,
@@ -96,32 +93,20 @@ class LiteClient(BaseClient):
96
93
  return self._provider
97
94
 
98
95
  @property
99
- def is_connected(self) -> bool:
96
+ def connected(self) -> bool:
100
97
  """
101
98
  Check whether the lite-server connection is established.
102
99
 
103
100
  :return: True if connected, False otherwise
104
101
  """
105
- return self._provider.is_connected
106
-
107
- async def __aenter__(self) -> LiteClient:
108
- await self.connect()
109
- return self
110
-
111
- async def __aexit__(
112
- self,
113
- exc_type: t.Optional[t.Type[BaseException]],
114
- exc_value: t.Optional[BaseException],
115
- traceback: t.Optional[t.Any],
116
- ) -> None:
117
- await self.close()
102
+ return self._provider.connected
118
103
 
119
104
  @classmethod
120
105
  def from_config(
121
106
  cls,
122
- *,
123
107
  network: NetworkGlobalID = NetworkGlobalID.MAINNET,
124
- config: t.Union[GlobalConfig, t.Dict[str, t.Any]],
108
+ *,
109
+ config: t.Union[GlobalConfig, t.Dict[str, t.Any], str],
125
110
  index: int,
126
111
  connect_timeout: float = 2.0,
127
112
  request_timeout: float = 10.0,
@@ -141,7 +126,7 @@ class LiteClient(BaseClient):
141
126
  Public free configs may also be used, but may be unstable under load.
142
127
 
143
128
  :param network: Target TON network
144
- :param config: GlobalConfig instance or raw dict
129
+ :param config: GlobalConfig instance, config file path as string, or raw dict
145
130
  :param index: Index of lite-server entry in the configuration
146
131
  :param connect_timeout: Timeout in seconds for connect/handshake performed
147
132
  by this client.
@@ -152,9 +137,20 @@ class LiteClient(BaseClient):
152
137
  :param retry_policy: Optional retry policy that defines per-error-code retry rules
153
138
  :return: Configured LiteClient instance
154
139
  """
140
+ if isinstance(config, str):
141
+ config = load_global_config(config)
155
142
  if isinstance(config, dict):
156
143
  config = GlobalConfig(**config)
144
+
145
+ liteservers = config.liteservers
146
+ if not 0 <= index < len(liteservers):
147
+ raise ClientError(
148
+ f"{cls.__name__}.from_config: "
149
+ f"liteserver index {index} is out of range "
150
+ f"(available: 0..{len(liteservers) - 1})."
151
+ )
157
152
  ls = config.liteservers[index]
153
+
158
154
  return cls(
159
155
  network=network,
160
156
  ip=ls.host,
@@ -170,8 +166,8 @@ class LiteClient(BaseClient):
170
166
  @classmethod
171
167
  def from_network_config(
172
168
  cls,
173
- *,
174
169
  network: NetworkGlobalID = NetworkGlobalID.MAINNET,
170
+ *,
175
171
  index: int,
176
172
  connect_timeout: float = 2.0,
177
173
  request_timeout: float = 10.0,
@@ -216,197 +212,28 @@ class LiteClient(BaseClient):
216
212
  retry_policy=retry_policy,
217
213
  )
218
214
 
219
- async def _send_boc(self, boc: str) -> None:
220
- return await self.provider.send_message(bytes.fromhex(boc))
221
-
222
- async def _get_blockchain_config(self) -> t.Dict[int, t.Any]:
223
- return await self.provider.get_config_all()
224
-
225
- async def _get_contract_info(self, address: str) -> ContractStateInfo:
226
- return await self.provider.get_account_state(Address(address))
227
-
228
- async def _get_contract_transactions(
229
- self,
230
- address: str,
231
- limit: int = 100,
232
- from_lt: t.Optional[int] = None,
233
- to_lt: int = 0,
234
- ) -> t.List[Transaction]:
235
- state = await self._get_contract_info(address)
236
- account = Address(address).to_tl_account_id()
237
-
238
- if state.last_transaction_lt is None or state.last_transaction_hash is None:
239
- return []
240
-
241
- curr_lt = state.last_transaction_lt
242
- curr_hash = state.last_transaction_hash
243
- transactions: t.List[Transaction] = []
244
-
245
- while len(transactions) < limit and curr_lt != 0:
246
- batch_size = min(16, limit - len(transactions))
247
-
248
- txs = await self.provider.get_transactions(
249
- account=account,
250
- count=batch_size,
251
- from_lt=curr_lt,
252
- from_hash=curr_hash,
253
- )
254
- if not txs:
255
- break
256
-
257
- if to_lt > 0 and txs[-1].lt <= to_lt:
258
- trimmed: t.List[Transaction] = []
259
- for tx in txs:
260
- if tx.lt <= to_lt:
261
- break
262
- trimmed.append(tx)
263
- transactions.extend(trimmed)
264
- break
265
-
266
- transactions.extend(txs)
267
-
268
- last_tx = txs[-1]
269
- curr_lt = last_tx.prev_trans_lt
270
- curr_hash = last_tx.prev_trans_hash.hex()
271
-
272
- return (
273
- [tx for tx in transactions if tx.lt < from_lt]
274
- if from_lt is not None
275
- else transactions
276
- )
277
-
278
- async def _run_get_method(
279
- self,
280
- address: str,
281
- method_name: str,
282
- stack: t.Optional[t.List[t.Any]] = None,
283
- ) -> t.List[t.Any]:
284
- result = await self.provider.run_smc_method(
285
- address=Address(address),
286
- method_name=method_name,
287
- stack=encode_stack(stack or []),
288
- )
289
- return decode_stack(result or [])
290
-
291
215
  async def connect(self) -> None:
292
216
  """Establish connection to the lite-server."""
293
217
  await self.provider.connect()
294
218
 
295
- async def reconnect(self) -> None:
296
- """Force reconnection to the lite-server."""
297
- await self.provider.reconnect()
298
-
299
219
  async def close(self) -> None:
300
220
  """Close the lite-server connection."""
301
221
  await self.provider.close()
302
222
 
303
- async def get_time(self) -> int:
304
- """
305
- Fetch current network time from lite-server.
306
-
307
- :return: Current UNIX timestamp
308
- """
309
- return await self.provider.get_time()
310
-
311
- async def get_version(self) -> int:
312
- """
313
- Fetch lite-server protocol version.
314
-
315
- :return: Version number
316
- """
317
- return await self.provider.get_version()
318
-
319
- async def wait_masterchain_seqno(
320
- self,
321
- seqno: int,
322
- timeout_ms: int,
323
- schema_name: str,
324
- data: t.Optional[dict] = None,
325
- ) -> dict:
326
- """
327
- Combine waitMasterchainSeqno with another lite-server query.
328
-
329
- :param seqno: Masterchain seqno to wait for
330
- :param timeout_ms: Wait timeout in milliseconds
331
- :param schema_name: Lite-server TL method name without prefix
332
- :param data: Additional method arguments
333
- :return: Lite-server response as dictionary
334
- """
335
- return await self.provider.wait_masterchain_seqno(
336
- seqno=seqno,
337
- timeout_ms=timeout_ms,
338
- schema_name=schema_name,
339
- data=data,
340
- )
341
-
342
- async def get_masterchain_info(self) -> MasterchainInfo:
343
- """
344
- Fetch basic masterchain information.
345
-
346
- :return: MasterchainInfo instance
347
- """
348
- return await self.provider.get_masterchain_info()
349
-
350
- async def lookup_block(
351
- self,
352
- workchain: WorkchainID,
353
- shard: int,
354
- seqno: t.Optional[int] = None,
355
- lt: t.Optional[int] = None,
356
- utime: t.Optional[int] = None,
357
- ) -> t.Tuple[BlockIdExt, Block]:
223
+ async def _adnl_call(self, method: str, /, *args: t.Any, **kwargs: t.Any) -> t.Any:
358
224
  """
359
- Locate a block by workchain/shard and one of seqno/lt/utime.
225
+ Execute lite-server call using the current provider.
360
226
 
361
- :param workchain: Workchain identifier
362
- :param shard: Shard identifier
363
- :param seqno: Block sequence number
364
- :param lt: Logical time filter
365
- :param utime: UNIX time filter
366
- :return: Tuple of BlockIdExt and deserialized Block
367
- """
368
- return await self.provider.lookup_block(
369
- workchain=workchain,
370
- shard=shard,
371
- seqno=seqno,
372
- lt=lt,
373
- utime=utime,
374
- )
375
-
376
- async def get_block_header(
377
- self,
378
- block: BlockIdExt,
379
- ) -> t.Tuple[BlockIdExt, Block]:
227
+ :param method: Provider coroutine method name.
228
+ :param args: Positional arguments forwarded to the provider method.
229
+ :param kwargs: Keyword arguments forwarded to the provider method.
230
+ :return: Provider method result.
380
231
  """
381
- Fetch and deserialize block header by BlockIdExt.
382
-
383
- :param block: BlockIdExt to query
384
- :return: Tuple of BlockIdExt and deserialized Block
385
- """
386
- return await self.provider.get_block_header(block)
387
-
388
- async def get_block_transactions_ext(
389
- self,
390
- block: BlockIdExt,
391
- count: int = 1024,
392
- ) -> t.List[Transaction]:
393
- """
394
- Fetch extended block transactions list.
395
-
396
- :param block: Target block identifier
397
- :param count: Maximum number of transactions per request
398
- :return: List of deserialized Transaction objects
399
- """
400
- return await self.provider.get_block_transactions_ext(block, count=count)
401
-
402
- async def get_all_shards_info(
403
- self,
404
- block: t.Optional[BlockIdExt] = None,
405
- ) -> t.List[BlockIdExt]:
406
- """
407
- Fetch shard info for all workchains at a given masterchain block.
232
+ if not self.connected:
233
+ raise NotConnectedError(
234
+ component=self.__class__.__name__,
235
+ operation=method,
236
+ )
408
237
 
409
- :param block: Masterchain block ID or None to use latest
410
- :return: List of shard BlockIdExt objects
411
- """
412
- return await self.provider.get_all_shards_info(block)
238
+ fn = getattr(self.provider, method)
239
+ return await fn(*args, **kwargs)
@@ -0,0 +1,268 @@
1
+ import typing as t
2
+
3
+ from pytoniq_core import Address, Block, BlockIdExt, Transaction, Account, ShardAccount
4
+
5
+ from tonutils.clients.adnl.provider.models import MasterchainInfo
6
+ from tonutils.clients.adnl.utils import decode_stack, encode_stack
7
+ from tonutils.types import ContractInfo, WorkchainID, AddressLike
8
+
9
+
10
+ class LiteMixin:
11
+
12
+ async def _adnl_call(
13
+ self,
14
+ method: str,
15
+ /,
16
+ *args: t.Any,
17
+ **kwargs: t.Any,
18
+ ) -> t.Any:
19
+ """
20
+ Execute a single lite-server provider call.
21
+
22
+ This is an internal hook used by the mixin to unify implementations between
23
+ LiteClient (direct call) and LiteBalancer (failover / retry call).
24
+
25
+ :param method: Provider coroutine method name (e.g. "get_time", "lookup_block").
26
+ :param args: Positional arguments forwarded to the provider method.
27
+ :param kwargs: Keyword arguments forwarded to the provider method.
28
+ :return: Provider method result.
29
+ """
30
+ raise NotImplementedError("LiteMixin requires `_adnl_call()` implementation.")
31
+
32
+ async def _send_message(self, boc: str) -> None:
33
+ method = "send_message"
34
+ await self._adnl_call(method, bytes.fromhex(boc))
35
+
36
+ async def _get_config(self) -> t.Dict[int, t.Any]:
37
+ method = "get_config"
38
+ return t.cast(t.Dict[int, t.Any], await self._adnl_call(method))
39
+
40
+ async def _get_info(self, address: str) -> ContractInfo:
41
+ method = "get_info"
42
+ return t.cast(
43
+ ContractInfo,
44
+ await self._adnl_call(method, Address(address)),
45
+ )
46
+
47
+ async def _run_get_method(
48
+ self,
49
+ address: str,
50
+ method_name: str,
51
+ stack: t.Optional[t.List[t.Any]] = None,
52
+ ) -> t.List[t.Any]:
53
+ method = "run_get_method"
54
+ res = await self._adnl_call(
55
+ method,
56
+ address=Address(address),
57
+ method_name=method_name,
58
+ stack=encode_stack(stack or []),
59
+ )
60
+ return decode_stack(res or [])
61
+
62
+ async def _get_transactions(
63
+ self,
64
+ address: str,
65
+ limit: int = 100,
66
+ from_lt: t.Optional[int] = None,
67
+ to_lt: t.Optional[int] = None,
68
+ ) -> t.List[Transaction]:
69
+ method = "get_transactions"
70
+
71
+ to_lt_i = 0 if to_lt is None else to_lt
72
+ state = await self._get_info(address)
73
+ account = Address(address).to_tl_account_id()
74
+
75
+ if state.last_transaction_lt is None or state.last_transaction_hash is None:
76
+ return []
77
+
78
+ curr_lt = state.last_transaction_lt
79
+ curr_hash = state.last_transaction_hash
80
+ out: t.List[Transaction] = []
81
+
82
+ while curr_lt != 0:
83
+ fetch_lt = curr_lt
84
+ fetch_hash = curr_hash
85
+
86
+ txs = t.cast(
87
+ t.List[Transaction],
88
+ await self._adnl_call(
89
+ method,
90
+ account=account,
91
+ count=16,
92
+ from_lt=fetch_lt,
93
+ from_hash=fetch_hash,
94
+ ),
95
+ )
96
+ if not txs:
97
+ break
98
+
99
+ for tx in txs:
100
+ if from_lt is not None and tx.lt > from_lt:
101
+ continue
102
+ if to_lt_i > 0 and tx.lt <= to_lt_i:
103
+ return out[:limit]
104
+
105
+ out.append(tx)
106
+ if len(out) >= limit:
107
+ return out
108
+
109
+ last_tx = txs[-1]
110
+ curr_lt = last_tx.prev_trans_lt
111
+ curr_hash = last_tx.prev_trans_hash.hex()
112
+
113
+ return out[:limit]
114
+
115
+ async def get_time(self) -> int:
116
+ """
117
+ Fetch current network time from lite-server.
118
+
119
+ :return: Current UNIX timestamp
120
+ """
121
+ method = "get_time"
122
+ return t.cast(int, await self._adnl_call(method))
123
+
124
+ async def get_version(self) -> int:
125
+ """
126
+ Fetch lite-server protocol version.
127
+
128
+ :return: Version number
129
+ """
130
+ method = "get_version"
131
+ return t.cast(int, await self._adnl_call(method))
132
+
133
+ async def get_masterchain_info(self) -> MasterchainInfo:
134
+ """
135
+ Fetch basic masterchain information.
136
+
137
+ :return: MasterchainInfo instance
138
+ """
139
+ method = "get_masterchain_info"
140
+ return t.cast(MasterchainInfo, await self._adnl_call(method))
141
+
142
+ async def wait_masterchain_seqno(
143
+ self,
144
+ seqno: int,
145
+ timeout_ms: int,
146
+ schema_name: str,
147
+ data: t.Optional[dict] = None,
148
+ ) -> t.Dict[str, t.Any]:
149
+ """
150
+ Combine waitMasterchainSeqno with another lite-server query.
151
+
152
+ :param seqno: Masterchain seqno to wait for
153
+ :param timeout_ms: Wait timeout in milliseconds
154
+ :param schema_name: Lite-server TL method name without prefix
155
+ :param data: Additional method arguments
156
+ :return: Lite-server response as dictionary
157
+ """
158
+ method = "wait_masterchain_seqno"
159
+ return t.cast(
160
+ t.Dict[str, t.Any],
161
+ await self._adnl_call(
162
+ method,
163
+ seqno=seqno,
164
+ timeout_ms=timeout_ms,
165
+ schema_name=schema_name,
166
+ data=data,
167
+ ),
168
+ )
169
+
170
+ async def lookup_block(
171
+ self,
172
+ workchain: WorkchainID,
173
+ shard: int,
174
+ seqno: t.Optional[int] = None,
175
+ lt: t.Optional[int] = None,
176
+ utime: t.Optional[int] = None,
177
+ ) -> t.Tuple[BlockIdExt, Block]:
178
+ """
179
+ Locate a block by workchain/shard and one of seqno/lt/utime.
180
+
181
+ :param workchain: Workchain identifier
182
+ :param shard: Shard identifier
183
+ :param seqno: Block sequence number
184
+ :param lt: Logical time filter
185
+ :param utime: UNIX time filter
186
+ :return: Tuple of BlockIdExt and deserialized Block
187
+ """
188
+ method = "lookup_block"
189
+ return t.cast(
190
+ t.Tuple[BlockIdExt, Block],
191
+ await self._adnl_call(
192
+ method,
193
+ workchain=workchain,
194
+ shard=shard,
195
+ seqno=seqno,
196
+ lt=lt,
197
+ utime=utime,
198
+ ),
199
+ )
200
+
201
+ async def get_block_header(
202
+ self,
203
+ block: BlockIdExt,
204
+ ) -> t.Tuple[BlockIdExt, Block]:
205
+ """
206
+ Fetch and deserialize block header by BlockIdExt.
207
+
208
+ :param block: BlockIdExt to query
209
+ :return: Tuple of BlockIdExt and deserialized Block
210
+ """
211
+ method = "get_block_header"
212
+ return t.cast(
213
+ t.Tuple[BlockIdExt, Block],
214
+ await self._adnl_call(method, block),
215
+ )
216
+
217
+ async def get_block_transactions(
218
+ self,
219
+ block: BlockIdExt,
220
+ count: int = 1024,
221
+ ) -> t.List[Transaction]:
222
+ """
223
+ Fetch extended block transactions list.
224
+
225
+ :param block: Target block identifier
226
+ :param count: Maximum number of transactions per request
227
+ :return: List of deserialized Transaction objects
228
+ """
229
+ method = "get_block_transactions"
230
+ return t.cast(
231
+ t.List[Transaction],
232
+ await self._adnl_call(method, block, count=count),
233
+ )
234
+
235
+ async def get_all_shards_info(
236
+ self,
237
+ block: t.Optional[BlockIdExt] = None,
238
+ ) -> t.List[BlockIdExt]:
239
+ """
240
+ Fetch shard info for all workchains at a given masterchain block.
241
+
242
+ :param block: Masterchain block ID or None to use latest
243
+ :return: List of shard BlockIdExt objects
244
+ """
245
+ method = "get_all_shards_info"
246
+ return t.cast(
247
+ t.List[BlockIdExt],
248
+ await self._adnl_call(method, block),
249
+ )
250
+
251
+ async def get_account_state(
252
+ self,
253
+ address: AddressLike,
254
+ ) -> t.Tuple[t.Optional[Account], t.Optional[ShardAccount]]:
255
+ """
256
+ Fetch account state and shard account from lite-server.
257
+
258
+ :param address: Contract address as Address object or string
259
+ :return: Tuple of (Account | None, ShardAccount | None)
260
+ """
261
+ if isinstance(address, str):
262
+ address = Address(address)
263
+
264
+ method = "get_account_state"
265
+ return t.cast(
266
+ t.Tuple[t.Optional[Account], t.Optional[ShardAccount]],
267
+ await self._adnl_call(method, address),
268
+ )
@@ -1,18 +1,33 @@
1
1
  import json
2
2
  import urllib.request
3
3
  from pathlib import Path
4
+ from urllib.error import HTTPError, URLError
5
+
6
+ from pydantic import ValidationError
4
7
 
5
8
  from tonutils.clients.adnl.provider.models import GlobalConfig
6
9
 
7
10
 
8
11
  def load_global_config(source: str) -> GlobalConfig:
9
- if source.startswith(("http://", "https://")):
10
- with urllib.request.urlopen(source) as response:
11
- data = json.loads(response.read().decode())
12
- else:
13
- data = json.loads(Path(source).read_text())
14
-
15
- return GlobalConfig.model_validate(data)
12
+ try:
13
+ if source.startswith(("http://", "https://")):
14
+ with urllib.request.urlopen(source) as r:
15
+ data = json.loads(r.read().decode("utf-8"))
16
+ else:
17
+ data = json.loads(Path(source).read_text(encoding="utf-8"))
18
+ return GlobalConfig.model_validate(data)
19
+ except HTTPError as e:
20
+ raise RuntimeError(f"Config fetch failed: {e} ({source})") from e
21
+ except URLError as e:
22
+ raise RuntimeError(f"Config fetch failed: {e.reason} ({source})") from e
23
+ except json.JSONDecodeError as e:
24
+ raise RuntimeError(f"Config JSON is invalid: {e.msg} ({source})") from e
25
+ except ValidationError as e:
26
+ raise RuntimeError(f"Config validation failed: {e} ({source})") from e
27
+ except OSError as e:
28
+ raise RuntimeError(f"Config read failed: {e} ({source})") from e
29
+ except Exception as e:
30
+ raise RuntimeError(f"Config load failed: {e} ({source})") from e
16
31
 
17
32
 
18
33
  def get_mainnet_global_config() -> GlobalConfig: