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
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import typing as t
|
|
2
|
+
|
|
3
|
+
import aiohttp
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
|
|
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,
|
|
12
|
+
SendBocPayload,
|
|
13
|
+
RunGetMethodPayload,
|
|
14
|
+
)
|
|
15
|
+
from tonutils.types import NetworkGlobalID, RetryPolicy
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ToncenterHttpProvider(HttpProvider):
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
network: NetworkGlobalID,
|
|
23
|
+
api_key: t.Optional[str] = None,
|
|
24
|
+
base_url: t.Optional[str] = None,
|
|
25
|
+
timeout: float = 10.0,
|
|
26
|
+
session: t.Optional[aiohttp.ClientSession] = None,
|
|
27
|
+
headers: t.Optional[t.Dict[str, str]] = None,
|
|
28
|
+
cookies: t.Optional[t.Dict[str, str]] = None,
|
|
29
|
+
rps_limit: t.Optional[int] = None,
|
|
30
|
+
rps_period: float = 1.0,
|
|
31
|
+
retry_policy: t.Optional[RetryPolicy] = None,
|
|
32
|
+
) -> None:
|
|
33
|
+
urls = {
|
|
34
|
+
NetworkGlobalID.MAINNET: "https://toncenter.com/api/v2",
|
|
35
|
+
NetworkGlobalID.TESTNET: "https://testnet.toncenter.com/api/v2",
|
|
36
|
+
}
|
|
37
|
+
base_url = base_url or urls[network]
|
|
38
|
+
headers = {**(headers or {}), **({"X-Api-Key": api_key} if api_key else {})}
|
|
39
|
+
super().__init__(
|
|
40
|
+
base_url=base_url,
|
|
41
|
+
session=session,
|
|
42
|
+
headers=headers,
|
|
43
|
+
cookies=cookies,
|
|
44
|
+
timeout=timeout,
|
|
45
|
+
rps_limit=rps_limit,
|
|
46
|
+
rps_period=rps_period,
|
|
47
|
+
retry_policy=retry_policy,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
@staticmethod
|
|
51
|
+
def _model(model: t.Type[BaseModel], data: t.Any) -> t.Any:
|
|
52
|
+
return model.model_validate(data)
|
|
53
|
+
|
|
54
|
+
async def send_boc(
|
|
55
|
+
self,
|
|
56
|
+
payload: SendBocPayload,
|
|
57
|
+
) -> None:
|
|
58
|
+
await self.send_http_request(
|
|
59
|
+
"POST",
|
|
60
|
+
"/sendBoc",
|
|
61
|
+
json_data=payload.model_dump(),
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
async def get_config_all(
|
|
65
|
+
self,
|
|
66
|
+
) -> GetConfigAllResult:
|
|
67
|
+
return self._model(
|
|
68
|
+
GetConfigAllResult,
|
|
69
|
+
await self.send_http_request(
|
|
70
|
+
"GET",
|
|
71
|
+
"/getConfigAll",
|
|
72
|
+
),
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
async def get_address_information(
|
|
76
|
+
self,
|
|
77
|
+
address: str,
|
|
78
|
+
) -> GetAddressInformationResult:
|
|
79
|
+
return self._model(
|
|
80
|
+
GetAddressInformationResult,
|
|
81
|
+
await self.send_http_request(
|
|
82
|
+
"GET",
|
|
83
|
+
"/getAddressInformation",
|
|
84
|
+
params={"address": address},
|
|
85
|
+
),
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
async def get_transaction(
|
|
89
|
+
self,
|
|
90
|
+
address: str,
|
|
91
|
+
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
|
|
98
|
+
|
|
99
|
+
return self._model(
|
|
100
|
+
GetTransactionResult,
|
|
101
|
+
await self.send_http_request(
|
|
102
|
+
"GET",
|
|
103
|
+
"/getTransactions",
|
|
104
|
+
params=params,
|
|
105
|
+
),
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
async def run_get_method(
|
|
109
|
+
self,
|
|
110
|
+
payload: RunGetMethodPayload,
|
|
111
|
+
) -> RunGetMethodResul:
|
|
112
|
+
return self._model(
|
|
113
|
+
RunGetMethodResul,
|
|
114
|
+
await self.send_http_request(
|
|
115
|
+
"POST",
|
|
116
|
+
"/runGetMethod",
|
|
117
|
+
json_data=payload.model_dump(),
|
|
118
|
+
),
|
|
119
|
+
)
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import typing as t
|
|
4
|
+
|
|
5
|
+
from pytoniq_core import Address, Cell, Slice
|
|
6
|
+
|
|
7
|
+
from tonutils.types import StackTag, StackItem, StackItems
|
|
8
|
+
from tonutils.utils import cell_to_b64, cell_to_hex, norm_stack_num, norm_stack_cell
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def decode_toncenter_stack(items: t.List[t.Any]) -> StackItems:
|
|
12
|
+
"""
|
|
13
|
+
Decode Toncenter TVM stack format into internal python structures.
|
|
14
|
+
|
|
15
|
+
Converts Toncenter stack elements into native Python values:
|
|
16
|
+
- NUM → int
|
|
17
|
+
- CELL/SLICE → Cell or Address (auto-detected)
|
|
18
|
+
- LIST/TUPLE → recursively decoded lists
|
|
19
|
+
- NULL → None
|
|
20
|
+
|
|
21
|
+
:param items: Raw stack items as returned by Toncenter
|
|
22
|
+
:return: Normalized list of decoded stack values
|
|
23
|
+
"""
|
|
24
|
+
out: StackItems = []
|
|
25
|
+
for item in items:
|
|
26
|
+
if not (isinstance(item, list) and len(item) == 2):
|
|
27
|
+
continue
|
|
28
|
+
tag, payload = item
|
|
29
|
+
if tag == StackTag.NULL:
|
|
30
|
+
out.append(None)
|
|
31
|
+
elif tag == StackTag.NUM.value:
|
|
32
|
+
out.append(norm_stack_num(payload))
|
|
33
|
+
elif tag in (
|
|
34
|
+
StackTag.CELL.value,
|
|
35
|
+
StackTag.TVM_CELL.value,
|
|
36
|
+
StackTag.SLICE.value,
|
|
37
|
+
StackTag.TVM_SLICE.value,
|
|
38
|
+
):
|
|
39
|
+
out.append(norm_stack_cell((payload or {}).get("bytes")))
|
|
40
|
+
elif tag in (StackTag.LIST.value, StackTag.TUPLE.value):
|
|
41
|
+
elements = (payload or {}).get("elements") or []
|
|
42
|
+
out.append(decode_toncenter_stack(elements) if len(elements) > 0 else None)
|
|
43
|
+
return out
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def encode_toncenter_stack(items: t.List[StackItem]) -> t.List[list]:
|
|
47
|
+
"""
|
|
48
|
+
Encode Python stack values into Toncenter-compatible TVM stack format.
|
|
49
|
+
|
|
50
|
+
Supports:
|
|
51
|
+
- int → NUM
|
|
52
|
+
- Cell → TVM_CELL
|
|
53
|
+
- Slice → TVM_SLICE
|
|
54
|
+
- list/tuple → LIST/TUPLE (recursive)
|
|
55
|
+
- Address → encoded as TVM_CELL
|
|
56
|
+
|
|
57
|
+
:param items: List of python TVM stack values
|
|
58
|
+
:return: Encoded stack items suitable for Toncenter API
|
|
59
|
+
"""
|
|
60
|
+
out: t.List[t.Any] = []
|
|
61
|
+
for item in items:
|
|
62
|
+
tpe = StackTag.of(item)
|
|
63
|
+
if tpe is StackTag.NUM:
|
|
64
|
+
out.append([StackTag.NUM.value, str(t.cast(str, item))])
|
|
65
|
+
elif tpe is StackTag.CELL:
|
|
66
|
+
cell = item.to_cell() if isinstance(item, Address) else t.cast(Cell, item)
|
|
67
|
+
out.append([StackTag.TVM_CELL.value, cell_to_b64(cell)])
|
|
68
|
+
elif tpe is StackTag.SLICE:
|
|
69
|
+
cell = t.cast(Slice, item).to_cell()
|
|
70
|
+
out.append([StackTag.TVM_SLICE.value, cell_to_b64(cell)])
|
|
71
|
+
elif tpe in (StackTag.LIST, StackTag.TUPLE):
|
|
72
|
+
out.append(
|
|
73
|
+
[tpe.value, {"elements": encode_toncenter_stack(t.cast(list, item))}]
|
|
74
|
+
)
|
|
75
|
+
return out
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def decode_tonapi_stack(items: t.List[t.Any]) -> StackItems:
|
|
79
|
+
"""
|
|
80
|
+
Decode Tonapi TVM stack format into internal Python structures.
|
|
81
|
+
|
|
82
|
+
Tonapi represents stack items as dictionaries with a "type" key and
|
|
83
|
+
a value under the same key. This function converts them into:
|
|
84
|
+
- NUM → int
|
|
85
|
+
- CELL/SLICE → Cell or Address (auto-detected)
|
|
86
|
+
- LIST/TUPLE → nested Python lists
|
|
87
|
+
- NULL → None
|
|
88
|
+
|
|
89
|
+
:param items: Raw stack items as returned by Tonapi
|
|
90
|
+
:return: Normalized list of decoded stack values
|
|
91
|
+
"""
|
|
92
|
+
out: StackItems = []
|
|
93
|
+
for item in items:
|
|
94
|
+
if not isinstance(item, dict):
|
|
95
|
+
continue
|
|
96
|
+
tpe = item.get("type")
|
|
97
|
+
val = item.get(tpe)
|
|
98
|
+
if tpe == StackTag.NULL.value:
|
|
99
|
+
out.append(None)
|
|
100
|
+
elif tpe == StackTag.NUM.value and val is not None:
|
|
101
|
+
out.append(norm_stack_num(t.cast(str | int, val)))
|
|
102
|
+
elif tpe in (StackTag.CELL.value, StackTag.SLICE.value):
|
|
103
|
+
out.append(norm_stack_cell(val))
|
|
104
|
+
elif tpe in (StackTag.LIST.value, StackTag.TUPLE.value):
|
|
105
|
+
inner: t.List[t.Any] = []
|
|
106
|
+
for el in val or []:
|
|
107
|
+
inner.append(
|
|
108
|
+
decode_tonapi_stack([el])[0] if isinstance(el, dict) else el
|
|
109
|
+
)
|
|
110
|
+
out.append(inner)
|
|
111
|
+
return out
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def encode_tonapi_stack(items: t.List[StackItem]) -> t.List[t.Any]:
|
|
115
|
+
"""
|
|
116
|
+
Encode Python TVM stack values into Tonapi-compatible format.
|
|
117
|
+
|
|
118
|
+
Produces values expected by Tonapi query parameters:
|
|
119
|
+
- int → hex string
|
|
120
|
+
- Cell/Address → hex BoC string
|
|
121
|
+
- Slice → hex BoC string
|
|
122
|
+
- list/tuple → recursively encoded list
|
|
123
|
+
|
|
124
|
+
:param items: List of python TVM stack values
|
|
125
|
+
:return: Encoded stack items suitable for Tonapi API
|
|
126
|
+
"""
|
|
127
|
+
out: t.List[t.Any] = []
|
|
128
|
+
for item in items:
|
|
129
|
+
tpe = StackTag.of(item)
|
|
130
|
+
if tpe == StackTag.NUM:
|
|
131
|
+
out.append(hex(t.cast(int, item)))
|
|
132
|
+
elif tpe == StackTag.CELL:
|
|
133
|
+
cell = item.to_cell() if isinstance(item, Address) else item
|
|
134
|
+
out.append(cell_to_hex(cell))
|
|
135
|
+
elif tpe == StackTag.SLICE:
|
|
136
|
+
cell = t.cast(Slice, item).to_cell()
|
|
137
|
+
out.append(cell_to_hex(cell))
|
|
138
|
+
elif tpe in (StackTag.LIST, StackTag.TUPLE):
|
|
139
|
+
out.append(encode_tonapi_stack(t.cast(list, item)))
|
|
140
|
+
return out
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import time
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class RateLimiter:
|
|
6
|
+
"""
|
|
7
|
+
Asynchronous token-bucket rate limiter with optional priority acquisition.
|
|
8
|
+
|
|
9
|
+
Limits the number of acquire operations per time period and supports
|
|
10
|
+
priority waiters that can bypass non-priority requests when tokens
|
|
11
|
+
become available.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
__slots__ = (
|
|
15
|
+
"_max_rate",
|
|
16
|
+
"_period",
|
|
17
|
+
"_tokens",
|
|
18
|
+
"_updated_at",
|
|
19
|
+
"_cond",
|
|
20
|
+
"_priority_waiters",
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
def __init__(self, max_rate: int, period: float = 1.0) -> None:
|
|
24
|
+
"""
|
|
25
|
+
Initialize the rate limiter.
|
|
26
|
+
|
|
27
|
+
:param max_rate: Maximum number of acquisitions allowed per period.
|
|
28
|
+
:param period: Period length in seconds.
|
|
29
|
+
:raises ValueError: If ``max_rate`` or ``period`` is not positive.
|
|
30
|
+
"""
|
|
31
|
+
if max_rate <= 0:
|
|
32
|
+
raise ValueError("max_rate must be > 0")
|
|
33
|
+
if period <= 0:
|
|
34
|
+
raise ValueError("period must be > 0")
|
|
35
|
+
|
|
36
|
+
self._max_rate = max_rate
|
|
37
|
+
self._period = period
|
|
38
|
+
self._tokens = float(max_rate)
|
|
39
|
+
self._updated_at = time.monotonic()
|
|
40
|
+
self._cond = asyncio.Condition()
|
|
41
|
+
self._priority_waiters = 0
|
|
42
|
+
|
|
43
|
+
def when_ready(self) -> float:
|
|
44
|
+
"""
|
|
45
|
+
Calculate delay until the next token becomes available.
|
|
46
|
+
|
|
47
|
+
:return: Number of seconds to wait before a token can be acquired,
|
|
48
|
+
or ``0`` if a token is available immediately.
|
|
49
|
+
"""
|
|
50
|
+
now = time.monotonic()
|
|
51
|
+
tokens = self._peek_tokens(now)
|
|
52
|
+
if tokens >= 1.0:
|
|
53
|
+
return 0.0
|
|
54
|
+
return self._seconds_to_one_token(tokens)
|
|
55
|
+
|
|
56
|
+
async def acquire(self, priority: bool = False) -> None:
|
|
57
|
+
"""
|
|
58
|
+
Acquire a single token, waiting asynchronously if necessary.
|
|
59
|
+
|
|
60
|
+
Priority acquisitions are allowed to bypass non-priority waiters
|
|
61
|
+
when tokens become available.
|
|
62
|
+
|
|
63
|
+
:param priority: Whether to acquire the token with priority.
|
|
64
|
+
"""
|
|
65
|
+
if priority:
|
|
66
|
+
async with self._cond:
|
|
67
|
+
self._priority_waiters += 1
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
await self._acquire(priority=True)
|
|
71
|
+
finally:
|
|
72
|
+
async with self._cond:
|
|
73
|
+
self._priority_waiters -= 1
|
|
74
|
+
self._cond.notify_all()
|
|
75
|
+
return
|
|
76
|
+
|
|
77
|
+
await self._acquire(priority=False)
|
|
78
|
+
|
|
79
|
+
async def _acquire(self, priority: bool) -> None:
|
|
80
|
+
while True:
|
|
81
|
+
async with self._cond:
|
|
82
|
+
now = time.monotonic()
|
|
83
|
+
self._refill(now)
|
|
84
|
+
|
|
85
|
+
if self._tokens >= 1.0 and (priority or self._priority_waiters == 0):
|
|
86
|
+
self._tokens -= 1.0
|
|
87
|
+
self._cond.notify_all()
|
|
88
|
+
return
|
|
89
|
+
|
|
90
|
+
if self._tokens < 1.0:
|
|
91
|
+
timeout = self._seconds_to_one_token(self._tokens)
|
|
92
|
+
await asyncio.wait_for(self._cond.wait(), timeout=timeout)
|
|
93
|
+
else:
|
|
94
|
+
await self._cond.wait()
|
|
95
|
+
|
|
96
|
+
def _peek_tokens(self, now: float) -> float:
|
|
97
|
+
elapsed = now - self._updated_at
|
|
98
|
+
if elapsed <= 0.0:
|
|
99
|
+
return self._tokens
|
|
100
|
+
|
|
101
|
+
rate = self._max_rate / self._period
|
|
102
|
+
tokens = self._tokens + elapsed * rate
|
|
103
|
+
return min(float(self._max_rate), tokens)
|
|
104
|
+
|
|
105
|
+
def _refill(self, now: float) -> None:
|
|
106
|
+
self._tokens = self._peek_tokens(now)
|
|
107
|
+
self._updated_at = now
|
|
108
|
+
|
|
109
|
+
def _seconds_to_one_token(self, tokens: float) -> float:
|
|
110
|
+
missing = 1.0 - tokens
|
|
111
|
+
if missing <= 0.0:
|
|
112
|
+
return 0.0
|
|
113
|
+
|
|
114
|
+
rate = self._max_rate / self._period
|
|
115
|
+
return missing / rate
|
tonutils/contracts/__init__.py
CHANGED
|
@@ -91,6 +91,7 @@ from .nft import (
|
|
|
91
91
|
royalty_params_get_method,
|
|
92
92
|
)
|
|
93
93
|
from .opcodes import OpCode
|
|
94
|
+
from .protocol import ContractProtocol
|
|
94
95
|
from .telegram import (
|
|
95
96
|
TeleCollectionData,
|
|
96
97
|
TeleItemAuction,
|
|
@@ -142,6 +143,7 @@ from .wallet import (
|
|
|
142
143
|
WalletHighloadV3Data,
|
|
143
144
|
WalletHighloadV3Params,
|
|
144
145
|
WalletHighloadV3R1,
|
|
146
|
+
WalletProtocol,
|
|
145
147
|
WalletPreprocessedV2,
|
|
146
148
|
WalletPreprocessedV2Config,
|
|
147
149
|
WalletPreprocessedV2Data,
|
|
@@ -194,6 +196,7 @@ __all__ = [
|
|
|
194
196
|
"BaseMessageBuilder",
|
|
195
197
|
"ChangeDNSRecordBody",
|
|
196
198
|
"CONTRACT_CODES",
|
|
199
|
+
"ContractProtocol",
|
|
197
200
|
"ContractVersion",
|
|
198
201
|
"DNSBalanceReleaseBody",
|
|
199
202
|
"DNSRecordDNSNextResolver",
|
|
@@ -297,6 +300,7 @@ __all__ = [
|
|
|
297
300
|
"WalletHighloadV3Data",
|
|
298
301
|
"WalletHighloadV3Params",
|
|
299
302
|
"WalletHighloadV3R1",
|
|
303
|
+
"WalletProtocol",
|
|
300
304
|
"WalletPreprocessedV2",
|
|
301
305
|
"WalletPreprocessedV2Config",
|
|
302
306
|
"WalletPreprocessedV2Data",
|
tonutils/contracts/base.py
CHANGED
|
@@ -2,14 +2,17 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import typing as t
|
|
4
4
|
|
|
5
|
-
from pyapiq.exceptions import APIQException, RateLimitExceeded
|
|
6
5
|
from pytoniq_core import Address, Cell, StateInit, TlbScheme
|
|
7
6
|
|
|
7
|
+
from tonutils.clients.protocol import ClientProtocol
|
|
8
8
|
from tonutils.contracts.codes import CONTRACT_CODES
|
|
9
|
+
from tonutils.contracts.protocol import ContractProtocol
|
|
9
10
|
from tonutils.contracts.versions import ContractVersion
|
|
10
|
-
from tonutils.exceptions import
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
from tonutils.exceptions import (
|
|
12
|
+
ContractError,
|
|
13
|
+
StateNotLoadedError,
|
|
14
|
+
ProviderResponseError,
|
|
15
|
+
)
|
|
13
16
|
from tonutils.types import AddressLike, ContractState, ContractStateInfo, WorkchainID
|
|
14
17
|
from tonutils.utils import to_cell
|
|
15
18
|
|
|
@@ -49,7 +52,7 @@ class BaseContract(ContractProtocol[_D]):
|
|
|
49
52
|
default_code = to_cell(CONTRACT_CODES[cls.VERSION])
|
|
50
53
|
except KeyError:
|
|
51
54
|
raise ContractError(
|
|
52
|
-
cls, f"No contract code defined for `
|
|
55
|
+
cls, f"No contract code defined for `version` {cls.VERSION!r}."
|
|
53
56
|
)
|
|
54
57
|
return default_code
|
|
55
58
|
|
|
@@ -96,7 +99,7 @@ class BaseContract(ContractProtocol[_D]):
|
|
|
96
99
|
:return: Contract state information
|
|
97
100
|
"""
|
|
98
101
|
if self._state_info is None:
|
|
99
|
-
raise
|
|
102
|
+
raise StateNotLoadedError(self, missing="state_info")
|
|
100
103
|
return t.cast(ContractStateInfo, self._state_info)
|
|
101
104
|
|
|
102
105
|
@property
|
|
@@ -111,7 +114,7 @@ class BaseContract(ContractProtocol[_D]):
|
|
|
111
114
|
if not hasattr(self, "_data_model") or self._data_model is None:
|
|
112
115
|
raise ContractError(self, "No `_data_model` defined for contract class.")
|
|
113
116
|
if not (self._state_info and self._state_info.data):
|
|
114
|
-
raise
|
|
117
|
+
raise StateNotLoadedError(self, missing="state_data")
|
|
115
118
|
cs = self._state_info.data.begin_parse()
|
|
116
119
|
return self._data_model.deserialize(cs)
|
|
117
120
|
|
|
@@ -205,6 +208,26 @@ class BaseContract(ContractProtocol[_D]):
|
|
|
205
208
|
"""
|
|
206
209
|
return self.state_info.data
|
|
207
210
|
|
|
211
|
+
@classmethod
|
|
212
|
+
async def _load_state_info(
|
|
213
|
+
cls,
|
|
214
|
+
client: ClientProtocol,
|
|
215
|
+
address: Address,
|
|
216
|
+
) -> ContractStateInfo:
|
|
217
|
+
"""
|
|
218
|
+
Fetch contract state from the blockchain.
|
|
219
|
+
|
|
220
|
+
If the request fails (except rate limits), sets state to default empty state.
|
|
221
|
+
"""
|
|
222
|
+
try:
|
|
223
|
+
return await client.get_contract_info(address)
|
|
224
|
+
except ProviderResponseError as e:
|
|
225
|
+
if e.code in {429, 228, 5556}: # rate limit exceed
|
|
226
|
+
raise
|
|
227
|
+
return ContractStateInfo()
|
|
228
|
+
except (Exception,):
|
|
229
|
+
return ContractStateInfo()
|
|
230
|
+
|
|
208
231
|
async def refresh(self) -> None:
|
|
209
232
|
"""
|
|
210
233
|
Refresh contract state from the blockchain.
|
|
@@ -212,13 +235,7 @@ class BaseContract(ContractProtocol[_D]):
|
|
|
212
235
|
Fetches current contract information and updates the cached state_info.
|
|
213
236
|
If the request fails (except rate limits), sets state to default empty state.
|
|
214
237
|
"""
|
|
215
|
-
|
|
216
|
-
state_info = await self.client.get_contract_info(self.address)
|
|
217
|
-
except RateLimitExceeded:
|
|
218
|
-
raise
|
|
219
|
-
except APIQException:
|
|
220
|
-
state_info = ContractStateInfo()
|
|
221
|
-
self._state_info = state_info
|
|
238
|
+
self._state_info = await self._load_state_info(self.client, self.address)
|
|
222
239
|
|
|
223
240
|
@classmethod
|
|
224
241
|
def from_state_init(
|
|
@@ -305,12 +322,8 @@ class BaseContract(ContractProtocol[_D]):
|
|
|
305
322
|
address = Address(address)
|
|
306
323
|
if not load_state:
|
|
307
324
|
return cls(client, address)
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
except RateLimitExceeded:
|
|
311
|
-
raise
|
|
312
|
-
except APIQException:
|
|
313
|
-
state_info = ContractStateInfo()
|
|
325
|
+
|
|
326
|
+
state_info = await cls._load_state_info(client, address)
|
|
314
327
|
return cls(client, address, state_info=state_info)
|
|
315
328
|
|
|
316
329
|
def __repr__(self) -> str:
|
|
@@ -2,8 +2,8 @@ import typing as t
|
|
|
2
2
|
|
|
3
3
|
from pytoniq_core import Cell, begin_cell
|
|
4
4
|
|
|
5
|
-
from tonutils.
|
|
6
|
-
from tonutils.
|
|
5
|
+
from tonutils.clients.protocol import ClientProtocol
|
|
6
|
+
from tonutils.contracts.protocol import ContractProtocol
|
|
7
7
|
from tonutils.types import AddressLike, DNSCategory
|
|
8
8
|
|
|
9
9
|
|
|
@@ -2,8 +2,8 @@ import typing as t
|
|
|
2
2
|
|
|
3
3
|
from pytoniq_core import Address
|
|
4
4
|
|
|
5
|
-
from tonutils.
|
|
6
|
-
from tonutils.
|
|
5
|
+
from tonutils.clients.protocol import ClientProtocol
|
|
6
|
+
from tonutils.contracts.protocol import ContractProtocol
|
|
7
7
|
from tonutils.types import AddressLike
|
|
8
8
|
|
|
9
9
|
|
|
@@ -2,8 +2,8 @@ import typing as t
|
|
|
2
2
|
|
|
3
3
|
from pytoniq_core import Address, Cell
|
|
4
4
|
|
|
5
|
-
from tonutils.
|
|
6
|
-
from tonutils.
|
|
5
|
+
from tonutils.clients.protocol import ClientProtocol
|
|
6
|
+
from tonutils.contracts.protocol import ContractProtocol
|
|
7
7
|
from tonutils.types import AddressLike
|
|
8
8
|
|
|
9
9
|
|
tonutils/contracts/nft/tlb.py
CHANGED
|
@@ -822,7 +822,7 @@ class NFTCollectionBatchMintItemBody(TlbScheme):
|
|
|
822
822
|
:return: Sorted list of (index, amount, item_ref) tuples
|
|
823
823
|
"""
|
|
824
824
|
hashmap = cs.load_dict(key_length=64)
|
|
825
|
-
out:
|
|
825
|
+
out: t.List[tuple[int, int, Cell]] = []
|
|
826
826
|
for key, val in hashmap.items():
|
|
827
827
|
amount = val.load_coins()
|
|
828
828
|
item_ref = val.load_ref()
|