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.
- tonutils/__meta__.py +1 -1
- tonutils/clients/__init__.py +10 -10
- tonutils/clients/adnl/balancer.py +135 -361
- tonutils/clients/adnl/client.py +35 -208
- tonutils/clients/adnl/mixin.py +268 -0
- tonutils/clients/adnl/provider/config.py +22 -7
- tonutils/clients/adnl/provider/provider.py +61 -16
- tonutils/clients/adnl/provider/transport.py +13 -4
- tonutils/clients/adnl/provider/workers/pinger.py +1 -1
- tonutils/clients/adnl/utils.py +5 -5
- tonutils/clients/base.py +61 -95
- tonutils/clients/http/__init__.py +11 -11
- tonutils/clients/http/balancer.py +103 -100
- tonutils/clients/http/clients/__init__.py +10 -10
- tonutils/clients/http/clients/chainstack.py +3 -3
- tonutils/clients/http/clients/quicknode.py +2 -3
- tonutils/clients/http/clients/tatum.py +4 -3
- tonutils/clients/http/clients/tonapi.py +20 -33
- tonutils/clients/http/clients/toncenter.py +64 -55
- tonutils/clients/http/{providers → provider}/__init__.py +4 -1
- tonutils/clients/http/{providers → provider}/base.py +140 -61
- tonutils/clients/http/{providers/toncenter → provider}/models.py +44 -2
- tonutils/clients/http/{providers/tonapi/provider.py → provider/tonapi.py} +8 -13
- tonutils/clients/http/{providers/toncenter/provider.py → provider/toncenter.py} +25 -21
- tonutils/clients/limiter.py +61 -59
- tonutils/clients/protocol.py +8 -8
- tonutils/contracts/base.py +32 -32
- tonutils/contracts/protocol.py +9 -9
- tonutils/contracts/wallet/base.py +7 -8
- tonutils/contracts/wallet/messages.py +4 -8
- tonutils/contracts/wallet/versions/v5.py +2 -2
- tonutils/exceptions.py +29 -13
- tonutils/tonconnect/bridge/__init__.py +0 -0
- tonutils/tonconnect/events.py +0 -0
- tonutils/tonconnect/models/__init__.py +0 -0
- tonutils/tonconnect/storage.py +0 -0
- tonutils/tonconnect/tonconnect.py +0 -0
- tonutils/tools/block_scanner/__init__.py +2 -5
- tonutils/tools/block_scanner/events.py +48 -7
- tonutils/tools/block_scanner/scanner.py +316 -222
- tonutils/tools/block_scanner/storage.py +11 -0
- tonutils/tools/status_monitor/monitor.py +6 -6
- tonutils/types.py +2 -2
- tonutils/utils.py +0 -48
- {tonutils-2.0.1b5.dist-info → tonutils-2.0.1b7.dist-info}/METADATA +3 -18
- {tonutils-2.0.1b5.dist-info → tonutils-2.0.1b7.dist-info}/RECORD +50 -51
- {tonutils-2.0.1b5.dist-info → tonutils-2.0.1b7.dist-info}/WHEEL +1 -1
- tonutils/clients/http/providers/response.py +0 -85
- tonutils/clients/http/providers/tonapi/__init__.py +0 -3
- tonutils/clients/http/providers/tonapi/models.py +0 -47
- tonutils/clients/http/providers/toncenter/__init__.py +0 -3
- tonutils/tools/block_scanner/annotations.py +0 -23
- tonutils/tools/block_scanner/dispatcher.py +0 -141
- tonutils/tools/block_scanner/traversal.py +0 -97
- tonutils/tools/block_scanner/where.py +0 -53
- {tonutils-2.0.1b5.dist-info → tonutils-2.0.1b7.dist-info}/entry_points.txt +0 -0
- {tonutils-2.0.1b5.dist-info → tonutils-2.0.1b7.dist-info}/licenses/LICENSE +0 -0
- {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.
|
|
11
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
107
|
+
async def _get_info(self, address: str) -> ContractInfo:
|
|
123
108
|
request = await self.provider.get_address_information(address)
|
|
124
109
|
|
|
125
|
-
contract_info =
|
|
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
|
|
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 =
|
|
159
|
+
to_lt: t.Optional[int] = None,
|
|
175
160
|
) -> t.List[Transaction]:
|
|
176
|
-
if
|
|
177
|
-
|
|
161
|
+
to_lt = 0 if to_lt is None else to_lt
|
|
162
|
+
transactions: t.List[Transaction] = []
|
|
178
163
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
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,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
|
|
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.
|
|
137
|
+
if self.connected:
|
|
74
138
|
return
|
|
75
139
|
|
|
76
140
|
async with self._connect_lock:
|
|
77
|
-
if self.
|
|
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.
|
|
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
|
|
225
|
+
data = await self._read_response(resp)
|
|
130
226
|
if resp.status >= 400:
|
|
131
|
-
|
|
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
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
return
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
|
|
188
|
-
if rule is None:
|
|
189
|
-
raise
|
|
264
|
+
return None
|
|
190
265
|
|
|
191
|
-
|
|
192
|
-
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
|
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
|
|
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.
|
|
9
|
-
from tonutils.clients.http.
|
|
10
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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.
|
|
7
|
-
from tonutils.clients.http.
|
|
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
|
|
85
|
+
async def get_transactions(
|
|
89
86
|
self,
|
|
90
87
|
address: str,
|
|
91
88
|
limit: int = 100,
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
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
|
-
) ->
|
|
115
|
+
) -> RunGetMethodResult:
|
|
112
116
|
return self._model(
|
|
113
|
-
|
|
117
|
+
RunGetMethodResult,
|
|
114
118
|
await self.send_http_request(
|
|
115
119
|
"POST",
|
|
116
120
|
"/runGetMethod",
|