tonutils 2.0.1b2__py3-none-any.whl → 2.0.1b3__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 (76) hide show
  1. tonutils/__init__.py +0 -2
  2. tonutils/__meta__.py +1 -1
  3. tonutils/clients/__init__.py +5 -9
  4. tonutils/clients/adnl/__init__.py +5 -1
  5. tonutils/clients/adnl/balancer.py +319 -125
  6. tonutils/clients/adnl/client.py +187 -51
  7. tonutils/clients/adnl/provider/config.py +19 -25
  8. tonutils/clients/adnl/provider/models.py +4 -0
  9. tonutils/clients/adnl/provider/provider.py +191 -145
  10. tonutils/clients/adnl/provider/transport.py +38 -32
  11. tonutils/clients/adnl/provider/workers/base.py +0 -2
  12. tonutils/clients/adnl/provider/workers/pinger.py +1 -1
  13. tonutils/clients/adnl/provider/workers/reader.py +3 -2
  14. tonutils/clients/adnl/{provider/builder.py → utils.py} +62 -2
  15. tonutils/clients/http/__init__.py +11 -8
  16. tonutils/clients/http/balancer.py +75 -63
  17. tonutils/clients/http/clients/__init__.py +13 -0
  18. tonutils/clients/http/clients/chainstack.py +48 -0
  19. tonutils/clients/http/clients/quicknode.py +47 -0
  20. tonutils/clients/http/clients/tatum.py +56 -0
  21. tonutils/clients/http/{tonapi/client.py → clients/tonapi.py} +31 -31
  22. tonutils/clients/http/{toncenter/client.py → clients/toncenter.py} +59 -48
  23. tonutils/clients/http/providers/__init__.py +4 -0
  24. tonutils/clients/http/providers/base.py +201 -0
  25. tonutils/clients/http/providers/response.py +85 -0
  26. tonutils/clients/http/providers/tonapi/__init__.py +3 -0
  27. tonutils/clients/http/{tonapi → providers/tonapi}/models.py +1 -0
  28. tonutils/clients/http/providers/tonapi/provider.py +125 -0
  29. tonutils/clients/http/providers/toncenter/__init__.py +3 -0
  30. tonutils/clients/http/{toncenter → providers/toncenter}/models.py +1 -0
  31. tonutils/clients/http/providers/toncenter/provider.py +119 -0
  32. tonutils/clients/http/utils.py +140 -0
  33. tonutils/clients/limiter.py +115 -0
  34. tonutils/contracts/__init__.py +4 -0
  35. tonutils/contracts/base.py +33 -20
  36. tonutils/contracts/dns/methods.py +2 -2
  37. tonutils/contracts/jetton/methods.py +2 -2
  38. tonutils/contracts/nft/methods.py +2 -2
  39. tonutils/contracts/nft/tlb.py +1 -1
  40. tonutils/{protocols/contract.py → contracts/protocol.py} +29 -29
  41. tonutils/contracts/telegram/methods.py +2 -2
  42. tonutils/contracts/vanity/vanity.py +1 -1
  43. tonutils/contracts/wallet/__init__.py +2 -0
  44. tonutils/contracts/wallet/base.py +3 -3
  45. tonutils/contracts/wallet/messages.py +1 -1
  46. tonutils/contracts/wallet/methods.py +2 -2
  47. tonutils/{protocols/wallet.py → contracts/wallet/protocol.py} +35 -35
  48. tonutils/contracts/wallet/versions/v5.py +3 -3
  49. tonutils/exceptions.py +134 -226
  50. tonutils/types.py +115 -0
  51. tonutils/utils.py +3 -3
  52. {tonutils-2.0.1b2.dist-info → tonutils-2.0.1b3.dist-info}/METADATA +2 -2
  53. tonutils-2.0.1b3.dist-info/RECORD +93 -0
  54. tonutils/clients/adnl/provider/limiter.py +0 -56
  55. tonutils/clients/adnl/stack.py +0 -64
  56. tonutils/clients/http/chainstack/__init__.py +0 -4
  57. tonutils/clients/http/chainstack/client.py +0 -63
  58. tonutils/clients/http/chainstack/provider.py +0 -44
  59. tonutils/clients/http/quicknode/__init__.py +0 -4
  60. tonutils/clients/http/quicknode/client.py +0 -60
  61. tonutils/clients/http/quicknode/provider.py +0 -42
  62. tonutils/clients/http/tatum/__init__.py +0 -4
  63. tonutils/clients/http/tatum/client.py +0 -66
  64. tonutils/clients/http/tatum/provider.py +0 -53
  65. tonutils/clients/http/tonapi/__init__.py +0 -4
  66. tonutils/clients/http/tonapi/provider.py +0 -150
  67. tonutils/clients/http/tonapi/stack.py +0 -71
  68. tonutils/clients/http/toncenter/__init__.py +0 -4
  69. tonutils/clients/http/toncenter/provider.py +0 -145
  70. tonutils/clients/http/toncenter/stack.py +0 -73
  71. tonutils/protocols/__init__.py +0 -9
  72. tonutils-2.0.1b2.dist-info/RECORD +0 -98
  73. /tonutils/{protocols/client.py → clients/protocol.py} +0 -0
  74. {tonutils-2.0.1b2.dist-info → tonutils-2.0.1b3.dist-info}/WHEEL +0 -0
  75. {tonutils-2.0.1b2.dist-info → tonutils-2.0.1b3.dist-info}/licenses/LICENSE +0 -0
  76. {tonutils-2.0.1b2.dist-info → tonutils-2.0.1b3.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,119 @@
1
+ import typing as t
2
+
3
+ import aiohttp
4
+ from pydantic import BaseModel
5
+
6
+ from tonutils.clients.http.providers.base import HttpProvider
7
+ from tonutils.clients.http.providers.toncenter.models import (
8
+ GetAddressInformationResult,
9
+ GetConfigAllResult,
10
+ GetTransactionResult,
11
+ RunGetMethodResul,
12
+ SendBocPayload,
13
+ RunGetMethodPayload,
14
+ )
15
+ from tonutils.types import NetworkGlobalID, RetryPolicy
16
+
17
+
18
+ class ToncenterHttpProvider(HttpProvider):
19
+
20
+ def __init__(
21
+ self,
22
+ network: NetworkGlobalID,
23
+ api_key: t.Optional[str] = None,
24
+ base_url: t.Optional[str] = None,
25
+ timeout: float = 10.0,
26
+ session: t.Optional[aiohttp.ClientSession] = None,
27
+ headers: t.Optional[t.Dict[str, str]] = None,
28
+ cookies: t.Optional[t.Dict[str, str]] = None,
29
+ rps_limit: t.Optional[int] = None,
30
+ rps_period: float = 1.0,
31
+ retry_policy: t.Optional[RetryPolicy] = None,
32
+ ) -> None:
33
+ urls = {
34
+ NetworkGlobalID.MAINNET: "https://toncenter.com/api/v2",
35
+ NetworkGlobalID.TESTNET: "https://testnet.toncenter.com/api/v2",
36
+ }
37
+ base_url = base_url or urls[network]
38
+ headers = {**(headers or {}), **({"X-Api-Key": api_key} if api_key else {})}
39
+ super().__init__(
40
+ base_url=base_url,
41
+ session=session,
42
+ headers=headers,
43
+ cookies=cookies,
44
+ timeout=timeout,
45
+ rps_limit=rps_limit,
46
+ rps_period=rps_period,
47
+ retry_policy=retry_policy,
48
+ )
49
+
50
+ @staticmethod
51
+ def _model(model: t.Type[BaseModel], data: t.Any) -> t.Any:
52
+ return model.model_validate(data)
53
+
54
+ async def send_boc(
55
+ self,
56
+ payload: SendBocPayload,
57
+ ) -> None:
58
+ await self.send_http_request(
59
+ "POST",
60
+ "/sendBoc",
61
+ json_data=payload.model_dump(),
62
+ )
63
+
64
+ async def get_config_all(
65
+ self,
66
+ ) -> GetConfigAllResult:
67
+ return self._model(
68
+ GetConfigAllResult,
69
+ await self.send_http_request(
70
+ "GET",
71
+ "/getConfigAll",
72
+ ),
73
+ )
74
+
75
+ async def get_address_information(
76
+ self,
77
+ address: str,
78
+ ) -> GetAddressInformationResult:
79
+ return self._model(
80
+ GetAddressInformationResult,
81
+ await self.send_http_request(
82
+ "GET",
83
+ "/getAddressInformation",
84
+ params={"address": address},
85
+ ),
86
+ )
87
+
88
+ async def get_transaction(
89
+ self,
90
+ address: str,
91
+ limit: int = 100,
92
+ from_lt: t.Optional[int] = None,
93
+ to_lt: int = 0,
94
+ ) -> GetTransactionResult:
95
+ params = {"address": address, "limit": limit, "to_lt": to_lt}
96
+ if from_lt is not None:
97
+ params["from_lt"] = from_lt
98
+
99
+ return self._model(
100
+ GetTransactionResult,
101
+ await self.send_http_request(
102
+ "GET",
103
+ "/getTransactions",
104
+ params=params,
105
+ ),
106
+ )
107
+
108
+ async def run_get_method(
109
+ self,
110
+ payload: RunGetMethodPayload,
111
+ ) -> RunGetMethodResul:
112
+ return self._model(
113
+ RunGetMethodResul,
114
+ await self.send_http_request(
115
+ "POST",
116
+ "/runGetMethod",
117
+ json_data=payload.model_dump(),
118
+ ),
119
+ )
@@ -0,0 +1,140 @@
1
+ from __future__ import annotations
2
+
3
+ import typing as t
4
+
5
+ from pytoniq_core import Address, Cell, Slice
6
+
7
+ from tonutils.types import StackTag, StackItem, StackItems
8
+ from tonutils.utils import cell_to_b64, cell_to_hex, norm_stack_num, norm_stack_cell
9
+
10
+
11
+ def decode_toncenter_stack(items: t.List[t.Any]) -> StackItems:
12
+ """
13
+ Decode Toncenter TVM stack format into internal python structures.
14
+
15
+ Converts Toncenter stack elements into native Python values:
16
+ - NUM → int
17
+ - CELL/SLICE → Cell or Address (auto-detected)
18
+ - LIST/TUPLE → recursively decoded lists
19
+ - NULL → None
20
+
21
+ :param items: Raw stack items as returned by Toncenter
22
+ :return: Normalized list of decoded stack values
23
+ """
24
+ out: StackItems = []
25
+ for item in items:
26
+ if not (isinstance(item, list) and len(item) == 2):
27
+ continue
28
+ tag, payload = item
29
+ if tag == StackTag.NULL:
30
+ out.append(None)
31
+ elif tag == StackTag.NUM.value:
32
+ out.append(norm_stack_num(payload))
33
+ elif tag in (
34
+ StackTag.CELL.value,
35
+ StackTag.TVM_CELL.value,
36
+ StackTag.SLICE.value,
37
+ StackTag.TVM_SLICE.value,
38
+ ):
39
+ out.append(norm_stack_cell((payload or {}).get("bytes")))
40
+ elif tag in (StackTag.LIST.value, StackTag.TUPLE.value):
41
+ elements = (payload or {}).get("elements") or []
42
+ out.append(decode_toncenter_stack(elements) if len(elements) > 0 else None)
43
+ return out
44
+
45
+
46
+ def encode_toncenter_stack(items: t.List[StackItem]) -> t.List[list]:
47
+ """
48
+ Encode Python stack values into Toncenter-compatible TVM stack format.
49
+
50
+ Supports:
51
+ - int → NUM
52
+ - Cell → TVM_CELL
53
+ - Slice → TVM_SLICE
54
+ - list/tuple → LIST/TUPLE (recursive)
55
+ - Address → encoded as TVM_CELL
56
+
57
+ :param items: List of python TVM stack values
58
+ :return: Encoded stack items suitable for Toncenter API
59
+ """
60
+ out: t.List[t.Any] = []
61
+ for item in items:
62
+ tpe = StackTag.of(item)
63
+ if tpe is StackTag.NUM:
64
+ out.append([StackTag.NUM.value, str(t.cast(str, item))])
65
+ elif tpe is StackTag.CELL:
66
+ cell = item.to_cell() if isinstance(item, Address) else t.cast(Cell, item)
67
+ out.append([StackTag.TVM_CELL.value, cell_to_b64(cell)])
68
+ elif tpe is StackTag.SLICE:
69
+ cell = t.cast(Slice, item).to_cell()
70
+ out.append([StackTag.TVM_SLICE.value, cell_to_b64(cell)])
71
+ elif tpe in (StackTag.LIST, StackTag.TUPLE):
72
+ out.append(
73
+ [tpe.value, {"elements": encode_toncenter_stack(t.cast(list, item))}]
74
+ )
75
+ return out
76
+
77
+
78
+ def decode_tonapi_stack(items: t.List[t.Any]) -> StackItems:
79
+ """
80
+ Decode Tonapi TVM stack format into internal Python structures.
81
+
82
+ Tonapi represents stack items as dictionaries with a "type" key and
83
+ a value under the same key. This function converts them into:
84
+ - NUM → int
85
+ - CELL/SLICE → Cell or Address (auto-detected)
86
+ - LIST/TUPLE → nested Python lists
87
+ - NULL → None
88
+
89
+ :param items: Raw stack items as returned by Tonapi
90
+ :return: Normalized list of decoded stack values
91
+ """
92
+ out: StackItems = []
93
+ for item in items:
94
+ if not isinstance(item, dict):
95
+ continue
96
+ tpe = item.get("type")
97
+ val = item.get(tpe)
98
+ if tpe == StackTag.NULL.value:
99
+ out.append(None)
100
+ elif tpe == StackTag.NUM.value and val is not None:
101
+ out.append(norm_stack_num(t.cast(str | int, val)))
102
+ elif tpe in (StackTag.CELL.value, StackTag.SLICE.value):
103
+ out.append(norm_stack_cell(val))
104
+ elif tpe in (StackTag.LIST.value, StackTag.TUPLE.value):
105
+ inner: t.List[t.Any] = []
106
+ for el in val or []:
107
+ inner.append(
108
+ decode_tonapi_stack([el])[0] if isinstance(el, dict) else el
109
+ )
110
+ out.append(inner)
111
+ return out
112
+
113
+
114
+ def encode_tonapi_stack(items: t.List[StackItem]) -> t.List[t.Any]:
115
+ """
116
+ Encode Python TVM stack values into Tonapi-compatible format.
117
+
118
+ Produces values expected by Tonapi query parameters:
119
+ - int → hex string
120
+ - Cell/Address → hex BoC string
121
+ - Slice → hex BoC string
122
+ - list/tuple → recursively encoded list
123
+
124
+ :param items: List of python TVM stack values
125
+ :return: Encoded stack items suitable for Tonapi API
126
+ """
127
+ out: t.List[t.Any] = []
128
+ for item in items:
129
+ tpe = StackTag.of(item)
130
+ if tpe == StackTag.NUM:
131
+ out.append(hex(t.cast(int, item)))
132
+ elif tpe == StackTag.CELL:
133
+ cell = item.to_cell() if isinstance(item, Address) else item
134
+ out.append(cell_to_hex(cell))
135
+ elif tpe == StackTag.SLICE:
136
+ cell = t.cast(Slice, item).to_cell()
137
+ out.append(cell_to_hex(cell))
138
+ elif tpe in (StackTag.LIST, StackTag.TUPLE):
139
+ out.append(encode_tonapi_stack(t.cast(list, item)))
140
+ return out
@@ -0,0 +1,115 @@
1
+ import asyncio
2
+ import time
3
+
4
+
5
+ class RateLimiter:
6
+ """
7
+ Asynchronous token-bucket rate limiter with optional priority acquisition.
8
+
9
+ Limits the number of acquire operations per time period and supports
10
+ priority waiters that can bypass non-priority requests when tokens
11
+ become available.
12
+ """
13
+
14
+ __slots__ = (
15
+ "_max_rate",
16
+ "_period",
17
+ "_tokens",
18
+ "_updated_at",
19
+ "_cond",
20
+ "_priority_waiters",
21
+ )
22
+
23
+ def __init__(self, max_rate: int, period: float = 1.0) -> None:
24
+ """
25
+ Initialize the rate limiter.
26
+
27
+ :param max_rate: Maximum number of acquisitions allowed per period.
28
+ :param period: Period length in seconds.
29
+ :raises ValueError: If ``max_rate`` or ``period`` is not positive.
30
+ """
31
+ if max_rate <= 0:
32
+ raise ValueError("max_rate must be > 0")
33
+ if period <= 0:
34
+ raise ValueError("period must be > 0")
35
+
36
+ self._max_rate = max_rate
37
+ self._period = period
38
+ self._tokens = float(max_rate)
39
+ self._updated_at = time.monotonic()
40
+ self._cond = asyncio.Condition()
41
+ self._priority_waiters = 0
42
+
43
+ def when_ready(self) -> float:
44
+ """
45
+ Calculate delay until the next token becomes available.
46
+
47
+ :return: Number of seconds to wait before a token can be acquired,
48
+ or ``0`` if a token is available immediately.
49
+ """
50
+ now = time.monotonic()
51
+ tokens = self._peek_tokens(now)
52
+ if tokens >= 1.0:
53
+ return 0.0
54
+ return self._seconds_to_one_token(tokens)
55
+
56
+ async def acquire(self, priority: bool = False) -> None:
57
+ """
58
+ Acquire a single token, waiting asynchronously if necessary.
59
+
60
+ Priority acquisitions are allowed to bypass non-priority waiters
61
+ when tokens become available.
62
+
63
+ :param priority: Whether to acquire the token with priority.
64
+ """
65
+ if priority:
66
+ async with self._cond:
67
+ self._priority_waiters += 1
68
+
69
+ try:
70
+ await self._acquire(priority=True)
71
+ finally:
72
+ async with self._cond:
73
+ self._priority_waiters -= 1
74
+ self._cond.notify_all()
75
+ return
76
+
77
+ await self._acquire(priority=False)
78
+
79
+ async def _acquire(self, priority: bool) -> None:
80
+ while True:
81
+ async with self._cond:
82
+ now = time.monotonic()
83
+ self._refill(now)
84
+
85
+ if self._tokens >= 1.0 and (priority or self._priority_waiters == 0):
86
+ self._tokens -= 1.0
87
+ self._cond.notify_all()
88
+ return
89
+
90
+ if self._tokens < 1.0:
91
+ timeout = self._seconds_to_one_token(self._tokens)
92
+ await asyncio.wait_for(self._cond.wait(), timeout=timeout)
93
+ else:
94
+ await self._cond.wait()
95
+
96
+ def _peek_tokens(self, now: float) -> float:
97
+ elapsed = now - self._updated_at
98
+ if elapsed <= 0.0:
99
+ return self._tokens
100
+
101
+ rate = self._max_rate / self._period
102
+ tokens = self._tokens + elapsed * rate
103
+ return min(float(self._max_rate), tokens)
104
+
105
+ def _refill(self, now: float) -> None:
106
+ self._tokens = self._peek_tokens(now)
107
+ self._updated_at = now
108
+
109
+ def _seconds_to_one_token(self, tokens: float) -> float:
110
+ missing = 1.0 - tokens
111
+ if missing <= 0.0:
112
+ return 0.0
113
+
114
+ rate = self._max_rate / self._period
115
+ return missing / rate
@@ -91,6 +91,7 @@ from .nft import (
91
91
  royalty_params_get_method,
92
92
  )
93
93
  from .opcodes import OpCode
94
+ from .protocol import ContractProtocol
94
95
  from .telegram import (
95
96
  TeleCollectionData,
96
97
  TeleItemAuction,
@@ -142,6 +143,7 @@ from .wallet import (
142
143
  WalletHighloadV3Data,
143
144
  WalletHighloadV3Params,
144
145
  WalletHighloadV3R1,
146
+ WalletProtocol,
145
147
  WalletPreprocessedV2,
146
148
  WalletPreprocessedV2Config,
147
149
  WalletPreprocessedV2Data,
@@ -194,6 +196,7 @@ __all__ = [
194
196
  "BaseMessageBuilder",
195
197
  "ChangeDNSRecordBody",
196
198
  "CONTRACT_CODES",
199
+ "ContractProtocol",
197
200
  "ContractVersion",
198
201
  "DNSBalanceReleaseBody",
199
202
  "DNSRecordDNSNextResolver",
@@ -297,6 +300,7 @@ __all__ = [
297
300
  "WalletHighloadV3Data",
298
301
  "WalletHighloadV3Params",
299
302
  "WalletHighloadV3R1",
303
+ "WalletProtocol",
300
304
  "WalletPreprocessedV2",
301
305
  "WalletPreprocessedV2Config",
302
306
  "WalletPreprocessedV2Data",
@@ -2,14 +2,17 @@ from __future__ import annotations
2
2
 
3
3
  import typing as t
4
4
 
5
- from pyapiq.exceptions import APIQException, RateLimitExceeded
6
5
  from pytoniq_core import Address, Cell, StateInit, TlbScheme
7
6
 
7
+ from tonutils.clients.protocol import ClientProtocol
8
8
  from tonutils.contracts.codes import CONTRACT_CODES
9
+ from tonutils.contracts.protocol import ContractProtocol
9
10
  from tonutils.contracts.versions import ContractVersion
10
- from tonutils.exceptions import ContractError, NotRefreshedError
11
- from tonutils.protocols.client import ClientProtocol
12
- from tonutils.protocols.contract import ContractProtocol
11
+ from tonutils.exceptions import (
12
+ ContractError,
13
+ StateNotLoadedError,
14
+ ProviderResponseError,
15
+ )
13
16
  from tonutils.types import AddressLike, ContractState, ContractStateInfo, WorkchainID
14
17
  from tonutils.utils import to_cell
15
18
 
@@ -49,7 +52,7 @@ class BaseContract(ContractProtocol[_D]):
49
52
  default_code = to_cell(CONTRACT_CODES[cls.VERSION])
50
53
  except KeyError:
51
54
  raise ContractError(
52
- cls, f"No contract code defined for `VERSION` {cls.VERSION!r}."
55
+ cls, f"No contract code defined for `version` {cls.VERSION!r}."
53
56
  )
54
57
  return default_code
55
58
 
@@ -96,7 +99,7 @@ class BaseContract(ContractProtocol[_D]):
96
99
  :return: Contract state information
97
100
  """
98
101
  if self._state_info is None:
99
- raise NotRefreshedError(self, "state_info")
102
+ raise StateNotLoadedError(self, missing="state_info")
100
103
  return t.cast(ContractStateInfo, self._state_info)
101
104
 
102
105
  @property
@@ -111,7 +114,7 @@ class BaseContract(ContractProtocol[_D]):
111
114
  if not hasattr(self, "_data_model") or self._data_model is None:
112
115
  raise ContractError(self, "No `_data_model` defined for contract class.")
113
116
  if not (self._state_info and self._state_info.data):
114
- raise NotRefreshedError(self, "state_data")
117
+ raise StateNotLoadedError(self, missing="state_data")
115
118
  cs = self._state_info.data.begin_parse()
116
119
  return self._data_model.deserialize(cs)
117
120
 
@@ -205,6 +208,26 @@ class BaseContract(ContractProtocol[_D]):
205
208
  """
206
209
  return self.state_info.data
207
210
 
211
+ @classmethod
212
+ async def _load_state_info(
213
+ cls,
214
+ client: ClientProtocol,
215
+ address: Address,
216
+ ) -> ContractStateInfo:
217
+ """
218
+ Fetch contract state from the blockchain.
219
+
220
+ If the request fails (except rate limits), sets state to default empty state.
221
+ """
222
+ try:
223
+ return await client.get_contract_info(address)
224
+ except ProviderResponseError as e:
225
+ if e.code in {429, 228, 5556}: # rate limit exceed
226
+ raise
227
+ return ContractStateInfo()
228
+ except (Exception,):
229
+ return ContractStateInfo()
230
+
208
231
  async def refresh(self) -> None:
209
232
  """
210
233
  Refresh contract state from the blockchain.
@@ -212,13 +235,7 @@ class BaseContract(ContractProtocol[_D]):
212
235
  Fetches current contract information and updates the cached state_info.
213
236
  If the request fails (except rate limits), sets state to default empty state.
214
237
  """
215
- try:
216
- state_info = await self.client.get_contract_info(self.address)
217
- except RateLimitExceeded:
218
- raise
219
- except APIQException:
220
- state_info = ContractStateInfo()
221
- self._state_info = state_info
238
+ self._state_info = await self._load_state_info(self.client, self.address)
222
239
 
223
240
  @classmethod
224
241
  def from_state_init(
@@ -305,12 +322,8 @@ class BaseContract(ContractProtocol[_D]):
305
322
  address = Address(address)
306
323
  if not load_state:
307
324
  return cls(client, address)
308
- try:
309
- state_info = await client.get_contract_info(address)
310
- except RateLimitExceeded:
311
- raise
312
- except APIQException:
313
- state_info = ContractStateInfo()
325
+
326
+ state_info = await cls._load_state_info(client, address)
314
327
  return cls(client, address, state_info=state_info)
315
328
 
316
329
  def __repr__(self) -> str:
@@ -2,8 +2,8 @@ import typing as t
2
2
 
3
3
  from pytoniq_core import Cell, begin_cell
4
4
 
5
- from tonutils.protocols.client import ClientProtocol
6
- from tonutils.protocols.contract import ContractProtocol
5
+ from tonutils.clients.protocol import ClientProtocol
6
+ from tonutils.contracts.protocol import ContractProtocol
7
7
  from tonutils.types import AddressLike, DNSCategory
8
8
 
9
9
 
@@ -2,8 +2,8 @@ import typing as t
2
2
 
3
3
  from pytoniq_core import Address
4
4
 
5
- from tonutils.protocols.client import ClientProtocol
6
- from tonutils.protocols.contract import ContractProtocol
5
+ from tonutils.clients.protocol import ClientProtocol
6
+ from tonutils.contracts.protocol import ContractProtocol
7
7
  from tonutils.types import AddressLike
8
8
 
9
9
 
@@ -2,8 +2,8 @@ import typing as t
2
2
 
3
3
  from pytoniq_core import Address, Cell
4
4
 
5
- from tonutils.protocols.client import ClientProtocol
6
- from tonutils.protocols.contract import ContractProtocol
5
+ from tonutils.clients.protocol import ClientProtocol
6
+ from tonutils.contracts.protocol import ContractProtocol
7
7
  from tonutils.types import AddressLike
8
8
 
9
9
 
@@ -822,7 +822,7 @@ class NFTCollectionBatchMintItemBody(TlbScheme):
822
822
  :return: Sorted list of (index, amount, item_ref) tuples
823
823
  """
824
824
  hashmap = cs.load_dict(key_length=64)
825
- out: list[tuple[int, int, Cell]] = []
825
+ out: t.List[tuple[int, int, Cell]] = []
826
826
  for key, val in hashmap.items():
827
827
  amount = val.load_coins()
828
828
  item_ref = val.load_ref()