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
@@ -7,32 +7,29 @@ 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.providers.toncenter.models import (
11
- SendBocPayload,
12
- RunGetMethodPayload,
13
- )
14
- from tonutils.clients.http.providers.toncenter.provider import ToncenterHttpProvider
10
+ from tonutils.clients.http.provider.models import SendBocPayload, RunGetMethodPayload
11
+ from tonutils.clients.http.provider.toncenter import ToncenterHttpProvider
15
12
  from tonutils.clients.http.utils import decode_toncenter_stack, encode_toncenter_stack
16
13
  from tonutils.exceptions import ClientError, RunGetMethodError
17
14
  from tonutils.types import (
18
15
  ClientType,
19
16
  ContractState,
20
- ContractStateInfo,
17
+ ContractInfo,
21
18
  NetworkGlobalID,
22
19
  RetryPolicy,
23
20
  )
24
21
  from tonutils.utils import cell_to_hex, parse_stack_config
25
22
 
26
23
 
27
- class ToncenterHttpClient(BaseClient):
24
+ class ToncenterClient(BaseClient):
28
25
  """TON blockchain client using Toncenter HTTP API as transport."""
29
26
 
30
27
  TYPE = ClientType.HTTP
31
28
 
32
29
  def __init__(
33
30
  self,
34
- *,
35
31
  network: NetworkGlobalID = NetworkGlobalID.MAINNET,
32
+ *,
36
33
  api_key: t.Optional[str] = None,
37
34
  base_url: t.Optional[str] = None,
38
35
  timeout: float = 10.0,
@@ -73,56 +70,44 @@ class ToncenterHttpClient(BaseClient):
73
70
  )
74
71
 
75
72
  @property
76
- def provider(self) -> ToncenterHttpProvider:
77
- return self._provider
78
-
79
- @property
80
- def is_connected(self) -> bool:
73
+ def connected(self) -> bool:
81
74
  session = self._provider.session
82
75
  return session is not None and not session.closed
83
76
 
84
- async def __aenter__(self) -> ToncenterHttpClient:
77
+ @property
78
+ def provider(self) -> ToncenterHttpProvider:
79
+ return self._provider
80
+
81
+ async def connect(self) -> None:
85
82
  await self._provider.connect()
86
- return self
87
83
 
88
- async def __aexit__(
89
- self,
90
- exc_type: t.Optional[t.Type[BaseException]],
91
- exc_value: t.Optional[BaseException],
92
- traceback: t.Optional[t.Any],
93
- ) -> None:
84
+ async def close(self) -> None:
94
85
  await self._provider.close()
95
86
 
96
- async def _send_boc(self, boc: str) -> None:
87
+ async def _send_message(self, boc: str) -> None:
97
88
  payload = SendBocPayload(boc=boc)
98
89
  return await self.provider.send_boc(payload=payload)
99
90
 
100
- async def _get_blockchain_config(self) -> t.Dict[int, t.Any]:
91
+ async def _get_config(self) -> t.Dict[int, t.Any]:
101
92
  request = await self.provider.get_config_all()
102
93
 
103
94
  if request.result is None:
104
- raise ClientError(
105
- "Invalid get_config_all response: missing 'result' field."
106
- )
95
+ raise ClientError("Invalid get_config response: missing `result`.")
107
96
 
108
97
  if request.result.config is None:
109
- raise ClientError(
110
- "Invalid config response: missing 'config' section in result."
111
- )
98
+ raise ClientError("Invalid config response: missing `config` in `result`.")
112
99
 
113
100
  if request.result.config.bytes is None:
114
- raise ClientError(
115
- "Invalid config response: missing 'bytes' field in 'config' section."
116
- )
101
+ raise ClientError("Invalid config response: missing `config.bytes`.")
117
102
 
118
103
  config_cell = Cell.one_from_boc(request.result.config.bytes)
119
104
  config_slice = config_cell.begin_parse()
120
105
  return parse_stack_config(config_slice)
121
106
 
122
- async def _get_contract_info(self, address: str) -> ContractStateInfo:
107
+ async def _get_info(self, address: str) -> ContractInfo:
123
108
  request = await self.provider.get_address_information(address)
124
109
 
125
- contract_info = ContractStateInfo(
110
+ contract_info = ContractInfo(
126
111
  balance=int(request.result.balance),
127
112
  state=ContractState(request.result.state),
128
113
  )
@@ -166,28 +151,58 @@ class ToncenterHttpClient(BaseClient):
166
151
 
167
152
  return contract_info
168
153
 
169
- async def _get_contract_transactions(
154
+ async def _get_transactions(
170
155
  self,
171
156
  address: str,
172
157
  limit: int = 100,
173
158
  from_lt: t.Optional[int] = None,
174
- to_lt: int = 0,
159
+ to_lt: t.Optional[int] = None,
175
160
  ) -> t.List[Transaction]:
176
- if from_lt is not None:
177
- from_lt += 1
161
+ to_lt = 0 if to_lt is None else to_lt
162
+ transactions: t.List[Transaction] = []
178
163
 
179
- request = await self.provider.get_transaction(
180
- address=address,
181
- limit=limit,
182
- from_lt=from_lt,
183
- to_lt=to_lt,
184
- )
164
+ curr_lt: t.Optional[int] = None
165
+ curr_hash: t.Optional[str] = None
166
+
167
+ while len(transactions) < limit:
168
+ request = await self.provider.get_transactions(
169
+ address=address,
170
+ limit=100,
171
+ lt=curr_lt,
172
+ from_hash=curr_hash,
173
+ to_lt=to_lt if to_lt > 0 else None,
174
+ )
175
+
176
+ batch = []
177
+ for tx in request.result or []:
178
+ if tx.data is not None:
179
+ tx_slice = Slice.one_from_boc(tx.data)
180
+ batch.append(Transaction.deserialize(tx_slice))
181
+
182
+ if not batch:
183
+ break
184
+
185
+ for tx in batch:
186
+ # Skip transactions above from_lt (if specified)
187
+ if from_lt is not None and tx.lt > from_lt:
188
+ continue
189
+
190
+ # Stop if we've reached the lower bound
191
+ if to_lt > 0 and tx.lt <= to_lt:
192
+ return transactions[:limit]
185
193
 
186
- transactions = []
187
- for tx in request.result or []:
188
- if tx.data is not None:
189
- tx_slice = Slice.one_from_boc(tx.data)
190
- transactions.append(Transaction.deserialize(tx_slice))
194
+ transactions.append(tx)
195
+
196
+ if len(transactions) >= limit:
197
+ return transactions
198
+
199
+ # Setup for next iteration
200
+ last_tx = batch[-1]
201
+ if last_tx.prev_trans_lt == 0:
202
+ break
203
+
204
+ curr_lt = last_tx.prev_trans_lt
205
+ curr_hash = last_tx.prev_trans_hash.hex()
191
206
 
192
207
  return transactions
193
208
 
@@ -214,9 +229,3 @@ class ToncenterHttpClient(BaseClient):
214
229
  )
215
230
 
216
231
  return decode_toncenter_stack(request.result.stack or [])
217
-
218
- async def connect(self) -> None:
219
- await self._provider.connect()
220
-
221
- async def close(self) -> None:
222
- await self._provider.close()
@@ -1,4 +1,7 @@
1
1
  from .tonapi import TonapiHttpProvider
2
2
  from .toncenter import ToncenterHttpProvider
3
3
 
4
- __all__ = ["TonapiHttpProvider", "ToncenterHttpProvider"]
4
+ __all__ = [
5
+ "TonapiHttpProvider",
6
+ "ToncenterHttpProvider",
7
+ ]
@@ -1,17 +1,21 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
+ import json
4
5
  import typing as t
5
6
 
6
7
  import aiohttp
8
+ from pydantic import BaseModel, ValidationError
7
9
 
8
- from tonutils.clients.http.providers.response import HttpResponse
9
10
  from tonutils.clients.limiter import RateLimiter
10
11
  from tonutils.exceptions import (
11
12
  NotConnectedError,
12
13
  ProviderResponseError,
13
14
  ProviderTimeoutError,
14
15
  RetryLimitError,
16
+ CDN_CHALLENGE_MARKERS,
17
+ TransportError,
18
+ ProviderError,
15
19
  )
16
20
  from tonutils.types import RetryPolicy
17
21
 
@@ -64,17 +68,77 @@ class HttpProvider:
64
68
  return self._session
65
69
 
66
70
  @property
67
- def is_connected(self) -> bool:
71
+ def connected(self) -> bool:
68
72
  """Check whether the provider session is initialized and open."""
69
73
  return self._session is not None and not self._session.closed
70
74
 
75
+ @staticmethod
76
+ def _model(model: t.Type[BaseModel], data: t.Any) -> t.Any:
77
+ try:
78
+ return model.model_validate(data)
79
+ except ValidationError as e:
80
+ raise ProviderError(
81
+ f"invalid response: {model.__name__} validation failed"
82
+ ) from e
83
+
84
+ async def send_http_request(
85
+ self,
86
+ method: str,
87
+ path: str,
88
+ *,
89
+ params: t.Any = None,
90
+ json_data: t.Any = None,
91
+ ) -> t.Any:
92
+ """Send an HTTP request with retry handling.
93
+
94
+ On provider error, retries the request according to the retry policy
95
+ matched by error code and message. If no rule matches, or retry attempts
96
+ are exhausted, the error is raised.
97
+
98
+ :param method: HTTP method.
99
+ :param path: Endpoint path relative to base_url.
100
+ :param params: Optional query parameters.
101
+ :param json_data: Optional JSON body.
102
+ :return: Parsed response payload.
103
+ """
104
+ attempts: t.Dict[int, int] = {}
105
+
106
+ while True:
107
+ try:
108
+ return await self._send_once(
109
+ method,
110
+ path,
111
+ params=params,
112
+ json_data=json_data,
113
+ )
114
+ except ProviderResponseError as e:
115
+ policy = self._retry_policy
116
+ if policy is None:
117
+ raise
118
+
119
+ rule = policy.rule_for(e.code, e.message)
120
+ if rule is None:
121
+ raise
122
+
123
+ key = id(rule)
124
+ attempts[key] = attempts.get(key, 0) + 1
125
+
126
+ if attempts[key] >= rule.attempts:
127
+ raise RetryLimitError(
128
+ attempts=attempts[key],
129
+ max_attempts=rule.attempts,
130
+ last_error=e,
131
+ ) from e
132
+
133
+ await asyncio.sleep(rule.delay(attempts[key] - 1))
134
+
71
135
  async def connect(self) -> None:
72
136
  """Initialize HTTP session if not already connected."""
73
- if self.is_connected:
137
+ if self.connected:
74
138
  return
75
139
 
76
140
  async with self._connect_lock:
77
- if self.is_connected:
141
+ if self.connected:
78
142
  return
79
143
 
80
144
  self._session = aiohttp.ClientSession(
@@ -90,6 +154,34 @@ class HttpProvider:
90
154
  await self._session.close()
91
155
  self._session = None
92
156
 
157
+ @classmethod
158
+ async def _read_response(cls, resp: aiohttp.ClientResponse) -> t.Any:
159
+ body = await resp.read()
160
+ if not body:
161
+ return ""
162
+
163
+ data = body.decode("utf-8", errors="replace").strip()
164
+ if not data:
165
+ return ""
166
+
167
+ try:
168
+ return json.loads(data)
169
+ except (Exception,):
170
+ return data
171
+
172
+ @classmethod
173
+ def _raise_error(cls, status: int, url: str, data: t.Any) -> None:
174
+ exc = cls._detect_proxy_error(data, status=status, url=url)
175
+ if exc is not None:
176
+ raise exc
177
+
178
+ message = cls._extract_error_message(data)
179
+ raise ProviderResponseError(
180
+ code=status,
181
+ message=message,
182
+ endpoint=url,
183
+ )
184
+
93
185
  async def _send_once(
94
186
  self,
95
187
  method: str,
@@ -110,8 +202,12 @@ class HttpProvider:
110
202
  :param json_data: Optional JSON body.
111
203
  :return: Parsed response payload.
112
204
  """
113
- if not self.is_connected:
114
- raise NotConnectedError()
205
+ if not self.connected:
206
+ raise NotConnectedError(
207
+ component="HttpProvider",
208
+ endpoint=self._base_url,
209
+ operation=f"{method} {path}",
210
+ )
115
211
 
116
212
  assert self._session is not None
117
213
  url = f"{self._base_url}/{path.lstrip('/')}"
@@ -126,13 +222,9 @@ class HttpProvider:
126
222
  params=params,
127
223
  json=json_data,
128
224
  ) as resp:
129
- data = await HttpResponse.read(resp)
225
+ data = await self._read_response(resp)
130
226
  if resp.status >= 400:
131
- HttpResponse.raise_error(
132
- status=int(resp.status),
133
- url=url,
134
- data=data,
135
- )
227
+ self._raise_error(int(resp.status), url, data)
136
228
  return data
137
229
 
138
230
  except asyncio.TimeoutError as exc:
@@ -141,61 +233,48 @@ class HttpProvider:
141
233
  endpoint=url,
142
234
  operation="http request",
143
235
  ) from exc
144
-
145
236
  except aiohttp.ClientError as exc:
146
- raise ProviderResponseError(
147
- code=0,
148
- message=str(exc),
237
+ raise TransportError(
149
238
  endpoint=url,
239
+ operation="http request",
240
+ reason=str(exc),
150
241
  ) from exc
151
242
 
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] = {}
243
+ @classmethod
244
+ def _detect_proxy_error(
245
+ cls,
246
+ data: t.Any,
247
+ status: int,
248
+ url: str,
249
+ ) -> t.Optional[ProviderResponseError]:
250
+ body = (
251
+ " ".join(str(v) for v in data.values())
252
+ if isinstance(data, dict)
253
+ else str(data)
254
+ ).lower()
173
255
 
174
- while True:
175
- try:
176
- return await self._send_once(
177
- method,
178
- path,
179
- params=params,
180
- json_data=json_data,
256
+ for marker, message in CDN_CHALLENGE_MARKERS.items():
257
+ if marker in body:
258
+ return ProviderResponseError(
259
+ code=status,
260
+ message=message,
261
+ endpoint=url,
181
262
  )
182
- except ProviderResponseError as e:
183
- policy = self._retry_policy
184
- if policy is None:
185
- raise
186
263
 
187
- rule = policy.rule_for(e.code, e.message)
188
- if rule is None:
189
- raise
264
+ return None
190
265
 
191
- key = id(rule)
192
- attempts[key] = attempts.get(key, 0) + 1
266
+ @staticmethod
267
+ def _extract_error_message(data: t.Any) -> str:
268
+ if isinstance(data, dict):
269
+ lowered = {k.lower(): v for k, v in data.items()}
270
+ for key in ("error", "message", "detail", "description"):
271
+ if key in lowered and isinstance(lowered[key], str):
272
+ return lowered[key]
273
+ string_values = [str(v) for v in data.values() if isinstance(v, str)]
274
+ return "; ".join(string_values) if string_values else str(data)
193
275
 
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))
276
+ if isinstance(data, list):
277
+ return "; ".join(map(str, data))
278
+ if isinstance(data, str):
279
+ return data
280
+ return repr(data)
@@ -6,6 +6,48 @@ from tonutils.types import ContractState
6
6
  from tonutils.utils import to_cell, cell_to_b64
7
7
 
8
8
 
9
+ class BlockchainMessagePayload(BaseModel):
10
+ """Payload for /blockchain/message endpoint."""
11
+
12
+ boc: str
13
+
14
+
15
+ class BlockchainConfigResult(BaseModel):
16
+ """Result model for /blockchain/config."""
17
+
18
+ raw: t.Optional[str] = None
19
+
20
+
21
+ class BlockchainAccountResult(BaseModel):
22
+ """Result model for /blockchain/accounts/{address}."""
23
+
24
+ balance: int = 0
25
+ status: str = ContractState.NONEXIST.value
26
+ code: t.Optional[str] = None
27
+ data: t.Optional[str] = None
28
+ last_transaction_lt: t.Optional[int] = None
29
+ last_transaction_hash: t.Optional[str] = None
30
+
31
+
32
+ class _BlockchainAccountTransaction(BaseModel):
33
+ """Single account transaction with raw BoC payload."""
34
+
35
+ raw: t.Optional[str] = None
36
+
37
+
38
+ class BlockchainAccountTransactionsResult(BaseModel):
39
+ """Result model for /blockchain/accounts/{address}/transactions."""
40
+
41
+ transactions: t.Optional[t.List[_BlockchainAccountTransaction]] = None
42
+
43
+
44
+ class BlockchainAccountMethodResult(BaseModel):
45
+ """Result model for /blockchain/accounts/{address}/methods/{method_name}."""
46
+
47
+ stack: t.Optional[t.List[t.Any]] = None
48
+ exit_code: int
49
+
50
+
9
51
  class SendBocPayload(BaseModel):
10
52
  """
11
53
  Payload for /sendBoc endpoint.
@@ -77,7 +119,7 @@ class _Transaction(BaseModel):
77
119
  data: t.Optional[str] = None
78
120
 
79
121
 
80
- class GetTransactionResult(BaseModel):
122
+ class GetTransactionsResult(BaseModel):
81
123
  """Result wrapper for /getTransactions."""
82
124
 
83
125
  result: t.Optional[t.List[_Transaction]] = None
@@ -98,7 +140,7 @@ class RunGetMethodPayload(BaseModel):
98
140
  stack: t.List[t.Any]
99
141
 
100
142
 
101
- class RunGetMethodResul(BaseModel):
143
+ class RunGetMethodResult(BaseModel):
102
144
  """Response wrapper for /runGetMethod."""
103
145
 
104
146
  result: t.Optional[_GetMethod] = None
@@ -3,15 +3,14 @@ from __future__ import annotations
3
3
  import typing as t
4
4
 
5
5
  import aiohttp
6
- from pydantic import BaseModel
7
6
 
8
- from tonutils.clients.http.providers.base import HttpProvider
9
- from tonutils.clients.http.providers.tonapi.models import (
10
- BlockchainAccountMethodResult,
7
+ from tonutils.clients.http.provider.base import HttpProvider
8
+ from tonutils.clients.http.provider.models import (
9
+ BlockchainMessagePayload,
10
+ BlockchainConfigResult,
11
11
  BlockchainAccountResult,
12
12
  BlockchainAccountTransactionsResult,
13
- BlockchainConfigResult,
14
- BlockchainMessagePayload,
13
+ BlockchainAccountMethodResult,
15
14
  )
16
15
  from tonutils.types import NetworkGlobalID, RetryPolicy
17
16
 
@@ -21,6 +20,7 @@ class TonapiHttpProvider(HttpProvider):
21
20
  def __init__(
22
21
  self,
23
22
  network: NetworkGlobalID,
23
+ *,
24
24
  api_key: str,
25
25
  base_url: t.Optional[str] = None,
26
26
  timeout: float = 10.0,
@@ -49,10 +49,6 @@ class TonapiHttpProvider(HttpProvider):
49
49
  retry_policy=retry_policy,
50
50
  )
51
51
 
52
- @staticmethod
53
- def _model(model: t.Type[BaseModel], data: t.Any) -> t.Any:
54
- return model.model_validate(data)
55
-
56
52
  async def blockchain_message(
57
53
  self,
58
54
  payload: BlockchainMessagePayload,
@@ -92,12 +88,11 @@ class TonapiHttpProvider(HttpProvider):
92
88
  limit: int = 100,
93
89
  after_lt: t.Optional[int] = None,
94
90
  before_lt: t.Optional[int] = None,
95
- sort_order: str = "desc",
96
91
  ) -> BlockchainAccountTransactionsResult:
97
- params = {"limit": limit, "sort_order": sort_order}
92
+ params = {"limit": limit}
98
93
  if after_lt is not None:
99
94
  params["after_lt"] = after_lt
100
- if before_lt is not None and before_lt > 0:
95
+ if before_lt is not None:
101
96
  params["before_lt"] = before_lt
102
97
 
103
98
  return self._model(
@@ -1,16 +1,15 @@
1
1
  import typing as t
2
2
 
3
3
  import aiohttp
4
- from pydantic import BaseModel
5
4
 
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,
5
+ from tonutils.clients.http.provider.base import HttpProvider
6
+ from tonutils.clients.http.provider.models import (
12
7
  SendBocPayload,
8
+ GetConfigAllResult,
9
+ GetAddressInformationResult,
10
+ GetTransactionsResult,
13
11
  RunGetMethodPayload,
12
+ RunGetMethodResult,
14
13
  )
15
14
  from tonutils.types import NetworkGlobalID, RetryPolicy
16
15
 
@@ -20,6 +19,7 @@ class ToncenterHttpProvider(HttpProvider):
20
19
  def __init__(
21
20
  self,
22
21
  network: NetworkGlobalID,
22
+ *,
23
23
  api_key: t.Optional[str] = None,
24
24
  base_url: t.Optional[str] = None,
25
25
  timeout: float = 10.0,
@@ -36,6 +36,7 @@ class ToncenterHttpProvider(HttpProvider):
36
36
  }
37
37
  base_url = base_url or urls[network]
38
38
  headers = {**(headers or {}), **({"X-Api-Key": api_key} if api_key else {})}
39
+
39
40
  super().__init__(
40
41
  base_url=base_url,
41
42
  session=session,
@@ -47,10 +48,6 @@ class ToncenterHttpProvider(HttpProvider):
47
48
  retry_policy=retry_policy,
48
49
  )
49
50
 
50
- @staticmethod
51
- def _model(model: t.Type[BaseModel], data: t.Any) -> t.Any:
52
- return model.model_validate(data)
53
-
54
51
  async def send_boc(
55
52
  self,
56
53
  payload: SendBocPayload,
@@ -85,19 +82,26 @@ class ToncenterHttpProvider(HttpProvider):
85
82
  ),
86
83
  )
87
84
 
88
- async def get_transaction(
85
+ async def get_transactions(
89
86
  self,
90
87
  address: str,
91
88
  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
89
+ lt: t.Optional[int] = None,
90
+ from_hash: t.Optional[str] = None,
91
+ to_lt: t.Optional[int] = None,
92
+ ) -> GetTransactionsResult:
93
+ params = {"address": address, "limit": limit, "archival": "true"}
94
+
95
+ # lt and hash must be used together
96
+ if lt is not None and from_hash is not None:
97
+ params["lt"] = lt
98
+ params["hash"] = from_hash
99
+
100
+ if to_lt is not None:
101
+ params["to_lt"] = to_lt
98
102
 
99
103
  return self._model(
100
- GetTransactionResult,
104
+ GetTransactionsResult,
101
105
  await self.send_http_request(
102
106
  "GET",
103
107
  "/getTransactions",
@@ -108,9 +112,9 @@ class ToncenterHttpProvider(HttpProvider):
108
112
  async def run_get_method(
109
113
  self,
110
114
  payload: RunGetMethodPayload,
111
- ) -> RunGetMethodResul:
115
+ ) -> RunGetMethodResult:
112
116
  return self._model(
113
- RunGetMethodResul,
117
+ RunGetMethodResult,
114
118
  await self.send_http_request(
115
119
  "POST",
116
120
  "/runGetMethod",