tonutils 2.0.1b6__py3-none-any.whl → 2.0.1b8__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 (47) hide show
  1. tonutils/__init__.py +8 -13
  2. tonutils/cli.py +1 -1
  3. tonutils/clients/adnl/balancer.py +132 -355
  4. tonutils/clients/adnl/client.py +32 -202
  5. tonutils/clients/adnl/mixin.py +268 -0
  6. tonutils/clients/adnl/provider/config.py +7 -20
  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 +52 -92
  12. tonutils/clients/http/balancer.py +93 -90
  13. tonutils/clients/http/clients/tatum.py +1 -0
  14. tonutils/clients/http/clients/tonapi.py +12 -24
  15. tonutils/clients/http/clients/toncenter.py +15 -33
  16. tonutils/clients/http/provider/base.py +75 -60
  17. tonutils/clients/http/provider/models.py +1 -1
  18. tonutils/clients/http/provider/tonapi.py +0 -5
  19. tonutils/clients/http/provider/toncenter.py +4 -8
  20. tonutils/clients/protocol.py +6 -6
  21. tonutils/contracts/__init__.py +3 -0
  22. tonutils/contracts/base.py +32 -32
  23. tonutils/contracts/protocol.py +9 -9
  24. tonutils/contracts/telegram/tlb.py +1 -1
  25. tonutils/contracts/wallet/__init__.py +4 -0
  26. tonutils/contracts/wallet/base.py +5 -5
  27. tonutils/contracts/wallet/tlb.py +18 -16
  28. tonutils/contracts/wallet/versions/v5.py +6 -6
  29. tonutils/exceptions.py +45 -102
  30. tonutils/tools/block_scanner/__init__.py +5 -1
  31. tonutils/tools/block_scanner/scanner.py +1 -1
  32. tonutils/tools/status_monitor/monitor.py +6 -6
  33. tonutils/types.py +24 -10
  34. tonutils/utils.py +47 -7
  35. {tonutils-2.0.1b6.dist-info → tonutils-2.0.1b8.dist-info}/METADATA +3 -20
  36. {tonutils-2.0.1b6.dist-info → tonutils-2.0.1b8.dist-info}/RECORD +40 -46
  37. {tonutils-2.0.1b6.dist-info → tonutils-2.0.1b8.dist-info}/WHEEL +1 -1
  38. tonutils/__meta__.py +0 -1
  39. tonutils/tonconnect/__init__.py +0 -0
  40. tonutils/tonconnect/bridge/__init__.py +0 -0
  41. tonutils/tonconnect/events.py +0 -0
  42. tonutils/tonconnect/models/__init__.py +0 -0
  43. tonutils/tonconnect/storage.py +0 -0
  44. tonutils/tonconnect/tonconnect.py +0 -0
  45. {tonutils-2.0.1b6.dist-info → tonutils-2.0.1b8.dist-info}/entry_points.txt +0 -0
  46. {tonutils-2.0.1b6.dist-info → tonutils-2.0.1b8.dist-info}/licenses/LICENSE +0 -0
  47. {tonutils-2.0.1b6.dist-info → tonutils-2.0.1b8.dist-info}/top_level.txt +0 -0
@@ -14,6 +14,7 @@ from pytoniq_core import (
14
14
  Transaction,
15
15
  VmStack,
16
16
  deserialize_shard_hashes,
17
+ ShardAccount,
17
18
  )
18
19
  from pytoniq_core.crypto.ciphers import get_random
19
20
  from pytoniq_core.crypto.crc import crc16
@@ -41,7 +42,7 @@ from tonutils.exceptions import (
41
42
  ProviderResponseError,
42
43
  )
43
44
  from tonutils.types import (
44
- ContractStateInfo,
45
+ ContractInfo,
45
46
  RetryPolicy,
46
47
  WorkchainID,
47
48
  )
@@ -95,13 +96,13 @@ class AdnlProvider:
95
96
  self._connect_lock: asyncio.Lock = asyncio.Lock()
96
97
 
97
98
  @property
98
- def is_connected(self) -> bool:
99
+ def connected(self) -> bool:
99
100
  """
100
101
  Whether the underlying transport is currently connected.
101
102
 
102
103
  :return: True if connected, False otherwise
103
104
  """
104
- return self.transport.is_connected
105
+ return self.transport.connected
105
106
 
106
107
  @property
107
108
  def last_mc_block(self) -> BlockIdExt:
@@ -165,7 +166,7 @@ class AdnlProvider:
165
166
 
166
167
  Establishes transport, performs handshake and starts background workers.
167
168
  """
168
- if self.is_connected:
169
+ if self.connected:
169
170
  return
170
171
 
171
172
  self.loop = asyncio.get_running_loop()
@@ -215,8 +216,12 @@ class AdnlProvider:
215
216
  :param priority: Whether to use priority slot in the limiter
216
217
  :return: Lite-server response payload as a decoded dictionary
217
218
  """
218
- if not self.is_connected or self.loop is None:
219
- raise NotConnectedError()
219
+ if not self.connected or self.loop is None:
220
+ raise NotConnectedError(
221
+ component="AdnlProvider",
222
+ endpoint=self.node.endpoint,
223
+ operation="request",
224
+ )
220
225
 
221
226
  if self._limiter is not None:
222
227
  await self._limiter.acquire(priority=priority)
@@ -492,7 +497,7 @@ class AdnlProvider:
492
497
 
493
498
  return block_id, block_obj
494
499
 
495
- async def get_block_transactions_ext(
500
+ async def get_block_transactions(
496
501
  self,
497
502
  block: BlockIdExt,
498
503
  count: int = 1024,
@@ -591,7 +596,7 @@ class AdnlProvider:
591
596
  for sh in v.list
592
597
  ]
593
598
 
594
- async def run_smc_method(
599
+ async def run_get_method(
595
600
  self,
596
601
  address: Address,
597
602
  method_name: str,
@@ -643,7 +648,7 @@ class AdnlProvider:
643
648
  cs = Slice.one_from_boc(result["result"])
644
649
  return VmStack.deserialize(cs)
645
650
 
646
- async def get_config_all(
651
+ async def get_config(
647
652
  self,
648
653
  *,
649
654
  priority: bool = False,
@@ -667,18 +672,18 @@ class AdnlProvider:
667
672
  config_proof = Cell.one_from_boc(result.get("config_proof"))
668
673
  return build_config_all(config_proof)
669
674
 
670
- async def get_account_state(
675
+ async def get_info(
671
676
  self,
672
677
  address: Address,
673
678
  *,
674
679
  priority: bool = False,
675
- ) -> ContractStateInfo:
680
+ ) -> ContractInfo:
676
681
  """
677
682
  Fetch contract state at the latest masterchain block.
678
683
 
679
684
  :param address: Contract address
680
685
  :param priority: Whether to use priority slot in the limiter
681
- :return: ContractStateInfo with balance, code, data and last tx
686
+ :return: ContractInfo with balance, code, data and last tx
682
687
  """
683
688
  if self.last_mc_block is None:
684
689
  await self.updater.refresh()
@@ -693,7 +698,7 @@ class AdnlProvider:
693
698
  priority=priority,
694
699
  )
695
700
  if not result["state"]:
696
- return ContractStateInfo(balance=0)
701
+ return ContractInfo(balance=0)
697
702
 
698
703
  account_state_root = Cell.one_from_boc(result["state"])
699
704
  account = Account.deserialize(account_state_root.begin_parse())
@@ -755,9 +760,9 @@ class AdnlProvider:
755
760
  for i, cell in enumerate(cells):
756
761
  curr_hash = cell.get_hash(0).hex()
757
762
  if curr_hash != prev_tr_hash:
758
- raise ClientError(
759
- f"transaction hash mismatch: "
760
- f"expected {prev_tr_hash}, got {curr_hash}"
763
+ raise ProviderError(
764
+ "getTransactions failed: transaction hash mismatch "
765
+ f"(expected {prev_tr_hash}, got {curr_hash})"
761
766
  )
762
767
 
763
768
  tx = Transaction.deserialize(cell.begin_parse())
@@ -765,3 +770,43 @@ class AdnlProvider:
765
770
  transactions.append(tx)
766
771
 
767
772
  return transactions
773
+
774
+ async def get_account_state(
775
+ self,
776
+ address: Address,
777
+ *,
778
+ priority: bool = False,
779
+ ) -> t.Tuple[t.Optional[Account], t.Optional[ShardAccount]]:
780
+ """
781
+ Fetch account state and shard account from lite-server.
782
+
783
+ :param address: Account address
784
+ :param priority: Whether to use priority slot in the limiter
785
+ :return: Tuple of (Account | None, ShardAccount | None)
786
+ """
787
+ if self.last_mc_block is None:
788
+ await self.updater.refresh()
789
+
790
+ data = {
791
+ "id": self.last_mc_block.to_dict(),
792
+ "account": address.to_tl_account_id(),
793
+ }
794
+ result = await self.send_liteserver_query(
795
+ method="getAccountState",
796
+ data=data,
797
+ priority=priority,
798
+ )
799
+ if not result.get("state"):
800
+ return None, None
801
+
802
+ account_state_root = Cell.one_from_boc(result["state"])
803
+ account = Account.deserialize(account_state_root.begin_parse())
804
+
805
+ shrd_blk = BlockIdExt.from_dict(result["shardblk"])
806
+ shard_account = build_shard_account(
807
+ account_state_root=account_state_root,
808
+ shard_account_descr=result["proof"],
809
+ shrd_blk=shrd_blk,
810
+ address=address,
811
+ )
812
+ return account, shard_account
@@ -64,7 +64,7 @@ class AdnlTcpTransport:
64
64
  self._closing = False
65
65
 
66
66
  @property
67
- def is_connected(self) -> bool:
67
+ def connected(self) -> bool:
68
68
  """Check if the transport is currently connected."""
69
69
  return self._connected
70
70
 
@@ -178,7 +178,7 @@ class AdnlTcpTransport:
178
178
  "connect", f"timeout after {self.connect_timeout}s"
179
179
  ) from exc
180
180
  except OSError as exc:
181
- raise self._error("connect", "connection refused") from exc
181
+ raise self._error("connect", str(exc)) from exc
182
182
 
183
183
  if self.writer is None or self.reader is None:
184
184
  raise self._error("connect", "stream init failed")
@@ -218,7 +218,11 @@ class AdnlTcpTransport:
218
218
  :param payload: Raw ADNL packet bytes
219
219
  """
220
220
  if not self._connected or self.writer is None:
221
- raise NotConnectedError()
221
+ raise NotConnectedError(
222
+ component="ADNL transport",
223
+ endpoint=self.node.endpoint,
224
+ operation="send",
225
+ )
222
226
 
223
227
  packet = self._build_frame(payload)
224
228
  encrypted = self.encrypt_frame(packet)
@@ -233,7 +237,12 @@ class AdnlTcpTransport:
233
237
  Blocks until a complete packet is available from the background reader.
234
238
  """
235
239
  if not self._connected:
236
- raise NotConnectedError()
240
+ raise NotConnectedError(
241
+ component="ADNL transport",
242
+ endpoint=self.node.endpoint,
243
+ operation="recv",
244
+ )
245
+
237
246
  return await self._incoming.get()
238
247
 
239
248
  async def read_frame(self, discard: bool = False) -> t.Optional[bytes]:
@@ -79,5 +79,5 @@ class PingerWorker(BaseWorker):
79
79
  while self.running:
80
80
  await asyncio.sleep(self._interval)
81
81
 
82
- if self.provider.is_connected:
82
+ if self.provider.connected:
83
83
  await self.ping_once()
@@ -15,7 +15,7 @@ from pytoniq_core import (
15
15
  VmTuple,
16
16
  )
17
17
 
18
- from tonutils.types import ContractState, ContractStateInfo, StackItems, StackItem
18
+ from tonutils.types import ContractState, ContractInfo, StackItems, StackItem
19
19
  from tonutils.utils import cell_to_hex, norm_stack_num, norm_stack_cell
20
20
 
21
21
 
@@ -72,17 +72,17 @@ def build_contract_state_info(
72
72
  address: Address,
73
73
  account: Account,
74
74
  shard_account: ShardAccount,
75
- ) -> ContractStateInfo:
75
+ ) -> ContractInfo:
76
76
  """
77
- Build a high-level ContractStateInfo object from raw account data.
77
+ Build a high-level ContractInfo object from raw account data.
78
78
 
79
79
  :param address: Contract address
80
80
  :param account: Raw Account data structure
81
81
  :param shard_account: Parsed ShardAccount entry
82
- :return: Filled ContractStateInfo instance
82
+ :return: Filled ContractInfo instance
83
83
  """
84
84
  simple_account = SimpleAccount.from_raw(account, address)
85
- info = ContractStateInfo(balance=simple_account.balance)
85
+ info = ContractInfo(balance=simple_account.balance)
86
86
 
87
87
  if simple_account.state is not None:
88
88
  state_init = simple_account.state.state_init
tonutils/clients/base.py CHANGED
@@ -1,13 +1,16 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import abc
4
+ import asyncio
4
5
  import typing as t
6
+ from contextlib import suppress
5
7
 
6
8
  from pytoniq_core import Address, Cell, Transaction, begin_cell
7
9
 
10
+ from tonutils.exceptions import ProviderError
8
11
  from tonutils.types import (
9
12
  AddressLike,
10
- ContractStateInfo,
13
+ ContractInfo,
11
14
  ClientType,
12
15
  DNSCategory,
13
16
  NetworkGlobalID,
@@ -36,66 +39,39 @@ class BaseClient(abc.ABC):
36
39
 
37
40
  @property
38
41
  @abc.abstractmethod
39
- def provider(self) -> t.Any:
42
+ def connected(self) -> bool:
40
43
  """
41
- Underlying transport/provider backend used for all requests.
44
+ Check whether provider resources are initialized and usable.
42
45
 
43
- Expected to expose network I/O primitives appropriate for the client
44
- type (HTTP session, ADNL transport, etc.).
46
+ :return: True if client has an active session/connection, False otherwise
45
47
  """
46
- raise NotImplementedError
47
48
 
49
+ @property
48
50
  @abc.abstractmethod
49
- async def __aenter__(self) -> BaseClient:
51
+ def provider(self) -> t.Any:
50
52
  """
51
- Prepare client resources for use.
53
+ Underlying transport/provider backend used for all requests.
52
54
 
53
- Should initialize network connections or sessions as required.
55
+ Expected to expose network I/O primitives appropriate for the client
56
+ type (HTTP session, ADNL transport, etc.).
54
57
  """
55
- raise NotImplementedError
56
58
 
57
59
  @abc.abstractmethod
58
- async def __aexit__(
59
- self,
60
- exc_type: t.Optional[t.Type[BaseException]],
61
- exc_value: t.Optional[BaseException],
62
- traceback: t.Optional[t.Any],
63
- ) -> None:
64
- """
65
- Release allocated client resources.
66
-
67
- Called automatically when the async context ends.
68
- """
69
- raise NotImplementedError
60
+ async def connect(self) -> None:
61
+ """Initialize any required provider resources (sessions, transports, etc.)."""
70
62
 
71
63
  @abc.abstractmethod
72
- async def _send_boc(self, boc: str) -> None:
73
- """
74
- Send an external message to the network using the underlying provider.
75
-
76
- :param boc: Message body serialized as BoC string,
77
- in a format accepted by the underlying provider
78
- """
79
- raise NotImplementedError
64
+ async def close(self) -> None:
65
+ """Close provider resources. Should be safe to call multiple times."""
80
66
 
81
67
  @abc.abstractmethod
82
- async def _get_blockchain_config(self) -> t.Dict[int, t.Any]:
83
- """
84
- Fetch full blockchain configuration from the underlying provider.
85
-
86
- :return: Mapping of configuration parameter ID to parsed value
87
- """
88
- raise NotImplementedError
68
+ async def _send_message(self, boc: str) -> None: ...
89
69
 
90
70
  @abc.abstractmethod
91
- async def _get_contract_info(self, address: str) -> ContractStateInfo:
92
- """
93
- Retrieve basic contract state information from the provider.
71
+ async def _get_config(self) -> t.Dict[int, t.Any]: ...
94
72
 
95
- :param address: Contract address in raw
96
- :return: ContractStateInfo with basic state, balance and last tx data
97
- """
98
- raise NotImplementedError
73
+ @abc.abstractmethod
74
+ async def _get_info(self, address: str) -> ContractInfo: ...
99
75
 
100
76
  @abc.abstractmethod
101
77
  async def _get_transactions(
@@ -104,20 +80,7 @@ class BaseClient(abc.ABC):
104
80
  limit: int = 100,
105
81
  from_lt: t.Optional[int] = None,
106
82
  to_lt: t.Optional[int] = None,
107
- ) -> t.List[Transaction]:
108
- """
109
- Fetch transaction history for a contract.
110
- Returns transactions in the range (to_lt, from_lt), ordered from newest to oldest.
111
-
112
- :param address: Contract address as string
113
- :param limit: Maximum number of transactions to return
114
- :param from_lt: Upper bound logical time (inclusive).
115
- If None, starts from the most recent transaction.
116
- :param to_lt: Lower bound logical time (exclusive).
117
- If None or 0, no lower bound is applied.
118
- :return: List of Transaction objects ordered from newest to oldest
119
- """
120
- raise NotImplementedError
83
+ ) -> t.List[Transaction]: ...
121
84
 
122
85
  @abc.abstractmethod
123
86
  async def _run_get_method(
@@ -125,63 +88,57 @@ class BaseClient(abc.ABC):
125
88
  address: str,
126
89
  method_name: str,
127
90
  stack: t.Optional[t.List[t.Any]] = None,
128
- ) -> t.List[t.Any]:
129
- """
130
- Execute a contract get-method via the provider.
91
+ ) -> t.List[t.Any]: ...
131
92
 
132
- :param address: Contract address in raw
133
- :param method_name: Name of the get-method to execute
134
- :param stack: Optional initial TVM stack items for the call
135
- :return: Decoded TVM stack items returned by the method
93
+ async def __aenter__(self) -> BaseClient:
136
94
  """
137
- raise NotImplementedError
95
+ Prepare client resources for use.
138
96
 
139
- @property
140
- @abc.abstractmethod
141
- def is_connected(self) -> bool:
97
+ Should initialize network connections or sessions as required.
142
98
  """
143
- Check whether provider resources are initialized and usable.
99
+ await self.connect()
100
+ return self
144
101
 
145
- :return: True if client has an active session/connection, False otherwise
102
+ async def __aexit__(
103
+ self,
104
+ exc_type: t.Optional[t.Type[BaseException]],
105
+ exc_value: t.Optional[BaseException],
106
+ traceback: t.Optional[t.Any],
107
+ ) -> None:
146
108
  """
147
- raise NotImplementedError
148
-
149
- @abc.abstractmethod
150
- async def connect(self) -> None:
151
- """Initialize any required provider resources (sessions, transports, etc.)."""
152
- raise NotImplementedError
109
+ Release allocated client resources.
153
110
 
154
- @abc.abstractmethod
155
- async def close(self) -> None:
156
- """Close provider resources. Should be safe to call multiple times."""
157
- raise NotImplementedError
111
+ Called automatically when the async context ends.
112
+ """
113
+ with suppress(asyncio.CancelledError):
114
+ await self.close()
158
115
 
159
- async def send_boc(self, boc: str) -> None:
116
+ async def send_message(self, boc: str) -> None:
160
117
  """
161
118
  Send an external message to the blockchain.
162
119
 
163
120
  :param boc: Message body serialized as BoC string, in a format accepted by the underlying provider
164
121
  """
165
- await self._send_boc(boc)
122
+ await self._send_message(boc)
166
123
 
167
- async def get_blockchain_config(self) -> t.Dict[int, t.Any]:
124
+ async def get_config(self) -> t.Dict[int, t.Any]:
168
125
  """
169
126
  Fetch and decode global blockchain configuration.
170
127
 
171
128
  :return: Mapping of configuration parameter ID to parsed value
172
129
  """
173
- return await self._get_blockchain_config()
130
+ return await self._get_config()
174
131
 
175
- async def get_contract_info(self, address: AddressLike) -> ContractStateInfo:
132
+ async def get_info(self, address: AddressLike) -> ContractInfo:
176
133
  """
177
134
  Fetch basic state information for a smart contract.
178
135
 
179
136
  :param address: Contract address as Address object or string
180
- :return: ContractStateInfo with code, data, balance and last tx data
137
+ :return: ContractInfo with code, data, balance and last tx data
181
138
  """
182
139
  if isinstance(address, Address):
183
140
  address = Address(address).to_str(is_user_friendly=False)
184
- return await self._get_contract_info(address=address)
141
+ return await self._get_info(address=address)
185
142
 
186
143
  async def get_transactions(
187
144
  self,
@@ -192,7 +149,6 @@ class BaseClient(abc.ABC):
192
149
  ) -> t.List[Transaction]:
193
150
  """
194
151
  Fetch transaction history for a contract.
195
- Returns transactions in the range (to_lt, from_lt), ordered from newest to oldest.
196
152
 
197
153
  :param address: Contract address as Address object or string
198
154
  :param limit: Maximum number of transactions to return
@@ -262,7 +218,7 @@ class BaseClient(abc.ABC):
262
218
  if isinstance(domain, str):
263
219
  domain = encode_dns_name(domain)
264
220
  if dns_root_address is None:
265
- blockchain_config = await self.get_blockchain_config()
221
+ blockchain_config = await self.get_config()
266
222
  hash_part = blockchain_config[4].dns_root_addr
267
223
  dns_root_address = Address((WorkchainID.MASTERCHAIN.value, hash_part))
268
224
 
@@ -274,7 +230,9 @@ class BaseClient(abc.ABC):
274
230
  stack=[domain_cell.to_slice(), category.value],
275
231
  )
276
232
  if len(res) < 2:
277
- raise ValueError(f"`dnsresolve` returned {len(res)} items, but 2 expected.")
233
+ raise ProviderError(
234
+ f"dnsresolve failed: invalid response (expected 2 stack items, got {len(res)})"
235
+ )
278
236
 
279
237
  blen = len(domain) * 8
280
238
  rlen = t.cast(int, res[0])
@@ -285,7 +243,9 @@ class BaseClient(abc.ABC):
285
243
  return None
286
244
 
287
245
  if rlen % 8 != 0 or rlen > blen:
288
- raise ValueError(f"Invalid resolved length: result {rlen}, bytes {blen}.")
246
+ raise ProviderError(
247
+ f"dnsresolve failed: invalid resolved length {rlen} bits (domain {blen} bits)"
248
+ )
289
249
  if rlen == blen:
290
250
  # noinspection PyProtectedMember
291
251
  tcls = DNSRecords._DNS_RECORDS_CLASSES.get(category.name.lower())