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
@@ -4,11 +4,10 @@ import time
4
4
 
5
5
  class RateLimiter:
6
6
  """
7
- Asynchronous token-bucket rate limiter with optional priority acquisition.
7
+ Asynchronous token-bucket rate limiter with priority support.
8
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.
9
+ Priority requests are served before non-priority requests when
10
+ tokens become available.
12
11
  """
13
12
 
14
13
  __slots__ = (
@@ -26,7 +25,7 @@ class RateLimiter:
26
25
 
27
26
  :param max_rate: Maximum number of acquisitions allowed per period.
28
27
  :param period: Period length in seconds.
29
- :raises ValueError: If ``max_rate`` or ``period`` is not positive.
28
+ :raises ValueError: If max_rate or period is not positive.
30
29
  """
31
30
  if max_rate <= 0:
32
31
  raise ValueError("max_rate must be > 0")
@@ -40,76 +39,79 @@ class RateLimiter:
40
39
  self._cond = asyncio.Condition()
41
40
  self._priority_waiters = 0
42
41
 
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
- """
42
+ def _refill(self) -> None:
43
+ """Refill tokens based on elapsed time."""
50
44
  now = time.monotonic()
51
- tokens = self._peek_tokens(now)
52
- if tokens >= 1.0:
45
+ elapsed = now - self._updated_at
46
+ if elapsed > 0:
47
+ rate = self._max_rate / self._period
48
+ self._tokens = min(float(self._max_rate), self._tokens + elapsed * rate)
49
+ self._updated_at = now
50
+
51
+ def _seconds_to_token(self) -> float:
52
+ """Calculate seconds until next token is available."""
53
+ if self._tokens >= 1.0:
53
54
  return 0.0
54
- return self._seconds_to_one_token(tokens)
55
+ missing = 1.0 - self._tokens
56
+ rate = self._max_rate / self._period
57
+ return missing / rate
55
58
 
56
59
  async def acquire(self, priority: bool = False) -> None:
57
60
  """
58
- Acquire a single token, waiting asynchronously if necessary.
61
+ Acquire a single token, waiting if necessary.
59
62
 
60
- Priority acquisitions are allowed to bypass non-priority waiters
61
- when tokens become available.
63
+ Priority requests bypass non-priority waiters when tokens
64
+ become available.
62
65
 
63
- :param priority: Whether to acquire the token with priority.
66
+ :param priority: Whether to acquire with priority.
64
67
  """
65
- if priority:
66
- async with self._cond:
67
- self._priority_waiters += 1
68
+ async with self._cond:
69
+ is_waiting = False
68
70
 
69
71
  try:
70
- await self._acquire(priority=True)
72
+ while True:
73
+ self._refill()
74
+
75
+ # Check if token available
76
+ if self._tokens >= 1.0:
77
+ # Priority always takes; non-priority only if no priority waiting
78
+ if priority or self._priority_waiters == 0:
79
+ self._tokens -= 1.0
80
+ self._cond.notify_all()
81
+ return
82
+
83
+ # Register as priority waiter only when actually waiting
84
+ if priority and not is_waiting:
85
+ self._priority_waiters += 1
86
+ is_waiting = True
87
+
88
+ # Wait for token
89
+ wait_time = self._seconds_to_token()
90
+ if wait_time > 0:
91
+ try:
92
+ await asyncio.wait_for(self._cond.wait(), timeout=wait_time)
93
+ except asyncio.TimeoutError:
94
+ pass # Expected — token should be ready now
95
+ else:
96
+ # Token available but blocked by priority — wait for notify
97
+ await self._cond.wait()
98
+
71
99
  finally:
72
- async with self._cond:
100
+ if is_waiting:
73
101
  self._priority_waiters -= 1
74
102
  self._cond.notify_all()
75
- return
76
103
 
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()
104
+ def when_ready(self) -> float:
105
+ """
106
+ Calculate delay until the next token becomes available.
95
107
 
96
- def _peek_tokens(self, now: float) -> float:
108
+ :return: Seconds to wait, or 0 if ready immediately.
109
+ """
110
+ now = time.monotonic()
97
111
  elapsed = now - self._updated_at
98
- if elapsed <= 0.0:
99
- return self._tokens
100
-
101
112
  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
113
+ tokens = min(float(self._max_rate), self._tokens + elapsed * rate)
108
114
 
109
- def _seconds_to_one_token(self, tokens: float) -> float:
110
- missing = 1.0 - tokens
111
- if missing <= 0.0:
115
+ if tokens >= 1.0:
112
116
  return 0.0
113
-
114
- rate = self._max_rate / self._period
115
- return missing / rate
117
+ return (1.0 - tokens) / rate
@@ -6,7 +6,7 @@ from pytoniq_core import Cell, Transaction
6
6
 
7
7
  from tonutils.types import (
8
8
  AddressLike,
9
- ContractStateInfo,
9
+ ContractInfo,
10
10
  DNSCategory,
11
11
  NetworkGlobalID,
12
12
  ClientType,
@@ -40,27 +40,27 @@ class ClientProtocol(t.Protocol):
40
40
  """Underlying provider or transport backend."""
41
41
 
42
42
  @property
43
- def is_connected(self) -> bool:
43
+ def connected(self) -> bool:
44
44
  """Whether the client is connected and ready for requests."""
45
45
 
46
- async def send_boc(self, boc: str) -> None:
46
+ async def send_message(self, boc: str) -> None:
47
47
  """Send an external message to the blockchain."""
48
48
 
49
- async def get_blockchain_config(self) -> t.Dict[int, t.Any]:
49
+ async def get_config(self) -> t.Dict[int, t.Any]:
50
50
  """Fetch global blockchain configuration."""
51
51
 
52
- async def get_contract_info(
52
+ async def get_info(
53
53
  self,
54
54
  address: AddressLike,
55
- ) -> ContractStateInfo:
55
+ ) -> ContractInfo:
56
56
  """Fetch basic contract state information."""
57
57
 
58
- async def get_contract_transactions(
58
+ async def get_transactions(
59
59
  self,
60
60
  address: AddressLike,
61
61
  limit: int = 100,
62
62
  from_lt: t.Optional[int] = None,
63
- to_lt: int = 0,
63
+ to_lt: t.Optional[int] = None,
64
64
  ) -> t.List[Transaction]:
65
65
  """Fetch contract transactions."""
66
66
 
@@ -13,7 +13,7 @@ from tonutils.exceptions import (
13
13
  StateNotLoadedError,
14
14
  ProviderResponseError,
15
15
  )
16
- from tonutils.types import AddressLike, ContractState, ContractStateInfo, WorkchainID
16
+ from tonutils.types import AddressLike, ContractInfo, ContractState, WorkchainID
17
17
  from tonutils.utils import to_cell
18
18
 
19
19
  _R = t.TypeVar("_R")
@@ -61,7 +61,7 @@ class BaseContract(ContractProtocol[_D]):
61
61
  client: ClientProtocol,
62
62
  address: Address,
63
63
  state_init: t.Optional[StateInit] = None,
64
- state_info: t.Optional[ContractStateInfo] = None,
64
+ info: t.Optional[ContractInfo] = None,
65
65
  ) -> None:
66
66
  """
67
67
  Initialize base contract wrapper.
@@ -69,12 +69,12 @@ class BaseContract(ContractProtocol[_D]):
69
69
  :param client: TON client for blockchain interactions
70
70
  :param address: Contract address on the blockchain
71
71
  :param state_init: Optional known StateInit (code and data)
72
- :param state_info: Optional preloaded on-chain contract state
72
+ :param info: Optional preloaded on-chain contract state
73
73
  """
74
74
  self._client = client
75
75
  self._address = address
76
76
  self._state_init = state_init
77
- self._state_info = state_info
77
+ self._info = info
78
78
 
79
79
  @property
80
80
  def client(self) -> ClientProtocol:
@@ -91,17 +91,6 @@ class BaseContract(ContractProtocol[_D]):
91
91
  """Locally known StateInit for this contract, if any."""
92
92
  return self._state_init
93
93
 
94
- @property
95
- def state_info(self) -> ContractStateInfo:
96
- """
97
- Cached snapshot of the contract state.
98
-
99
- :return: Contract state information
100
- """
101
- if self._state_info is None:
102
- raise StateNotLoadedError(self, missing="state_info")
103
- return t.cast(ContractStateInfo, self._state_info)
104
-
105
94
  @property
106
95
  def state_data(self) -> _D:
107
96
  """
@@ -113,11 +102,22 @@ class BaseContract(ContractProtocol[_D]):
113
102
  """
114
103
  if not hasattr(self, "_data_model") or self._data_model is None:
115
104
  raise ContractError(self, "No `_data_model` defined for contract class.")
116
- if not (self._state_info and self._state_info.data):
105
+ if not (self._info and self._info.data):
117
106
  raise StateNotLoadedError(self, missing="state_data")
118
- cs = self._state_info.data.begin_parse()
107
+ cs = self._info.data.begin_parse()
119
108
  return self._data_model.deserialize(cs)
120
109
 
110
+ @property
111
+ def info(self) -> ContractInfo:
112
+ """
113
+ Cached snapshot of the contract state info.
114
+
115
+ :return: Contract state information
116
+ """
117
+ if self._info is None:
118
+ raise StateNotLoadedError(self, missing="info")
119
+ return t.cast(ContractInfo, self._info)
120
+
121
121
  @property
122
122
  def balance(self) -> int:
123
123
  """
@@ -125,7 +125,7 @@ class BaseContract(ContractProtocol[_D]):
125
125
 
126
126
  :return: Balance from the latest known state
127
127
  """
128
- return self.state_info.balance
128
+ return self.info.balance
129
129
 
130
130
  @property
131
131
  def state(self) -> ContractState:
@@ -134,7 +134,7 @@ class BaseContract(ContractProtocol[_D]):
134
134
 
135
135
  :return: One of ContractState enum values (ACTIVE, FROZEN, UNINIT, NONEXIST)
136
136
  """
137
- return self.state_info.state
137
+ return self.info.state
138
138
 
139
139
  @property
140
140
  def is_active(self) -> bool:
@@ -179,7 +179,7 @@ class BaseContract(ContractProtocol[_D]):
179
179
 
180
180
  :return: Transaction LT or None if unknown
181
181
  """
182
- return self.state_info.last_transaction_lt
182
+ return self.info.last_transaction_lt
183
183
 
184
184
  @property
185
185
  def last_transaction_hash(self) -> t.Optional[str]:
@@ -188,7 +188,7 @@ class BaseContract(ContractProtocol[_D]):
188
188
 
189
189
  :return: Transaction hash as hex string or None if unknown
190
190
  """
191
- return self.state_info.last_transaction_hash
191
+ return self.info.last_transaction_hash
192
192
 
193
193
  @property
194
194
  def code(self) -> t.Optional[Cell]:
@@ -197,7 +197,7 @@ class BaseContract(ContractProtocol[_D]):
197
197
 
198
198
  :return: Code cell or None if not available
199
199
  """
200
- return self.state_info.code
200
+ return self.info.code
201
201
 
202
202
  @property
203
203
  def data(self) -> t.Optional[Cell]:
@@ -206,36 +206,36 @@ class BaseContract(ContractProtocol[_D]):
206
206
 
207
207
  :return: Data cell or None if not available
208
208
  """
209
- return self.state_info.data
209
+ return self.info.data
210
210
 
211
211
  @classmethod
212
- async def _load_state_info(
212
+ async def _load_info(
213
213
  cls,
214
214
  client: ClientProtocol,
215
215
  address: Address,
216
- ) -> ContractStateInfo:
216
+ ) -> ContractInfo:
217
217
  """
218
218
  Fetch contract state from the blockchain.
219
219
 
220
220
  If the request fails (except rate limits), sets state to default empty state.
221
221
  """
222
222
  try:
223
- return await client.get_contract_info(address)
223
+ return await client.get_info(address)
224
224
  except ProviderResponseError as e:
225
225
  if e.code in {429, 228, 5556}: # rate limit exceed
226
226
  raise
227
- return ContractStateInfo()
227
+ return ContractInfo()
228
228
  except (Exception,):
229
- return ContractStateInfo()
229
+ return ContractInfo()
230
230
 
231
231
  async def refresh(self) -> None:
232
232
  """
233
233
  Refresh contract state from the blockchain.
234
234
 
235
- Fetches current contract information and updates the cached state_info.
235
+ Fetches current contract information and updates the cached info.
236
236
  If the request fails (except rate limits), sets state to default empty state.
237
237
  """
238
- self._state_info = await self._load_state_info(self.client, self.address)
238
+ self._info = await self._load_info(self.client, self.address)
239
239
 
240
240
  @classmethod
241
241
  def from_state_init(
@@ -323,8 +323,8 @@ class BaseContract(ContractProtocol[_D]):
323
323
  if not load_state:
324
324
  return cls(client, address)
325
325
 
326
- state_info = await cls._load_state_info(client, address)
327
- return cls(client, address, state_info=state_info)
326
+ info = await cls._load_info(client, address)
327
+ return cls(client, address, info=info)
328
328
 
329
329
  def __repr__(self) -> str:
330
330
  return f"< Contract {self.__class__.__name__} address: {self.address} >"
@@ -5,7 +5,7 @@ import typing as t
5
5
  from pytoniq_core import Address, Cell, StateInit
6
6
 
7
7
  from tonutils.clients.protocol import ClientProtocol
8
- from tonutils.types import AddressLike, ContractState, ContractStateInfo, WorkchainID
8
+ from tonutils.types import AddressLike, ContractInfo, ContractState, WorkchainID
9
9
 
10
10
  if t.TYPE_CHECKING:
11
11
  from tonutils.contracts.versions import ContractVersion
@@ -48,21 +48,21 @@ class ContractProtocol(t.Protocol[_D]):
48
48
  """Locally known StateInit for this contract, if any."""
49
49
 
50
50
  @property
51
- def state_info(self) -> ContractStateInfo:
51
+ def state_data(self) -> _D:
52
52
  """
53
- Cached snapshot of the contract state.
53
+ Decoded on-chain data in typed form.
54
54
 
55
- Implementations usually update this via refresh() or in constructors
56
- that read on-chain data.
55
+ Implementations must decode the current data cell from info
56
+ using the associated _data_model.
57
57
  """
58
58
 
59
59
  @property
60
- def state_data(self) -> _D:
60
+ def info(self) -> ContractInfo:
61
61
  """
62
- Decoded on-chain data in typed form.
62
+ Cached snapshot of the contract state info.
63
63
 
64
- Implementations must decode the current data cell from state_info
65
- using the associated _data_model.
64
+ Implementations usually update this via refresh() or in constructors
65
+ that read on-chain data.
66
66
  """
67
67
 
68
68
  @property
@@ -22,14 +22,14 @@ from tonutils.contracts.wallet.tlb import BaseWalletData
22
22
  from tonutils.exceptions import ContractError
23
23
  from tonutils.types import (
24
24
  AddressLike,
25
- ContractStateInfo,
25
+ ContractInfo,
26
26
  SendMode,
27
27
  PublicKey,
28
28
  PrivateKey,
29
29
  WorkchainID,
30
30
  DEFAULT_SENDMODE,
31
31
  )
32
- from tonutils.utils import resolve_wallet_address, to_cell
32
+ from tonutils.utils import to_cell
33
33
 
34
34
  _D = t.TypeVar("_D", bound=BaseWalletData)
35
35
  _C = t.TypeVar("_C", bound=BaseWalletConfig)
@@ -61,7 +61,7 @@ class BaseWallet(BaseContract, WalletProtocol[_D, _C, _P], abc.ABC):
61
61
  client: ClientProtocol,
62
62
  address: Address,
63
63
  state_init: t.Optional[StateInit] = None,
64
- state_info: t.Optional[ContractStateInfo] = None,
64
+ info: t.Optional[ContractInfo] = None,
65
65
  config: t.Optional[_C] = None,
66
66
  private_key: t.Optional[PrivateKey] = None,
67
67
  ) -> None:
@@ -71,7 +71,7 @@ class BaseWallet(BaseContract, WalletProtocol[_D, _C, _P], abc.ABC):
71
71
  :param client: TON client for blockchain interactions
72
72
  :param address: Wallet address on the blockchain
73
73
  :param state_init: Optional StateInit (code and data)
74
- :param state_info: Optional preloaded contract state
74
+ :param info: Optional preloaded contract state
75
75
  :param config: Optional wallet configuration
76
76
  :param private_key: Optional private key for signing transactions
77
77
  """
@@ -82,7 +82,7 @@ class BaseWallet(BaseContract, WalletProtocol[_D, _C, _P], abc.ABC):
82
82
  if private_key is not None:
83
83
  self._private_key = private_key
84
84
  self._public_key = private_key.public_key
85
- super().__init__(client, address, state_init, state_info)
85
+ super().__init__(client, address, state_init, info)
86
86
 
87
87
  @property
88
88
  def state_data(self) -> _D:
@@ -295,7 +295,7 @@ class BaseWallet(BaseContract, WalletProtocol[_D, _C, _P], abc.ABC):
295
295
  :return: Signed external message that was sent
296
296
  """
297
297
  external_msg = await self.build_external_message(messages, params)
298
- await self.client.send_boc(external_msg.as_hex)
298
+ await self.client.send_message(external_msg.as_hex)
299
299
  return external_msg
300
300
 
301
301
  async def transfer_message(
@@ -325,7 +325,7 @@ class BaseWallet(BaseContract, WalletProtocol[_D, _C, _P], abc.ABC):
325
325
  """
326
326
  Build and send a transfer to a single destination.
327
327
 
328
- :param destination: Recipient address (Address, string, or domain)
328
+ :param destination: Recipient address
329
329
  :param amount: Amount to send in nanotons
330
330
  :param body: Optional message body (Cell or text comment)
331
331
  :param state_init: Optional StateInit for contract deployment
@@ -334,7 +334,6 @@ class BaseWallet(BaseContract, WalletProtocol[_D, _C, _P], abc.ABC):
334
334
  :param params: Optional transaction parameters
335
335
  :return: Signed external message that was sent
336
336
  """
337
- destination = await resolve_wallet_address(self.client, destination)
338
337
  message = TONTransferBuilder(
339
338
  destination=destination,
340
339
  amount=amount,
@@ -23,7 +23,6 @@ from tonutils.utils import (
23
23
  cell_to_b64,
24
24
  normalize_hash,
25
25
  to_nano,
26
- resolve_wallet_address,
27
26
  )
28
27
 
29
28
 
@@ -191,7 +190,7 @@ class TONTransferBuilder(BaseMessageBuilder):
191
190
  """
192
191
  Initialize TON transfer builder.
193
192
 
194
- :param destination: Recipient address (Address, string, or domain)
193
+ :param destination: Recipient address
195
194
  :param amount: Amount to send in nanotons
196
195
  :param body: Optional message body (Cell or text comment string)
197
196
  :param state_init: Optional StateInit for contract deployment
@@ -214,11 +213,10 @@ class TONTransferBuilder(BaseMessageBuilder):
214
213
  :param wallet: Wallet instance to build message for
215
214
  :return: WalletMessage with TON transfer
216
215
  """
217
- destination = await resolve_wallet_address(wallet.client, self.destination)
218
216
  return WalletMessage(
219
217
  send_mode=self.send_mode,
220
218
  message=InternalMessage(
221
- dest=destination,
219
+ dest=self.destination,
222
220
  value=self.amount,
223
221
  body=self.body,
224
222
  state_init=self.state_init,
@@ -277,9 +275,8 @@ class NFTTransferBuilder(BaseMessageBuilder):
277
275
  :param wallet: Wallet instance to build message for
278
276
  :return: WalletMessage with NFT transfer
279
277
  """
280
- destination = await resolve_wallet_address(wallet.client, self.destination)
281
278
  body = NFTTransferBody(
282
- destination=destination,
279
+ destination=self.destination,
283
280
  response_address=self.response_address or wallet.address,
284
281
  custom_payload=self.custom_payload,
285
282
  forward_payload=self.forward_payload,
@@ -358,7 +355,6 @@ class JettonTransferBuilder(BaseMessageBuilder):
358
355
  :param wallet: Wallet instance to build message for
359
356
  :return: WalletMessage with jetton transfer
360
357
  """
361
- destination = await resolve_wallet_address(wallet.client, self.destination)
362
358
  jetton_wallet_address = self.jetton_wallet_address
363
359
  if self.jetton_wallet_address is None:
364
360
  jetton_wallet_address = await get_wallet_address_get_method(
@@ -367,7 +363,7 @@ class JettonTransferBuilder(BaseMessageBuilder):
367
363
  owner_address=wallet.address,
368
364
  )
369
365
  body = JettonTransferBody(
370
- destination=destination,
366
+ destination=self.destination,
371
367
  jetton_amount=self.jetton_amount,
372
368
  response_address=self.response_address or wallet.address,
373
369
  custom_payload=self.custom_payload,
@@ -219,13 +219,13 @@ class WalletV5R1(
219
219
 
220
220
  :return: Typed wallet data
221
221
  """
222
- if not (self._state_info and self._state_info.data):
222
+ if not (self._info and self._info.data):
223
223
  raise StateNotLoadedError(self, missing="state_data")
224
224
 
225
225
  network_global_id = (
226
226
  NetworkGlobalID.TESTNET if self.client.network else NetworkGlobalID.MAINNET
227
227
  )
228
- cs = self._state_info.data.begin_parse()
228
+ cs = self._info.data.begin_parse()
229
229
  return self._data_model.deserialize(cs, network_global_id)
230
230
 
231
231
  async def _build_msg_cell(
tonutils/exceptions.py CHANGED
@@ -27,7 +27,7 @@ class TransportError(TonutilsError):
27
27
 
28
28
  Covers: TCP connect, ADNL handshake, send/recv, crypto failures.
29
29
 
30
- :param endpoint: Server address as "host:port"
30
+ :param endpoint: Endpoint identifier (URL or "host:port").
31
31
  :param operation: What was attempted ("connect", "handshake", "send", "recv")
32
32
  :param reason: Why it failed ("timeout 2.0s", "connection refused", etc.)
33
33
  """
@@ -46,7 +46,7 @@ class TransportError(TonutilsError):
46
46
  self.endpoint = endpoint
47
47
  self.operation = operation
48
48
  self.reason = reason
49
- super().__init__(f"{operation} failed at {endpoint}: {reason}")
49
+ super().__init__(f"{operation} failed: {reason} ({endpoint})")
50
50
 
51
51
 
52
52
  class ProviderError(TonutilsError):
@@ -64,14 +64,28 @@ class BalancerError(TonutilsError):
64
64
  class NotConnectedError(TonutilsError, RuntimeError):
65
65
  """Raise when an operation requires an active connection."""
66
66
 
67
+ component: str
67
68
  endpoint: t.Optional[str]
69
+ operation: t.Optional[str]
68
70
 
69
- def __init__(self, endpoint: t.Optional[str] = None) -> None:
71
+ def __init__(
72
+ self,
73
+ *,
74
+ component: str = "client",
75
+ endpoint: t.Optional[str] = None,
76
+ operation: t.Optional[str] = None,
77
+ hint: t.Optional[str] = None,
78
+ ) -> None:
79
+ self.component = component
70
80
  self.endpoint = endpoint
71
- if endpoint:
72
- super().__init__(f"not connected to {endpoint}")
73
- else:
74
- super().__init__("not connected")
81
+ self.operation = operation
82
+
83
+ if hint is None:
84
+ hint = "Call connect() first or use an async context manager (`async with ...`)."
85
+
86
+ where = f" ({endpoint})" if endpoint else ""
87
+ prefix = f"cannot `{operation}`: " if operation else ""
88
+ super().__init__(f"{prefix}{component} is not connected{where}. {hint}")
75
89
 
76
90
 
77
91
  class ProviderTimeoutError(ProviderError, asyncio.TimeoutError):
@@ -90,7 +104,7 @@ class ProviderTimeoutError(ProviderError, asyncio.TimeoutError):
90
104
  self.timeout = timeout
91
105
  self.endpoint = endpoint
92
106
  self.operation = operation
93
- super().__init__(f"{operation} timed out after {timeout}s at {endpoint}")
107
+ super().__init__(f"{operation} timed out after {timeout}s ({endpoint})")
94
108
 
95
109
 
96
110
  class ProviderResponseError(ProviderError):
@@ -109,7 +123,7 @@ class ProviderResponseError(ProviderError):
109
123
  self.code = code
110
124
  self.message = message
111
125
  self.endpoint = endpoint
112
- super().__init__(f"request failed with code {code} at {endpoint}: {message}")
126
+ super().__init__(f"request failed: {code} {message} ({endpoint})")
113
127
 
114
128
 
115
129
  class RetryLimitError(ProviderError):
@@ -134,7 +148,9 @@ class RetryLimitError(ProviderError):
134
148
  self.attempts = attempts
135
149
  self.max_attempts = max_attempts
136
150
  self.last_error = last_error
137
- super().__init__(f"retry exhausted ({attempts}/{max_attempts}): {last_error}")
151
+ super().__init__(
152
+ f"retry limit reached ({attempts}/{max_attempts}): {last_error}"
153
+ )
138
154
 
139
155
 
140
156
  class ContractError(ClientError):
@@ -153,14 +169,14 @@ class ContractError(ClientError):
153
169
  name = (
154
170
  target.__name__ if isinstance(target, type) else target.__class__.__name__
155
171
  )
156
- super().__init__(f"{name}: {message}")
172
+ super().__init__(f"{name} failed: {message}")
157
173
 
158
174
 
159
175
  class StateNotLoadedError(ContractError):
160
176
  """Raise when a contract wrapper requires state that is not loaded.
161
177
 
162
178
  :param contract: Contract instance related to the failure.
163
- :param missing: Missing field name (e.g. "state_info", "state_data").
179
+ :param missing: Missing field name (e.g. "info", "state_data").
164
180
  """
165
181
 
166
182
  missing: str
@@ -188,7 +204,7 @@ class RunGetMethodError(ClientError):
188
204
  self.method_name = method_name
189
205
  self.exit_code = exit_code
190
206
  super().__init__(
191
- f"get-method '{method_name}' failed for {address} with exit code {exit_code}"
207
+ f"get-method `{method_name}` failed: exit code {exit_code} ({address})"
192
208
  )
193
209
 
194
210
 
File without changes
File without changes
File without changes
File without changes