tonutils 2.0.1b1__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 (79) 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 +18 -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/__init__.py +17 -0
  43. tonutils/contracts/vanity/models.py +39 -0
  44. tonutils/contracts/vanity/tlb.py +40 -0
  45. tonutils/contracts/vanity/vanity.py +40 -0
  46. tonutils/contracts/wallet/__init__.py +2 -0
  47. tonutils/contracts/wallet/base.py +3 -3
  48. tonutils/contracts/wallet/messages.py +1 -1
  49. tonutils/contracts/wallet/methods.py +2 -2
  50. tonutils/{protocols/wallet.py → contracts/wallet/protocol.py} +35 -35
  51. tonutils/contracts/wallet/versions/v5.py +3 -3
  52. tonutils/exceptions.py +134 -226
  53. tonutils/types.py +115 -0
  54. tonutils/utils.py +3 -3
  55. {tonutils-2.0.1b1.dist-info → tonutils-2.0.1b3.dist-info}/METADATA +4 -4
  56. tonutils-2.0.1b3.dist-info/RECORD +93 -0
  57. tonutils/clients/adnl/provider/limiter.py +0 -56
  58. tonutils/clients/adnl/stack.py +0 -64
  59. tonutils/clients/http/chainstack/__init__.py +0 -4
  60. tonutils/clients/http/chainstack/client.py +0 -63
  61. tonutils/clients/http/chainstack/provider.py +0 -44
  62. tonutils/clients/http/quicknode/__init__.py +0 -4
  63. tonutils/clients/http/quicknode/client.py +0 -60
  64. tonutils/clients/http/quicknode/provider.py +0 -42
  65. tonutils/clients/http/tatum/__init__.py +0 -4
  66. tonutils/clients/http/tatum/client.py +0 -66
  67. tonutils/clients/http/tatum/provider.py +0 -53
  68. tonutils/clients/http/tonapi/__init__.py +0 -4
  69. tonutils/clients/http/tonapi/provider.py +0 -150
  70. tonutils/clients/http/tonapi/stack.py +0 -71
  71. tonutils/clients/http/toncenter/__init__.py +0 -4
  72. tonutils/clients/http/toncenter/provider.py +0 -145
  73. tonutils/clients/http/toncenter/stack.py +0 -73
  74. tonutils/protocols/__init__.py +0 -9
  75. tonutils-2.0.1b1.dist-info/RECORD +0 -94
  76. /tonutils/{protocols/client.py → clients/protocol.py} +0 -0
  77. {tonutils-2.0.1b1.dist-info → tonutils-2.0.1b3.dist-info}/WHEEL +0 -0
  78. {tonutils-2.0.1b1.dist-info → tonutils-2.0.1b3.dist-info}/licenses/LICENSE +0 -0
  79. {tonutils-2.0.1b1.dist-info → tonutils-2.0.1b3.dist-info}/top_level.txt +0 -0
@@ -7,11 +7,20 @@ from aiohttp import ClientSession
7
7
  from pytoniq_core import Cell, Slice, Transaction
8
8
 
9
9
  from tonutils.clients.base import BaseClient
10
- from tonutils.clients.http.toncenter.models import SendBocPayload, RunGetMethodPayload
11
- from tonutils.clients.http.toncenter.provider import ToncenterHttpProvider
12
- from tonutils.clients.http.toncenter.stack import decode_stack, encode_stack
13
- from tonutils.exceptions import ClientError, ClientNotConnectedError
14
- from tonutils.types import ClientType, ContractState, ContractStateInfo, NetworkGlobalID
10
+ from tonutils.clients.http.providers.toncenter.models import (
11
+ SendBocPayload,
12
+ RunGetMethodPayload,
13
+ )
14
+ from tonutils.clients.http.providers.toncenter.provider import ToncenterHttpProvider
15
+ from tonutils.clients.http.utils import decode_toncenter_stack, encode_toncenter_stack
16
+ from tonutils.exceptions import ClientError, RunGetMethodError
17
+ from tonutils.types import (
18
+ ClientType,
19
+ ContractState,
20
+ ContractStateInfo,
21
+ NetworkGlobalID,
22
+ RetryPolicy,
23
+ )
15
24
  from tonutils.utils import cell_to_hex, parse_stack_config
16
25
 
17
26
 
@@ -26,11 +35,13 @@ class ToncenterHttpClient(BaseClient):
26
35
  network: NetworkGlobalID = NetworkGlobalID.MAINNET,
27
36
  api_key: t.Optional[str] = None,
28
37
  base_url: t.Optional[str] = None,
29
- timeout: int = 10,
38
+ timeout: float = 10.0,
30
39
  session: t.Optional[ClientSession] = None,
40
+ headers: t.Optional[t.Dict[str, str]] = None,
41
+ cookies: t.Optional[t.Dict[str, str]] = None,
31
42
  rps_limit: t.Optional[int] = None,
32
43
  rps_period: float = 1.0,
33
- rps_retries: int = 2,
44
+ retry_policy: t.Optional[RetryPolicy] = None,
34
45
  ) -> None:
35
46
  """
36
47
  Initialize Toncenter HTTP client.
@@ -39,11 +50,13 @@ class ToncenterHttpClient(BaseClient):
39
50
  :param api_key: Optional Toncenter API key
40
51
  You can get an API key on the Toncenter telegram bot: https://t.me/toncenter
41
52
  :param base_url: Custom Toncenter endpoint base URL
42
- :param timeout: HTTP request timeout in seconds
43
- :param session: Optional externally managed aiohttp.ClientSession
44
- :param rps_limit: Optional requests-per-second limit for rate limiting
45
- :param rps_period: Time window in seconds for RPS limit
46
- :param rps_retries: Number of retries on rate limiting
53
+ :param timeout: Total request timeout in seconds.
54
+ :param session: Optional external aiohttp session.
55
+ :param headers: Default headers for owned session.
56
+ :param cookies: Default cookies for owned session.
57
+ :param rps_limit: Optional requests-per-period limit.
58
+ :param rps_period: Rate limit period in seconds.
59
+ :param retry_policy: Optional retry policy that defines per-error-code retry rules
47
60
  """
48
61
  self.network: NetworkGlobalID = network
49
62
  self._provider: ToncenterHttpProvider = ToncenterHttpProvider(
@@ -52,34 +65,24 @@ class ToncenterHttpClient(BaseClient):
52
65
  base_url=base_url,
53
66
  timeout=timeout,
54
67
  session=session,
68
+ headers=headers,
69
+ cookies=cookies,
55
70
  rps_limit=rps_limit,
56
71
  rps_period=rps_period,
57
- rps_retries=rps_retries,
72
+ retry_policy=retry_policy,
58
73
  )
59
74
 
60
75
  @property
61
76
  def provider(self) -> ToncenterHttpProvider:
62
- """
63
- Underlying Toncenter HTTP provider.
64
-
65
- :return: ToncenterHttpProvider instance used for all HTTP requests
66
- """
67
- if not self.is_connected:
68
- raise ClientNotConnectedError(self)
69
77
  return self._provider
70
78
 
71
79
  @property
72
80
  def is_connected(self) -> bool:
73
- """
74
- Check whether HTTP session is initialized and open.
75
-
76
- :return: True if session exists and is not closed, False otherwise
77
- """
78
81
  session = self._provider.session
79
82
  return session is not None and not session.closed
80
83
 
81
84
  async def __aenter__(self) -> ToncenterHttpClient:
82
- await self._provider.__aenter__()
85
+ await self._provider.connect()
83
86
  return self
84
87
 
85
88
  async def __aexit__(
@@ -88,7 +91,7 @@ class ToncenterHttpClient(BaseClient):
88
91
  exc_value: t.Optional[BaseException],
89
92
  traceback: t.Optional[t.Any],
90
93
  ) -> None:
91
- await self._provider.__aexit__(exc_type, exc_value, traceback)
94
+ await self._provider.close()
92
95
 
93
96
  async def _send_boc(self, boc: str) -> None:
94
97
  payload = SendBocPayload(boc=boc)
@@ -131,19 +134,25 @@ class ToncenterHttpClient(BaseClient):
131
134
 
132
135
  last_transaction_lt = last_transaction_hash = None
133
136
 
134
- if request.result.last_transaction_id:
137
+ tx_id = request.result.last_transaction_id
138
+ if tx_id is not None:
135
139
  try:
136
- lt = int(request.result.last_transaction_id.lt)
137
- last_transaction_lt = lt if lt > 0 else None
138
- except (TypeError, ValueError):
139
- last_transaction_lt = None
140
- try:
141
- raw = request.result.last_transaction_id.hash
142
- decoded = base64.b64decode(raw)
143
- h = decoded.hex()
144
- last_transaction_hash = None if h == "00" * 32 else h
145
- except (Exception,):
146
- last_transaction_hash = None
140
+ lt = int(tx_id.lt) if tx_id.lt is not None else 0
141
+ except (ValueError,):
142
+ pass
143
+ else:
144
+ if lt > 0:
145
+ last_transaction_lt = lt
146
+
147
+ raw = tx_id.hash
148
+ if raw is not None:
149
+ try:
150
+ h = base64.b64decode(raw).hex()
151
+ except (Exception,):
152
+ pass
153
+ else:
154
+ if h != "00" * 32:
155
+ last_transaction_hash = h
147
156
 
148
157
  contract_info.last_transaction_lt = last_transaction_lt
149
158
  contract_info.last_transaction_hash = last_transaction_hash
@@ -191,21 +200,23 @@ class ToncenterHttpClient(BaseClient):
191
200
  payload = RunGetMethodPayload(
192
201
  address=address,
193
202
  method=method_name,
194
- stack=encode_stack(stack or []),
203
+ stack=encode_toncenter_stack(stack or []),
195
204
  )
196
205
  request = await self.provider.run_get_method(payload=payload)
197
206
  if request.result is None:
198
207
  return []
199
- return decode_stack(request.result.stack or [])
200
208
 
201
- async def connect(self) -> None:
202
- """
203
- Ensure that HTTP session is initialized.
209
+ if request.result.exit_code != 0:
210
+ raise RunGetMethodError(
211
+ address=address,
212
+ method_name=method_name,
213
+ exit_code=request.result.exit_code,
214
+ )
204
215
 
205
- Safe to call multiple times; underlying provider will reuse the session.
206
- """
207
- await self._provider.ensure_session()
216
+ return decode_toncenter_stack(request.result.stack or [])
217
+
218
+ async def connect(self) -> None:
219
+ await self._provider.connect()
208
220
 
209
221
  async def close(self) -> None:
210
- """Close HTTP session if it is owned by the provider."""
211
222
  await self._provider.close()
@@ -0,0 +1,4 @@
1
+ from .tonapi import TonapiHttpProvider
2
+ from .toncenter import ToncenterHttpProvider
3
+
4
+ __all__ = ["TonapiHttpProvider", "ToncenterHttpProvider"]
@@ -0,0 +1,201 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import typing as t
5
+
6
+ import aiohttp
7
+
8
+ from tonutils.clients.http.providers.response import HttpResponse
9
+ from tonutils.clients.limiter import RateLimiter
10
+ from tonutils.exceptions import (
11
+ NotConnectedError,
12
+ ProviderResponseError,
13
+ ProviderTimeoutError,
14
+ RetryLimitError,
15
+ )
16
+ from tonutils.types import RetryPolicy
17
+
18
+
19
+ class HttpProvider:
20
+ """HTTP-based provider for TON HTTP APIs."""
21
+
22
+ def __init__(
23
+ self,
24
+ *,
25
+ base_url: str,
26
+ timeout: float = 10.0,
27
+ session: t.Optional[aiohttp.ClientSession] = None,
28
+ headers: t.Optional[t.Dict[str, str]] = None,
29
+ cookies: t.Optional[t.Dict[str, str]] = None,
30
+ rps_limit: t.Optional[int] = None,
31
+ rps_period: float = 1.0,
32
+ retry_policy: t.Optional[RetryPolicy] = None,
33
+ ) -> None:
34
+ """Initialize HTTP provider.
35
+
36
+ :param base_url: Base endpoint URL without trailing slash.
37
+ :param timeout: Total request timeout in seconds.
38
+ :param session: Optional external aiohttp session.
39
+ :param headers: Default headers for owned session.
40
+ :param cookies: Default cookies for owned session.
41
+ :param rps_limit: Optional requests-per-period limit.
42
+ :param rps_period: Rate limit period in seconds.
43
+ :param retry_policy: Optional retry policy that defines per-error-code retry rules
44
+ """
45
+ self._base_url = base_url.rstrip("/")
46
+ self._timeout = timeout
47
+ self._headers = headers
48
+ self._cookies = cookies
49
+
50
+ self._session = session
51
+ self._owns_session = session is None
52
+
53
+ self._limiter = (
54
+ RateLimiter(max_rate=rps_limit, period=rps_period)
55
+ if rps_limit is not None
56
+ else None
57
+ )
58
+ self._retry_policy = retry_policy
59
+ self._connect_lock = asyncio.Lock()
60
+
61
+ @property
62
+ def session(self) -> t.Optional[aiohttp.ClientSession]:
63
+ """Underlying aiohttp session, or None if not connected."""
64
+ return self._session
65
+
66
+ @property
67
+ def is_connected(self) -> bool:
68
+ """Check whether the provider session is initialized and open."""
69
+ return self._session is not None and not self._session.closed
70
+
71
+ async def connect(self) -> None:
72
+ """Initialize HTTP session if not already connected."""
73
+ if self.is_connected:
74
+ return
75
+
76
+ async with self._connect_lock:
77
+ if self.is_connected:
78
+ return
79
+
80
+ self._session = aiohttp.ClientSession(
81
+ headers=self._headers,
82
+ cookies=self._cookies,
83
+ timeout=aiohttp.ClientTimeout(total=self._timeout),
84
+ )
85
+
86
+ async def close(self) -> None:
87
+ """Close owned HTTP session and release resources."""
88
+ async with self._connect_lock:
89
+ if self._owns_session and self._session and not self._session.closed:
90
+ await self._session.close()
91
+ self._session = None
92
+
93
+ async def _send_once(
94
+ self,
95
+ method: str,
96
+ path: str,
97
+ *,
98
+ params: t.Any = None,
99
+ json_data: t.Any = None,
100
+ ) -> t.Any:
101
+ """Send a single HTTP request.
102
+
103
+ Performs exactly one request attempt:
104
+ - applies rate limiting if configured
105
+ - converts network and protocol errors into provider exceptions
106
+
107
+ :param method: HTTP method (GET, POST, etc.).
108
+ :param path: Endpoint path relative to base_url.
109
+ :param params: Optional query parameters.
110
+ :param json_data: Optional JSON body.
111
+ :return: Parsed response payload.
112
+ """
113
+ if not self.is_connected:
114
+ raise NotConnectedError()
115
+
116
+ assert self._session is not None
117
+ url = f"{self._base_url}/{path.lstrip('/')}"
118
+
119
+ try:
120
+ if self._limiter:
121
+ await self._limiter.acquire()
122
+
123
+ async with self._session.request(
124
+ method=method,
125
+ url=url,
126
+ params=params,
127
+ json=json_data,
128
+ ) as resp:
129
+ data = await HttpResponse.read(resp)
130
+ if resp.status >= 400:
131
+ HttpResponse.raise_error(
132
+ status=int(resp.status),
133
+ url=url,
134
+ data=data,
135
+ )
136
+ return data
137
+
138
+ except asyncio.TimeoutError as exc:
139
+ raise ProviderTimeoutError(
140
+ timeout=self._timeout,
141
+ endpoint=url,
142
+ operation="http request",
143
+ ) from exc
144
+
145
+ except aiohttp.ClientError as exc:
146
+ raise ProviderResponseError(
147
+ code=0,
148
+ message=str(exc),
149
+ endpoint=url,
150
+ ) from exc
151
+
152
+ async def send_http_request(
153
+ self,
154
+ method: str,
155
+ path: str,
156
+ *,
157
+ params: t.Any = None,
158
+ json_data: t.Any = None,
159
+ ) -> t.Any:
160
+ """Send an HTTP request with retry handling.
161
+
162
+ On provider error, retries the request according to the retry policy
163
+ matched by error code and message. If no rule matches, or retry attempts
164
+ are exhausted, the error is raised.
165
+
166
+ :param method: HTTP method.
167
+ :param path: Endpoint path relative to base_url.
168
+ :param params: Optional query parameters.
169
+ :param json_data: Optional JSON body.
170
+ :return: Parsed response payload.
171
+ """
172
+ attempts: t.Dict[int, int] = {}
173
+
174
+ while True:
175
+ try:
176
+ return await self._send_once(
177
+ method,
178
+ path,
179
+ params=params,
180
+ json_data=json_data,
181
+ )
182
+ except ProviderResponseError as e:
183
+ policy = self._retry_policy
184
+ if policy is None:
185
+ raise
186
+
187
+ rule = policy.rule_for(e.code, e.message)
188
+ if rule is None:
189
+ raise
190
+
191
+ key = id(rule)
192
+ attempts[key] = attempts.get(key, 0) + 1
193
+
194
+ if attempts[key] >= rule.attempts:
195
+ raise RetryLimitError(
196
+ attempts=attempts[key],
197
+ max_attempts=rule.attempts,
198
+ last_error=e,
199
+ ) from e
200
+
201
+ await asyncio.sleep(rule.delay(attempts[key] - 1))
@@ -0,0 +1,85 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import typing as t
5
+
6
+ import aiohttp
7
+
8
+ from tonutils.exceptions import ProviderResponseError, CDN_CHALLENGE_MARKERS
9
+
10
+
11
+ class HttpResponse:
12
+
13
+ @classmethod
14
+ async def read(cls, resp: aiohttp.ClientResponse) -> t.Any:
15
+ body = await resp.read()
16
+ if not body:
17
+ return ""
18
+
19
+ data = body.decode("utf-8", errors="replace").strip()
20
+ if not data:
21
+ return ""
22
+
23
+ try:
24
+ return json.loads(data)
25
+ except (Exception,):
26
+ return data
27
+
28
+ @classmethod
29
+ def raise_error(
30
+ cls,
31
+ *,
32
+ status: int,
33
+ url: str,
34
+ data: t.Any,
35
+ ) -> None:
36
+ exc = cls._detect_proxy_error(data, status=status, url=url)
37
+ if exc is not None:
38
+ raise exc
39
+
40
+ message = cls._extract_error_message(data)
41
+ raise ProviderResponseError(
42
+ code=status,
43
+ message=message,
44
+ endpoint=url,
45
+ )
46
+
47
+ @classmethod
48
+ def _detect_proxy_error(
49
+ cls,
50
+ data: t.Any,
51
+ *,
52
+ status: int,
53
+ url: str,
54
+ ) -> t.Optional[ProviderResponseError]:
55
+ body = (
56
+ " ".join(str(v) for v in data.values())
57
+ if isinstance(data, dict)
58
+ else str(data)
59
+ ).lower()
60
+
61
+ for marker, message in CDN_CHALLENGE_MARKERS.items():
62
+ if marker in body:
63
+ return ProviderResponseError(
64
+ code=status,
65
+ message=message,
66
+ endpoint=url,
67
+ )
68
+
69
+ return None
70
+
71
+ @staticmethod
72
+ def _extract_error_message(data: t.Any) -> str:
73
+ if isinstance(data, dict):
74
+ lowered = {k.lower(): v for k, v in data.items()}
75
+ for key in ("error", "message", "detail", "description"):
76
+ if key in lowered and isinstance(lowered[key], str):
77
+ return lowered[key]
78
+ string_values = [str(v) for v in data.values() if isinstance(v, str)]
79
+ return "; ".join(string_values) if string_values else str(data)
80
+
81
+ if isinstance(data, list):
82
+ return "; ".join(map(str, data))
83
+ if isinstance(data, str):
84
+ return data
85
+ return repr(data)
@@ -0,0 +1,3 @@
1
+ from .provider import TonapiHttpProvider
2
+
3
+ __all__ = ["TonapiHttpProvider"]
@@ -44,3 +44,4 @@ class BlockchainAccountMethodResult(BaseModel):
44
44
  """Result model for /blockchain/accounts/{address}/methods/{method_name}."""
45
45
 
46
46
  stack: t.Optional[t.List[t.Any]] = None
47
+ exit_code: int
@@ -0,0 +1,125 @@
1
+ from __future__ import annotations
2
+
3
+ import typing as t
4
+
5
+ import aiohttp
6
+ from pydantic import BaseModel
7
+
8
+ from tonutils.clients.http.providers.base import HttpProvider
9
+ from tonutils.clients.http.providers.tonapi.models import (
10
+ BlockchainAccountMethodResult,
11
+ BlockchainAccountResult,
12
+ BlockchainAccountTransactionsResult,
13
+ BlockchainConfigResult,
14
+ BlockchainMessagePayload,
15
+ )
16
+ from tonutils.types import NetworkGlobalID, RetryPolicy
17
+
18
+
19
+ class TonapiHttpProvider(HttpProvider):
20
+
21
+ def __init__(
22
+ self,
23
+ network: NetworkGlobalID,
24
+ api_key: str,
25
+ base_url: t.Optional[str] = None,
26
+ timeout: float = 10.0,
27
+ session: t.Optional[aiohttp.ClientSession] = None,
28
+ headers: t.Optional[t.Dict[str, str]] = None,
29
+ cookies: t.Optional[t.Dict[str, str]] = None,
30
+ rps_limit: t.Optional[int] = None,
31
+ rps_period: float = 1.0,
32
+ retry_policy: t.Optional[RetryPolicy] = None,
33
+ ) -> None:
34
+ urls = {
35
+ NetworkGlobalID.MAINNET: "https://tonapi.io/v2",
36
+ NetworkGlobalID.TESTNET: "https://testnet.tonapi.io/v2",
37
+ }
38
+ base_url = base_url or urls[network]
39
+ headers = {**(headers or {}), "Authorization": f"Bearer {api_key}"}
40
+
41
+ super().__init__(
42
+ base_url=base_url,
43
+ session=session,
44
+ headers=headers,
45
+ cookies=cookies,
46
+ timeout=timeout,
47
+ rps_limit=rps_limit,
48
+ rps_period=rps_period,
49
+ retry_policy=retry_policy,
50
+ )
51
+
52
+ @staticmethod
53
+ def _model(model: t.Type[BaseModel], data: t.Any) -> t.Any:
54
+ return model.model_validate(data)
55
+
56
+ async def blockchain_message(
57
+ self,
58
+ payload: BlockchainMessagePayload,
59
+ ) -> None:
60
+ await self.send_http_request(
61
+ "POST",
62
+ "/blockchain/message",
63
+ json_data=payload.model_dump(),
64
+ )
65
+
66
+ async def blockchain_config(
67
+ self,
68
+ ) -> BlockchainConfigResult:
69
+ return self._model(
70
+ BlockchainConfigResult,
71
+ await self.send_http_request(
72
+ "GET",
73
+ "/blockchain/config",
74
+ ),
75
+ )
76
+
77
+ async def blockchain_account(
78
+ self,
79
+ address: str,
80
+ ) -> BlockchainAccountResult:
81
+ return self._model(
82
+ BlockchainAccountResult,
83
+ await self.send_http_request(
84
+ "GET",
85
+ f"/blockchain/accounts/{address}",
86
+ ),
87
+ )
88
+
89
+ async def blockchain_account_transactions(
90
+ self,
91
+ address: str,
92
+ limit: int = 100,
93
+ after_lt: t.Optional[int] = None,
94
+ before_lt: t.Optional[int] = None,
95
+ sort_order: str = "desc",
96
+ ) -> BlockchainAccountTransactionsResult:
97
+ params = {"limit": limit, "sort_order": sort_order}
98
+ if after_lt is not None:
99
+ params["after_lt"] = after_lt
100
+ if before_lt is not None and before_lt > 0:
101
+ params["before_lt"] = before_lt
102
+
103
+ return self._model(
104
+ BlockchainAccountTransactionsResult,
105
+ await self.send_http_request(
106
+ "GET",
107
+ f"/blockchain/accounts/{address}/transactions",
108
+ params=params,
109
+ ),
110
+ )
111
+
112
+ async def blockchain_account_method(
113
+ self,
114
+ address: str,
115
+ method_name: str,
116
+ args: t.List[t.Any],
117
+ ) -> BlockchainAccountMethodResult:
118
+ return self._model(
119
+ BlockchainAccountMethodResult,
120
+ await self.send_http_request(
121
+ "GET",
122
+ f"/blockchain/accounts/{address}/methods/{method_name}",
123
+ params={"args": args} if args else None,
124
+ ),
125
+ )
@@ -0,0 +1,3 @@
1
+ from .provider import ToncenterHttpProvider
2
+
3
+ __all__ = ["ToncenterHttpProvider"]
@@ -87,6 +87,7 @@ class _GetMethod(BaseModel):
87
87
  """Container for TVM stack array."""
88
88
 
89
89
  stack: t.List[t.Any]
90
+ exit_code: int
90
91
 
91
92
 
92
93
  class RunGetMethodPayload(BaseModel):