tonutils 2.0.1b2__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.
- tonutils/__init__.py +0 -2
- tonutils/__meta__.py +1 -1
- tonutils/clients/__init__.py +5 -9
- tonutils/clients/adnl/__init__.py +5 -1
- tonutils/clients/adnl/balancer.py +319 -125
- tonutils/clients/adnl/client.py +187 -51
- tonutils/clients/adnl/provider/config.py +19 -25
- tonutils/clients/adnl/provider/models.py +4 -0
- tonutils/clients/adnl/provider/provider.py +191 -145
- tonutils/clients/adnl/provider/transport.py +38 -32
- tonutils/clients/adnl/provider/workers/base.py +0 -2
- tonutils/clients/adnl/provider/workers/pinger.py +1 -1
- tonutils/clients/adnl/provider/workers/reader.py +3 -2
- tonutils/clients/adnl/{provider/builder.py → utils.py} +62 -2
- tonutils/clients/http/__init__.py +11 -8
- tonutils/clients/http/balancer.py +75 -63
- tonutils/clients/http/clients/__init__.py +13 -0
- tonutils/clients/http/clients/chainstack.py +48 -0
- tonutils/clients/http/clients/quicknode.py +47 -0
- tonutils/clients/http/clients/tatum.py +56 -0
- tonutils/clients/http/{tonapi/client.py → clients/tonapi.py} +31 -31
- tonutils/clients/http/{toncenter/client.py → clients/toncenter.py} +59 -48
- tonutils/clients/http/providers/__init__.py +4 -0
- tonutils/clients/http/providers/base.py +201 -0
- tonutils/clients/http/providers/response.py +85 -0
- tonutils/clients/http/providers/tonapi/__init__.py +3 -0
- tonutils/clients/http/{tonapi → providers/tonapi}/models.py +1 -0
- tonutils/clients/http/providers/tonapi/provider.py +125 -0
- tonutils/clients/http/providers/toncenter/__init__.py +3 -0
- tonutils/clients/http/{toncenter → providers/toncenter}/models.py +1 -0
- tonutils/clients/http/providers/toncenter/provider.py +119 -0
- tonutils/clients/http/utils.py +140 -0
- tonutils/clients/limiter.py +115 -0
- tonutils/contracts/__init__.py +4 -0
- tonutils/contracts/base.py +33 -20
- tonutils/contracts/dns/methods.py +2 -2
- tonutils/contracts/jetton/methods.py +2 -2
- tonutils/contracts/nft/methods.py +2 -2
- tonutils/contracts/nft/tlb.py +1 -1
- tonutils/{protocols/contract.py → contracts/protocol.py} +29 -29
- tonutils/contracts/telegram/methods.py +2 -2
- tonutils/contracts/vanity/vanity.py +1 -1
- tonutils/contracts/wallet/__init__.py +2 -0
- tonutils/contracts/wallet/base.py +3 -3
- tonutils/contracts/wallet/messages.py +1 -1
- tonutils/contracts/wallet/methods.py +2 -2
- tonutils/{protocols/wallet.py → contracts/wallet/protocol.py} +35 -35
- tonutils/contracts/wallet/versions/v5.py +3 -3
- tonutils/exceptions.py +134 -226
- tonutils/types.py +115 -0
- tonutils/utils.py +3 -3
- {tonutils-2.0.1b2.dist-info → tonutils-2.0.1b3.dist-info}/METADATA +2 -2
- tonutils-2.0.1b3.dist-info/RECORD +93 -0
- tonutils/clients/adnl/provider/limiter.py +0 -56
- tonutils/clients/adnl/stack.py +0 -64
- tonutils/clients/http/chainstack/__init__.py +0 -4
- tonutils/clients/http/chainstack/client.py +0 -63
- tonutils/clients/http/chainstack/provider.py +0 -44
- tonutils/clients/http/quicknode/__init__.py +0 -4
- tonutils/clients/http/quicknode/client.py +0 -60
- tonutils/clients/http/quicknode/provider.py +0 -42
- tonutils/clients/http/tatum/__init__.py +0 -4
- tonutils/clients/http/tatum/client.py +0 -66
- tonutils/clients/http/tatum/provider.py +0 -53
- tonutils/clients/http/tonapi/__init__.py +0 -4
- tonutils/clients/http/tonapi/provider.py +0 -150
- tonutils/clients/http/tonapi/stack.py +0 -71
- tonutils/clients/http/toncenter/__init__.py +0 -4
- tonutils/clients/http/toncenter/provider.py +0 -145
- tonutils/clients/http/toncenter/stack.py +0 -73
- tonutils/protocols/__init__.py +0 -9
- tonutils-2.0.1b2.dist-info/RECORD +0 -98
- /tonutils/{protocols/client.py → clients/protocol.py} +0 -0
- {tonutils-2.0.1b2.dist-info → tonutils-2.0.1b3.dist-info}/WHEEL +0 -0
- {tonutils-2.0.1b2.dist-info → tonutils-2.0.1b3.dist-info}/licenses/LICENSE +0 -0
- {tonutils-2.0.1b2.dist-info → tonutils-2.0.1b3.dist-info}/top_level.txt +0 -0
|
@@ -11,10 +11,12 @@ from pytoniq_core import (
|
|
|
11
11
|
SimpleAccount,
|
|
12
12
|
begin_cell,
|
|
13
13
|
check_account_proof,
|
|
14
|
+
Slice,
|
|
15
|
+
VmTuple,
|
|
14
16
|
)
|
|
15
17
|
|
|
16
|
-
from tonutils.types import ContractState, ContractStateInfo
|
|
17
|
-
from tonutils.utils import cell_to_hex
|
|
18
|
+
from tonutils.types import ContractState, ContractStateInfo, StackItems, StackItem
|
|
19
|
+
from tonutils.utils import cell_to_hex, norm_stack_num, norm_stack_cell
|
|
18
20
|
|
|
19
21
|
|
|
20
22
|
def build_config_all(config_proof: Cell) -> t.Dict[int, t.Any]:
|
|
@@ -109,3 +111,61 @@ def build_contract_state_info(
|
|
|
109
111
|
info.state = ContractState.NONEXIST
|
|
110
112
|
|
|
111
113
|
return info
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def decode_stack(items: t.List[t.Any]) -> StackItems:
|
|
117
|
+
"""
|
|
118
|
+
Decode VM stack items into internal Python structures.
|
|
119
|
+
|
|
120
|
+
Supports:
|
|
121
|
+
- int → int
|
|
122
|
+
- Cell/Slice → normalized cell
|
|
123
|
+
- Address → address cell
|
|
124
|
+
- VmTuple/list → recursive decode
|
|
125
|
+
- None → None
|
|
126
|
+
|
|
127
|
+
:param items: Raw VM stack items
|
|
128
|
+
:return: Normalized Python stack values
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
out: StackItems = []
|
|
132
|
+
for item in items:
|
|
133
|
+
if item is None:
|
|
134
|
+
out.append(None)
|
|
135
|
+
elif isinstance(item, int):
|
|
136
|
+
out.append(norm_stack_num(item))
|
|
137
|
+
elif isinstance(item, Address):
|
|
138
|
+
out.append(item.to_cell())
|
|
139
|
+
elif isinstance(item, (Cell, Slice)):
|
|
140
|
+
out.append(norm_stack_cell(item))
|
|
141
|
+
elif isinstance(item, VmTuple):
|
|
142
|
+
out.append(decode_stack(item.list))
|
|
143
|
+
elif isinstance(item, list):
|
|
144
|
+
out.append(decode_stack(item))
|
|
145
|
+
return out
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def encode_stack(items: t.List[StackItem]) -> t.List[t.Any]:
|
|
149
|
+
"""
|
|
150
|
+
Encode Python stack values into VM-compatible format.
|
|
151
|
+
|
|
152
|
+
Supports:
|
|
153
|
+
- int → int
|
|
154
|
+
- Cell/Slice → cell/slice
|
|
155
|
+
- Address → cell slice
|
|
156
|
+
- list/tuple → recursive encode
|
|
157
|
+
|
|
158
|
+
:param items: Normalized Python stack items
|
|
159
|
+
:return: VM-encoded stack values
|
|
160
|
+
"""
|
|
161
|
+
out: t.List[t.Any] = []
|
|
162
|
+
for item in items:
|
|
163
|
+
if isinstance(item, int):
|
|
164
|
+
out.append(item)
|
|
165
|
+
elif isinstance(item, Address):
|
|
166
|
+
out.append(item.to_cell().to_slice())
|
|
167
|
+
elif isinstance(item, (Cell, Slice)):
|
|
168
|
+
out.append(item)
|
|
169
|
+
elif isinstance(item, (list, tuple)):
|
|
170
|
+
out.append(encode_stack(list(item)))
|
|
171
|
+
return out
|
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
from .balancer import HttpBalancer
|
|
2
|
-
from .
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
from .clients import (
|
|
3
|
+
ChainstackHttpClient,
|
|
4
|
+
QuicknodeHttpClient,
|
|
5
|
+
TatumHttpClient,
|
|
6
|
+
TonapiHttpClient,
|
|
7
|
+
ToncenterHttpClient,
|
|
8
|
+
)
|
|
9
|
+
from .providers import (
|
|
10
|
+
TonapiHttpProvider,
|
|
11
|
+
ToncenterHttpProvider,
|
|
12
|
+
)
|
|
7
13
|
|
|
8
14
|
__all__ = [
|
|
9
15
|
"HttpBalancer",
|
|
10
16
|
"ChainstackHttpClient",
|
|
11
|
-
"ChainstackHttpProvider",
|
|
12
17
|
"QuicknodeHttpClient",
|
|
13
|
-
"QuicknodeHttpProvider",
|
|
14
18
|
"TatumHttpClient",
|
|
15
|
-
"TatumHttpProvider",
|
|
16
19
|
"TonapiHttpClient",
|
|
17
20
|
"TonapiHttpProvider",
|
|
18
21
|
"ToncenterHttpClient",
|
|
@@ -7,18 +7,19 @@ from contextlib import suppress
|
|
|
7
7
|
from dataclasses import dataclass
|
|
8
8
|
from itertools import cycle
|
|
9
9
|
|
|
10
|
-
from pyapiq.exceptions import (
|
|
11
|
-
APIQException,
|
|
12
|
-
APIClientResponseError,
|
|
13
|
-
APIClientServerError,
|
|
14
|
-
APIClientTooManyRequestsError,
|
|
15
|
-
RateLimitExceeded,
|
|
16
|
-
)
|
|
17
10
|
from pytoniq_core import Transaction
|
|
18
11
|
|
|
19
12
|
from tonutils.clients.base import BaseClient
|
|
20
|
-
from tonutils.clients.http.quicknode import QuicknodeHttpClient
|
|
21
|
-
from tonutils.exceptions import
|
|
13
|
+
from tonutils.clients.http.clients.quicknode import QuicknodeHttpClient
|
|
14
|
+
from tonutils.exceptions import (
|
|
15
|
+
BalancerError,
|
|
16
|
+
ClientError,
|
|
17
|
+
TransportError,
|
|
18
|
+
ProviderError,
|
|
19
|
+
ProviderResponseError,
|
|
20
|
+
RunGetMethodError,
|
|
21
|
+
ProviderTimeoutError,
|
|
22
|
+
)
|
|
22
23
|
from tonutils.types import ClientType, ContractStateInfo, NetworkGlobalID
|
|
23
24
|
|
|
24
25
|
_T = t.TypeVar("_T")
|
|
@@ -52,6 +53,7 @@ class HttpBalancer(BaseClient):
|
|
|
52
53
|
*,
|
|
53
54
|
network: NetworkGlobalID = NetworkGlobalID.MAINNET,
|
|
54
55
|
clients: t.List[BaseClient],
|
|
56
|
+
request_timeout: float = 12.0,
|
|
55
57
|
) -> None:
|
|
56
58
|
"""
|
|
57
59
|
Initialize HTTP balancer.
|
|
@@ -71,6 +73,8 @@ class HttpBalancer(BaseClient):
|
|
|
71
73
|
|
|
72
74
|
:param network: Target TON network (mainnet or testnet)
|
|
73
75
|
:param clients: List of HTTP BaseClient instances to balance between
|
|
76
|
+
:param request_timeout: Maximum total time in seconds for a balancer operation,
|
|
77
|
+
including all failover attempts across providers
|
|
74
78
|
"""
|
|
75
79
|
self.network = network
|
|
76
80
|
|
|
@@ -83,6 +87,8 @@ class HttpBalancer(BaseClient):
|
|
|
83
87
|
self._retry_after_base = 1.0
|
|
84
88
|
self._retry_after_max = 10.0
|
|
85
89
|
|
|
90
|
+
self._request_timeout = request_timeout
|
|
91
|
+
|
|
86
92
|
def __init_clients(
|
|
87
93
|
self,
|
|
88
94
|
clients: t.List[BaseClient],
|
|
@@ -109,6 +115,25 @@ class HttpBalancer(BaseClient):
|
|
|
109
115
|
self._clients.append(client)
|
|
110
116
|
self._states.append(state)
|
|
111
117
|
|
|
118
|
+
@property
|
|
119
|
+
def provider(self) -> t.Any:
|
|
120
|
+
"""
|
|
121
|
+
Provider of the currently selected client.
|
|
122
|
+
|
|
123
|
+
:return: Provider instance of chosen HTTP client
|
|
124
|
+
"""
|
|
125
|
+
c = self._pick_client()
|
|
126
|
+
return c.provider
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def is_connected(self) -> bool:
|
|
130
|
+
"""
|
|
131
|
+
Check whether at least one underlying client is connected.
|
|
132
|
+
|
|
133
|
+
:return: True if any client is connected, otherwise False
|
|
134
|
+
"""
|
|
135
|
+
return any(c.is_connected for c in self._clients)
|
|
136
|
+
|
|
112
137
|
@property
|
|
113
138
|
def clients(self) -> t.Tuple[BaseClient, ...]:
|
|
114
139
|
"""
|
|
@@ -146,27 +171,6 @@ class HttpBalancer(BaseClient):
|
|
|
146
171
|
if state.retry_after is not None and state.retry_after > now
|
|
147
172
|
)
|
|
148
173
|
|
|
149
|
-
@property
|
|
150
|
-
def provider(self) -> t.Any:
|
|
151
|
-
"""
|
|
152
|
-
Provider of the currently selected client.
|
|
153
|
-
|
|
154
|
-
:return: Provider instance of chosen HTTP client
|
|
155
|
-
"""
|
|
156
|
-
if not self.is_connected:
|
|
157
|
-
raise ClientNotConnectedError(self)
|
|
158
|
-
client = self._pick_client()
|
|
159
|
-
return client.provider
|
|
160
|
-
|
|
161
|
-
@property
|
|
162
|
-
def is_connected(self) -> bool:
|
|
163
|
-
"""
|
|
164
|
-
Check whether at least one underlying client is connected.
|
|
165
|
-
|
|
166
|
-
:return: True if any client is connected, otherwise False
|
|
167
|
-
"""
|
|
168
|
-
return any(c.is_connected for c in self._clients)
|
|
169
|
-
|
|
170
174
|
async def __aenter__(self) -> HttpBalancer:
|
|
171
175
|
"""
|
|
172
176
|
Enter async context manager and connect all underlying clients.
|
|
@@ -219,7 +223,7 @@ class HttpBalancer(BaseClient):
|
|
|
219
223
|
height_candidates.append((wait, state.error_count, state))
|
|
220
224
|
|
|
221
225
|
if not height_candidates:
|
|
222
|
-
raise
|
|
226
|
+
raise BalancerError("no available HTTP clients")
|
|
223
227
|
|
|
224
228
|
height_candidates.sort(key=lambda x: (x[0], x[1]))
|
|
225
229
|
best_wait, best_err, _ = height_candidates[0]
|
|
@@ -292,39 +296,47 @@ class HttpBalancer(BaseClient):
|
|
|
292
296
|
:param func: Callable performing an operation using a client
|
|
293
297
|
:return: Result of the successful invocation
|
|
294
298
|
"""
|
|
295
|
-
last_exc: t.Optional[BaseException] = None
|
|
296
|
-
|
|
297
|
-
for _ in range(len(self._clients)):
|
|
298
|
-
if not self.alive_clients:
|
|
299
|
-
break
|
|
300
|
-
|
|
301
|
-
client = self._pick_client()
|
|
302
|
-
|
|
303
|
-
try:
|
|
304
|
-
result = await func(client)
|
|
305
|
-
except (APIClientTooManyRequestsError, RateLimitExceeded) as e:
|
|
306
|
-
self._mark_error(client, is_rate_limit=True)
|
|
307
|
-
last_exc = e
|
|
308
|
-
continue
|
|
309
|
-
except APIClientResponseError as e:
|
|
310
|
-
last_exc = e
|
|
311
|
-
break
|
|
312
|
-
except (APIClientServerError, APIQException) as e:
|
|
313
|
-
self._mark_error(client, is_rate_limit=False)
|
|
314
|
-
last_exc = e
|
|
315
|
-
continue
|
|
316
|
-
except Exception as e:
|
|
317
|
-
self._mark_error(client, is_rate_limit=False)
|
|
318
|
-
last_exc = e
|
|
319
|
-
continue
|
|
320
|
-
|
|
321
|
-
self._mark_success(client)
|
|
322
|
-
return result
|
|
323
|
-
|
|
324
|
-
if last_exc is not None:
|
|
325
|
-
raise last_exc
|
|
326
299
|
|
|
327
|
-
|
|
300
|
+
async def _run() -> _T:
|
|
301
|
+
last_exc: t.Optional[BaseException] = None
|
|
302
|
+
|
|
303
|
+
for _ in range(len(self._clients)):
|
|
304
|
+
if not self.alive_clients:
|
|
305
|
+
break
|
|
306
|
+
|
|
307
|
+
client = self._pick_client()
|
|
308
|
+
|
|
309
|
+
try:
|
|
310
|
+
result = await func(client)
|
|
311
|
+
|
|
312
|
+
except RunGetMethodError:
|
|
313
|
+
raise
|
|
314
|
+
except ProviderResponseError as e:
|
|
315
|
+
is_rate_limit = e.code == 429
|
|
316
|
+
self._mark_error(client, is_rate_limit=is_rate_limit)
|
|
317
|
+
last_exc = e
|
|
318
|
+
continue
|
|
319
|
+
except (TransportError, ProviderError) as e:
|
|
320
|
+
self._mark_error(client, is_rate_limit=False)
|
|
321
|
+
last_exc = e
|
|
322
|
+
continue
|
|
323
|
+
|
|
324
|
+
self._mark_success(client)
|
|
325
|
+
return result
|
|
326
|
+
|
|
327
|
+
if last_exc is not None:
|
|
328
|
+
raise last_exc
|
|
329
|
+
|
|
330
|
+
raise ClientError("all HTTP clients failed to process request.")
|
|
331
|
+
|
|
332
|
+
try:
|
|
333
|
+
return await asyncio.wait_for(_run(), timeout=self._request_timeout)
|
|
334
|
+
except asyncio.TimeoutError as exc:
|
|
335
|
+
raise ProviderTimeoutError(
|
|
336
|
+
timeout=self._request_timeout,
|
|
337
|
+
endpoint="http balancer",
|
|
338
|
+
operation="failover request",
|
|
339
|
+
) from exc
|
|
328
340
|
|
|
329
341
|
async def _send_boc(self, boc: str) -> None:
|
|
330
342
|
async def _call(client: BaseClient) -> None:
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from .chainstack import ChainstackHttpClient
|
|
2
|
+
from .quicknode import QuicknodeHttpClient
|
|
3
|
+
from .tatum import TatumHttpClient
|
|
4
|
+
from .tonapi import TonapiHttpClient
|
|
5
|
+
from .toncenter import ToncenterHttpClient
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"ChainstackHttpClient",
|
|
9
|
+
"QuicknodeHttpClient",
|
|
10
|
+
"TatumHttpClient",
|
|
11
|
+
"TonapiHttpClient",
|
|
12
|
+
"ToncenterHttpClient",
|
|
13
|
+
]
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import typing as t
|
|
2
|
+
|
|
3
|
+
from aiohttp import ClientSession
|
|
4
|
+
|
|
5
|
+
from tonutils.clients.http.clients.toncenter import ToncenterHttpClient
|
|
6
|
+
from tonutils.types import NetworkGlobalID, RetryPolicy
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ChainstackHttpClient(ToncenterHttpClient):
|
|
10
|
+
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
*,
|
|
14
|
+
network: NetworkGlobalID,
|
|
15
|
+
http_provider_url: str,
|
|
16
|
+
timeout: float = 10.0,
|
|
17
|
+
session: t.Optional[ClientSession] = None,
|
|
18
|
+
headers: t.Optional[t.Dict[str, str]] = None,
|
|
19
|
+
cookies: t.Optional[t.Dict[str, str]] = None,
|
|
20
|
+
rps_limit: t.Optional[int] = None,
|
|
21
|
+
rps_period: float = 1.0,
|
|
22
|
+
retry_policy: t.Optional[RetryPolicy] = None,
|
|
23
|
+
) -> None:
|
|
24
|
+
"""
|
|
25
|
+
Initialize Chainstack HTTP client.
|
|
26
|
+
|
|
27
|
+
:param network: Target TON network (mainnet or testnet)
|
|
28
|
+
:param http_provider_url: Chainstack TON HTTP endpoint URL
|
|
29
|
+
You can obtain a personal endpoint on the Chainstack website: https://chainstack.com/
|
|
30
|
+
:param timeout: Total request timeout in seconds.
|
|
31
|
+
:param session: Optional external aiohttp session.
|
|
32
|
+
:param headers: Default headers for owned session.
|
|
33
|
+
:param cookies: Default cookies for owned session.
|
|
34
|
+
:param rps_limit: Optional requests-per-period limit.
|
|
35
|
+
:param rps_period: Rate limit period in seconds.
|
|
36
|
+
:param retry_policy: Optional retry policy that defines per-error-code retry rules
|
|
37
|
+
"""
|
|
38
|
+
super().__init__(
|
|
39
|
+
network=network,
|
|
40
|
+
base_url=http_provider_url,
|
|
41
|
+
timeout=timeout,
|
|
42
|
+
session=session,
|
|
43
|
+
headers=headers,
|
|
44
|
+
cookies=cookies,
|
|
45
|
+
rps_limit=rps_limit,
|
|
46
|
+
rps_period=rps_period,
|
|
47
|
+
retry_policy=retry_policy,
|
|
48
|
+
)
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import typing as t
|
|
2
|
+
|
|
3
|
+
from aiohttp import ClientSession
|
|
4
|
+
|
|
5
|
+
from tonutils.clients.http.clients.toncenter import ToncenterHttpClient
|
|
6
|
+
from tonutils.types import NetworkGlobalID, RetryPolicy
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class QuicknodeHttpClient(ToncenterHttpClient):
|
|
10
|
+
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
*,
|
|
14
|
+
http_provider_url: str,
|
|
15
|
+
timeout: float = 10.0,
|
|
16
|
+
session: t.Optional[ClientSession] = None,
|
|
17
|
+
headers: t.Optional[t.Dict[str, str]] = None,
|
|
18
|
+
cookies: t.Optional[t.Dict[str, str]] = None,
|
|
19
|
+
rps_limit: t.Optional[int] = None,
|
|
20
|
+
rps_period: float = 1.0,
|
|
21
|
+
retry_policy: t.Optional[RetryPolicy] = None,
|
|
22
|
+
) -> None:
|
|
23
|
+
"""
|
|
24
|
+
Initialize QuickNode HTTP client.
|
|
25
|
+
|
|
26
|
+
:param http_provider_url: QuickNode TON HTTP endpoint URL.
|
|
27
|
+
You can obtain a personal endpoint on the QuickNode website: https://www.quicknode.com/
|
|
28
|
+
:param timeout: Total request timeout in seconds.
|
|
29
|
+
:param session: Optional external aiohttp session.
|
|
30
|
+
:param headers: Default headers for owned session.
|
|
31
|
+
:param cookies: Default cookies for owned session.
|
|
32
|
+
:param rps_limit: Optional requests-per-period limit.
|
|
33
|
+
:param rps_period: Rate limit period in seconds.
|
|
34
|
+
:param retry_policy: Optional retry policy that defines per-error-code retry rules
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
super().__init__(
|
|
38
|
+
network=NetworkGlobalID.MAINNET,
|
|
39
|
+
base_url=http_provider_url,
|
|
40
|
+
timeout=timeout,
|
|
41
|
+
session=session,
|
|
42
|
+
headers=headers,
|
|
43
|
+
cookies=cookies,
|
|
44
|
+
rps_limit=rps_limit,
|
|
45
|
+
rps_period=rps_period,
|
|
46
|
+
retry_policy=retry_policy,
|
|
47
|
+
)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import typing as t
|
|
2
|
+
|
|
3
|
+
from aiohttp import ClientSession
|
|
4
|
+
|
|
5
|
+
from tonutils.clients.http.clients.toncenter import ToncenterHttpClient
|
|
6
|
+
from tonutils.types import NetworkGlobalID, RetryPolicy
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TatumHttpClient(ToncenterHttpClient):
|
|
10
|
+
|
|
11
|
+
def __init__(
|
|
12
|
+
self,
|
|
13
|
+
*,
|
|
14
|
+
network: NetworkGlobalID,
|
|
15
|
+
api_key: str,
|
|
16
|
+
base_url: t.Optional[str] = None,
|
|
17
|
+
timeout: float = 10.0,
|
|
18
|
+
session: t.Optional[ClientSession] = None,
|
|
19
|
+
headers: t.Optional[t.Dict[str, str]] = None,
|
|
20
|
+
cookies: t.Optional[t.Dict[str, str]] = None,
|
|
21
|
+
rps_limit: t.Optional[int] = None,
|
|
22
|
+
rps_period: float = 1.0,
|
|
23
|
+
retry_policy: t.Optional[RetryPolicy] = None,
|
|
24
|
+
) -> None:
|
|
25
|
+
"""
|
|
26
|
+
Initialize Tatum HTTP client.
|
|
27
|
+
|
|
28
|
+
:param network: Target TON network (mainnet or testnet)
|
|
29
|
+
:param api_key: Tatum API key
|
|
30
|
+
You can get an API key on the Tatum website: https://tatum.io/
|
|
31
|
+
:param base_url: Optional custom Tatum base URL
|
|
32
|
+
:param timeout: Total request timeout in seconds.
|
|
33
|
+
:param session: Optional external aiohttp session.
|
|
34
|
+
:param headers: Default headers for owned session.
|
|
35
|
+
:param cookies: Default cookies for owned session.
|
|
36
|
+
:param rps_limit: Optional requests-per-period limit.
|
|
37
|
+
:param rps_period: Rate limit period in seconds.
|
|
38
|
+
:param retry_policy: Optional retry policy that defines per-error-code retry rules
|
|
39
|
+
"""
|
|
40
|
+
urls = {
|
|
41
|
+
NetworkGlobalID.MAINNET: "https://ton-mainnet.gateway.tatum.io",
|
|
42
|
+
NetworkGlobalID.TESTNET: "https://ton-testnet.gateway.tatum.io",
|
|
43
|
+
}
|
|
44
|
+
base_url = base_url or urls.get(network)
|
|
45
|
+
super().__init__(
|
|
46
|
+
network=network,
|
|
47
|
+
api_key=api_key,
|
|
48
|
+
base_url=base_url,
|
|
49
|
+
timeout=timeout,
|
|
50
|
+
session=session,
|
|
51
|
+
headers=headers,
|
|
52
|
+
cookies=cookies,
|
|
53
|
+
rps_limit=rps_limit,
|
|
54
|
+
rps_period=rps_period,
|
|
55
|
+
retry_policy=retry_policy,
|
|
56
|
+
)
|
|
@@ -6,15 +6,16 @@ from aiohttp import ClientSession
|
|
|
6
6
|
from pytoniq_core import Cell, Slice, Transaction
|
|
7
7
|
|
|
8
8
|
from tonutils.clients.base import BaseClient
|
|
9
|
-
from tonutils.clients.http.tonapi.models import BlockchainMessagePayload
|
|
10
|
-
from tonutils.clients.http.tonapi.provider import TonapiHttpProvider
|
|
11
|
-
from tonutils.clients.http.
|
|
12
|
-
from tonutils.exceptions import ClientError,
|
|
9
|
+
from tonutils.clients.http.providers.tonapi.models import BlockchainMessagePayload
|
|
10
|
+
from tonutils.clients.http.providers.tonapi.provider import TonapiHttpProvider
|
|
11
|
+
from tonutils.clients.http.utils import encode_tonapi_stack, decode_tonapi_stack
|
|
12
|
+
from tonutils.exceptions import ClientError, RunGetMethodError
|
|
13
13
|
from tonutils.types import (
|
|
14
14
|
ClientType,
|
|
15
15
|
ContractState,
|
|
16
16
|
ContractStateInfo,
|
|
17
17
|
NetworkGlobalID,
|
|
18
|
+
RetryPolicy,
|
|
18
19
|
)
|
|
19
20
|
from tonutils.utils import (
|
|
20
21
|
cell_to_hex,
|
|
@@ -33,11 +34,13 @@ class TonapiHttpClient(BaseClient):
|
|
|
33
34
|
network: NetworkGlobalID = NetworkGlobalID.MAINNET,
|
|
34
35
|
api_key: str,
|
|
35
36
|
base_url: t.Optional[str] = None,
|
|
36
|
-
timeout:
|
|
37
|
+
timeout: float = 10.0,
|
|
37
38
|
session: t.Optional[ClientSession] = None,
|
|
39
|
+
headers: t.Optional[t.Dict[str, str]] = None,
|
|
40
|
+
cookies: t.Optional[t.Dict[str, str]] = None,
|
|
38
41
|
rps_limit: t.Optional[int] = None,
|
|
39
42
|
rps_period: float = 1.0,
|
|
40
|
-
|
|
43
|
+
retry_policy: t.Optional[RetryPolicy] = None,
|
|
41
44
|
) -> None:
|
|
42
45
|
"""
|
|
43
46
|
Initialize Tonapi HTTP client.
|
|
@@ -46,11 +49,13 @@ class TonapiHttpClient(BaseClient):
|
|
|
46
49
|
:param api_key: Tonapi API key
|
|
47
50
|
You can get an API key on the Tonconsole website: https://tonconsole.com/
|
|
48
51
|
:param base_url: Optional custom Tonapi base URL
|
|
49
|
-
:param timeout:
|
|
50
|
-
:param session: Optional
|
|
51
|
-
:param
|
|
52
|
-
:param
|
|
53
|
-
:param
|
|
52
|
+
:param timeout: Total request timeout in seconds.
|
|
53
|
+
:param session: Optional external aiohttp session.
|
|
54
|
+
:param headers: Default headers for owned session.
|
|
55
|
+
:param cookies: Default cookies for owned session.
|
|
56
|
+
:param rps_limit: Optional requests-per-period limit.
|
|
57
|
+
:param rps_period: Rate limit period in seconds.
|
|
58
|
+
:param retry_policy: Optional retry policy that defines per-error-code retry rules
|
|
54
59
|
"""
|
|
55
60
|
self.network: NetworkGlobalID = network
|
|
56
61
|
self._provider: TonapiHttpProvider = TonapiHttpProvider(
|
|
@@ -59,34 +64,24 @@ class TonapiHttpClient(BaseClient):
|
|
|
59
64
|
base_url=base_url,
|
|
60
65
|
timeout=timeout,
|
|
61
66
|
session=session,
|
|
67
|
+
headers=headers,
|
|
68
|
+
cookies=cookies,
|
|
62
69
|
rps_limit=rps_limit,
|
|
63
70
|
rps_period=rps_period,
|
|
64
|
-
|
|
71
|
+
retry_policy=retry_policy,
|
|
65
72
|
)
|
|
66
73
|
|
|
67
74
|
@property
|
|
68
75
|
def provider(self) -> TonapiHttpProvider:
|
|
69
|
-
"""
|
|
70
|
-
Underlying Tonapi HTTP provider.
|
|
71
|
-
|
|
72
|
-
:return: TonapiHttpProvider instance used for HTTP requests
|
|
73
|
-
"""
|
|
74
|
-
if not self.is_connected:
|
|
75
|
-
raise ClientNotConnectedError(self)
|
|
76
76
|
return self._provider
|
|
77
77
|
|
|
78
78
|
@property
|
|
79
79
|
def is_connected(self) -> bool:
|
|
80
|
-
"""
|
|
81
|
-
Check whether HTTP session is initialized and open.
|
|
82
|
-
|
|
83
|
-
:return: True if session exists and is not closed, False otherwise
|
|
84
|
-
"""
|
|
85
80
|
session = self._provider.session
|
|
86
81
|
return session is not None and not session.closed
|
|
87
82
|
|
|
88
83
|
async def __aenter__(self) -> TonapiHttpClient:
|
|
89
|
-
await self._provider.
|
|
84
|
+
await self._provider.connect()
|
|
90
85
|
return self
|
|
91
86
|
|
|
92
87
|
async def __aexit__(
|
|
@@ -95,7 +90,7 @@ class TonapiHttpClient(BaseClient):
|
|
|
95
90
|
exc_value: t.Optional[BaseException],
|
|
96
91
|
traceback: t.Optional[t.Any],
|
|
97
92
|
) -> None:
|
|
98
|
-
await self._provider.
|
|
93
|
+
await self._provider.close()
|
|
99
94
|
|
|
100
95
|
async def _send_boc(self, boc: str) -> None:
|
|
101
96
|
payload = BlockchainMessagePayload(boc=boc)
|
|
@@ -161,14 +156,19 @@ class TonapiHttpClient(BaseClient):
|
|
|
161
156
|
result = await self.provider.blockchain_account_method(
|
|
162
157
|
address=address,
|
|
163
158
|
method_name=method_name,
|
|
164
|
-
args=
|
|
159
|
+
args=encode_tonapi_stack(stack or []),
|
|
165
160
|
)
|
|
166
|
-
|
|
161
|
+
if result.exit_code != 0:
|
|
162
|
+
raise RunGetMethodError(
|
|
163
|
+
address=address,
|
|
164
|
+
method_name=method_name,
|
|
165
|
+
exit_code=result.exit_code,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
return decode_tonapi_stack(result.stack or [])
|
|
167
169
|
|
|
168
170
|
async def connect(self) -> None:
|
|
169
|
-
|
|
170
|
-
await self._provider.ensure_session()
|
|
171
|
+
await self._provider.connect()
|
|
171
172
|
|
|
172
173
|
async def close(self) -> None:
|
|
173
|
-
"""Close HTTP session if it is owned by the provider."""
|
|
174
174
|
await self._provider.close()
|