tonutils 2.0.1b4__py3-none-any.whl → 2.0.1b6__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 +21 -24
- tonutils/clients/adnl/client.py +21 -24
- tonutils/clients/adnl/provider/config.py +22 -7
- tonutils/clients/base.py +22 -12
- tonutils/clients/http/__init__.py +11 -11
- tonutils/clients/http/balancer.py +11 -11
- 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 +3 -3
- tonutils/clients/http/clients/tonapi.py +9 -10
- tonutils/clients/http/clients/toncenter.py +50 -23
- tonutils/clients/http/{providers → provider}/__init__.py +4 -1
- tonutils/clients/http/{providers → provider}/base.py +71 -7
- tonutils/clients/http/{providers/toncenter → provider}/models.py +43 -1
- tonutils/clients/http/{providers/tonapi/provider.py → provider/tonapi.py} +8 -8
- tonutils/clients/http/{providers/toncenter/provider.py → provider/toncenter.py} +22 -14
- tonutils/clients/limiter.py +61 -59
- tonutils/clients/protocol.py +2 -2
- tonutils/contracts/wallet/base.py +2 -3
- tonutils/contracts/wallet/messages.py +4 -8
- 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 -19
- tonutils/tools/block_scanner/events.py +48 -7
- tonutils/tools/block_scanner/scanner.py +315 -223
- tonutils/tools/block_scanner/storage.py +11 -0
- tonutils/utils.py +0 -48
- {tonutils-2.0.1b4.dist-info → tonutils-2.0.1b6.dist-info}/METADATA +1 -1
- {tonutils-2.0.1b4.dist-info → tonutils-2.0.1b6.dist-info}/RECORD +39 -41
- {tonutils-2.0.1b4.dist-info → tonutils-2.0.1b6.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 -96
- tonutils/tools/block_scanner/where.py +0 -151
- {tonutils-2.0.1b4.dist-info → tonutils-2.0.1b6.dist-info}/entry_points.txt +0 -0
- {tonutils-2.0.1b4.dist-info → tonutils-2.0.1b6.dist-info}/licenses/LICENSE +0 -0
- {tonutils-2.0.1b4.dist-info → tonutils-2.0.1b6.dist-info}/top_level.txt +0 -0
|
@@ -7,11 +7,8 @@ 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 (
|
|
@@ -24,15 +21,15 @@ from tonutils.types import (
|
|
|
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,
|
|
@@ -81,7 +78,7 @@ class ToncenterHttpClient(BaseClient):
|
|
|
81
78
|
session = self._provider.session
|
|
82
79
|
return session is not None and not session.closed
|
|
83
80
|
|
|
84
|
-
async def __aenter__(self) ->
|
|
81
|
+
async def __aenter__(self) -> ToncenterClient:
|
|
85
82
|
await self._provider.connect()
|
|
86
83
|
return self
|
|
87
84
|
|
|
@@ -166,28 +163,58 @@ class ToncenterHttpClient(BaseClient):
|
|
|
166
163
|
|
|
167
164
|
return contract_info
|
|
168
165
|
|
|
169
|
-
async def
|
|
166
|
+
async def _get_transactions(
|
|
170
167
|
self,
|
|
171
168
|
address: str,
|
|
172
169
|
limit: int = 100,
|
|
173
170
|
from_lt: t.Optional[int] = None,
|
|
174
|
-
to_lt: int =
|
|
171
|
+
to_lt: t.Optional[int] = None,
|
|
175
172
|
) -> t.List[Transaction]:
|
|
176
|
-
if
|
|
177
|
-
|
|
173
|
+
to_lt = 0 if to_lt is None else to_lt
|
|
174
|
+
transactions: t.List[Transaction] = []
|
|
178
175
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
176
|
+
curr_lt: t.Optional[int] = None
|
|
177
|
+
curr_hash: t.Optional[str] = None
|
|
178
|
+
|
|
179
|
+
while len(transactions) < limit:
|
|
180
|
+
request = await self.provider.get_transactions(
|
|
181
|
+
address=address,
|
|
182
|
+
limit=100,
|
|
183
|
+
lt=curr_lt,
|
|
184
|
+
from_hash=curr_hash,
|
|
185
|
+
to_lt=to_lt if to_lt > 0 else None,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
batch = []
|
|
189
|
+
for tx in request.result or []:
|
|
190
|
+
if tx.data is not None:
|
|
191
|
+
tx_slice = Slice.one_from_boc(tx.data)
|
|
192
|
+
batch.append(Transaction.deserialize(tx_slice))
|
|
193
|
+
|
|
194
|
+
if not batch:
|
|
195
|
+
break
|
|
196
|
+
|
|
197
|
+
for tx in batch:
|
|
198
|
+
# Skip transactions above from_lt (if specified)
|
|
199
|
+
if from_lt is not None and tx.lt > from_lt:
|
|
200
|
+
continue
|
|
201
|
+
|
|
202
|
+
# Stop if we've reached the lower bound
|
|
203
|
+
if to_lt > 0 and tx.lt <= to_lt:
|
|
204
|
+
return transactions[:limit]
|
|
205
|
+
|
|
206
|
+
transactions.append(tx)
|
|
207
|
+
|
|
208
|
+
if len(transactions) >= limit:
|
|
209
|
+
return transactions
|
|
210
|
+
|
|
211
|
+
# Setup for next iteration
|
|
212
|
+
last_tx = batch[-1]
|
|
213
|
+
if last_tx.prev_trans_lt == 0:
|
|
214
|
+
break
|
|
185
215
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
if tx.data is not None:
|
|
189
|
-
tx_slice = Slice.one_from_boc(tx.data)
|
|
190
|
-
transactions.append(Transaction.deserialize(tx_slice))
|
|
216
|
+
curr_lt = last_tx.prev_trans_lt
|
|
217
|
+
curr_hash = last_tx.prev_trans_hash.hex()
|
|
191
218
|
|
|
192
219
|
return transactions
|
|
193
220
|
|
|
@@ -1,17 +1,18 @@
|
|
|
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
|
|
7
8
|
|
|
8
|
-
from tonutils.clients.http.providers.response import HttpResponse
|
|
9
9
|
from tonutils.clients.limiter import RateLimiter
|
|
10
10
|
from tonutils.exceptions import (
|
|
11
11
|
NotConnectedError,
|
|
12
12
|
ProviderResponseError,
|
|
13
13
|
ProviderTimeoutError,
|
|
14
14
|
RetryLimitError,
|
|
15
|
+
CDN_CHALLENGE_MARKERS,
|
|
15
16
|
)
|
|
16
17
|
from tonutils.types import RetryPolicy
|
|
17
18
|
|
|
@@ -90,6 +91,34 @@ class HttpProvider:
|
|
|
90
91
|
await self._session.close()
|
|
91
92
|
self._session = None
|
|
92
93
|
|
|
94
|
+
@classmethod
|
|
95
|
+
async def _read_response(cls, resp: aiohttp.ClientResponse) -> t.Any:
|
|
96
|
+
body = await resp.read()
|
|
97
|
+
if not body:
|
|
98
|
+
return ""
|
|
99
|
+
|
|
100
|
+
data = body.decode("utf-8", errors="replace").strip()
|
|
101
|
+
if not data:
|
|
102
|
+
return ""
|
|
103
|
+
|
|
104
|
+
try:
|
|
105
|
+
return json.loads(data)
|
|
106
|
+
except (Exception,):
|
|
107
|
+
return data
|
|
108
|
+
|
|
109
|
+
@classmethod
|
|
110
|
+
def _raise_error(cls, status: int, url: str, data: t.Any) -> None:
|
|
111
|
+
exc = cls._detect_proxy_error(data, status=status, url=url)
|
|
112
|
+
if exc is not None:
|
|
113
|
+
raise exc
|
|
114
|
+
|
|
115
|
+
message = cls._extract_error_message(data)
|
|
116
|
+
raise ProviderResponseError(
|
|
117
|
+
code=status,
|
|
118
|
+
message=message,
|
|
119
|
+
endpoint=url,
|
|
120
|
+
)
|
|
121
|
+
|
|
93
122
|
async def _send_once(
|
|
94
123
|
self,
|
|
95
124
|
method: str,
|
|
@@ -126,13 +155,9 @@ class HttpProvider:
|
|
|
126
155
|
params=params,
|
|
127
156
|
json=json_data,
|
|
128
157
|
) as resp:
|
|
129
|
-
data = await
|
|
158
|
+
data = await self._read_response(resp)
|
|
130
159
|
if resp.status >= 400:
|
|
131
|
-
|
|
132
|
-
status=int(resp.status),
|
|
133
|
-
url=url,
|
|
134
|
-
data=data,
|
|
135
|
-
)
|
|
160
|
+
self._raise_error(int(resp.status), url, data)
|
|
136
161
|
return data
|
|
137
162
|
|
|
138
163
|
except asyncio.TimeoutError as exc:
|
|
@@ -199,3 +224,42 @@ class HttpProvider:
|
|
|
199
224
|
) from e
|
|
200
225
|
|
|
201
226
|
await asyncio.sleep(rule.delay(attempts[key] - 1))
|
|
227
|
+
|
|
228
|
+
@classmethod
|
|
229
|
+
def _detect_proxy_error(
|
|
230
|
+
cls,
|
|
231
|
+
data: t.Any,
|
|
232
|
+
status: int,
|
|
233
|
+
url: str,
|
|
234
|
+
) -> t.Optional[ProviderResponseError]:
|
|
235
|
+
body = (
|
|
236
|
+
" ".join(str(v) for v in data.values())
|
|
237
|
+
if isinstance(data, dict)
|
|
238
|
+
else str(data)
|
|
239
|
+
).lower()
|
|
240
|
+
|
|
241
|
+
for marker, message in CDN_CHALLENGE_MARKERS.items():
|
|
242
|
+
if marker in body:
|
|
243
|
+
return ProviderResponseError(
|
|
244
|
+
code=status,
|
|
245
|
+
message=message,
|
|
246
|
+
endpoint=url,
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
return None
|
|
250
|
+
|
|
251
|
+
@staticmethod
|
|
252
|
+
def _extract_error_message(data: t.Any) -> str:
|
|
253
|
+
if isinstance(data, dict):
|
|
254
|
+
lowered = {k.lower(): v for k, v in data.items()}
|
|
255
|
+
for key in ("error", "message", "detail", "description"):
|
|
256
|
+
if key in lowered and isinstance(lowered[key], str):
|
|
257
|
+
return lowered[key]
|
|
258
|
+
string_values = [str(v) for v in data.values() if isinstance(v, str)]
|
|
259
|
+
return "; ".join(string_values) if string_values else str(data)
|
|
260
|
+
|
|
261
|
+
if isinstance(data, list):
|
|
262
|
+
return "; ".join(map(str, data))
|
|
263
|
+
if isinstance(data, str):
|
|
264
|
+
return data
|
|
265
|
+
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
|
|
@@ -5,13 +5,13 @@ import typing as t
|
|
|
5
5
|
import aiohttp
|
|
6
6
|
from pydantic import BaseModel
|
|
7
7
|
|
|
8
|
-
from tonutils.clients.http.
|
|
9
|
-
from tonutils.clients.http.
|
|
10
|
-
|
|
8
|
+
from tonutils.clients.http.provider.base import HttpProvider
|
|
9
|
+
from tonutils.clients.http.provider.models import (
|
|
10
|
+
BlockchainMessagePayload,
|
|
11
|
+
BlockchainConfigResult,
|
|
11
12
|
BlockchainAccountResult,
|
|
12
13
|
BlockchainAccountTransactionsResult,
|
|
13
|
-
|
|
14
|
-
BlockchainMessagePayload,
|
|
14
|
+
BlockchainAccountMethodResult,
|
|
15
15
|
)
|
|
16
16
|
from tonutils.types import NetworkGlobalID, RetryPolicy
|
|
17
17
|
|
|
@@ -21,6 +21,7 @@ class TonapiHttpProvider(HttpProvider):
|
|
|
21
21
|
def __init__(
|
|
22
22
|
self,
|
|
23
23
|
network: NetworkGlobalID,
|
|
24
|
+
*,
|
|
24
25
|
api_key: str,
|
|
25
26
|
base_url: t.Optional[str] = None,
|
|
26
27
|
timeout: float = 10.0,
|
|
@@ -92,12 +93,11 @@ class TonapiHttpProvider(HttpProvider):
|
|
|
92
93
|
limit: int = 100,
|
|
93
94
|
after_lt: t.Optional[int] = None,
|
|
94
95
|
before_lt: t.Optional[int] = None,
|
|
95
|
-
sort_order: str = "desc",
|
|
96
96
|
) -> BlockchainAccountTransactionsResult:
|
|
97
|
-
params = {"limit": limit
|
|
97
|
+
params = {"limit": limit}
|
|
98
98
|
if after_lt is not None:
|
|
99
99
|
params["after_lt"] = after_lt
|
|
100
|
-
if before_lt is not None
|
|
100
|
+
if before_lt is not None:
|
|
101
101
|
params["before_lt"] = before_lt
|
|
102
102
|
|
|
103
103
|
return self._model(
|
|
@@ -3,14 +3,14 @@ import typing as t
|
|
|
3
3
|
import aiohttp
|
|
4
4
|
from pydantic import BaseModel
|
|
5
5
|
|
|
6
|
-
from tonutils.clients.http.
|
|
7
|
-
from tonutils.clients.http.
|
|
8
|
-
GetAddressInformationResult,
|
|
9
|
-
GetConfigAllResult,
|
|
10
|
-
GetTransactionResult,
|
|
11
|
-
RunGetMethodResul,
|
|
6
|
+
from tonutils.clients.http.provider.base import HttpProvider
|
|
7
|
+
from tonutils.clients.http.provider.models import (
|
|
12
8
|
SendBocPayload,
|
|
9
|
+
GetConfigAllResult,
|
|
10
|
+
GetAddressInformationResult,
|
|
11
|
+
GetTransactionsResult,
|
|
13
12
|
RunGetMethodPayload,
|
|
13
|
+
RunGetMethodResul,
|
|
14
14
|
)
|
|
15
15
|
from tonutils.types import NetworkGlobalID, RetryPolicy
|
|
16
16
|
|
|
@@ -20,6 +20,7 @@ class ToncenterHttpProvider(HttpProvider):
|
|
|
20
20
|
def __init__(
|
|
21
21
|
self,
|
|
22
22
|
network: NetworkGlobalID,
|
|
23
|
+
*,
|
|
23
24
|
api_key: t.Optional[str] = None,
|
|
24
25
|
base_url: t.Optional[str] = None,
|
|
25
26
|
timeout: float = 10.0,
|
|
@@ -85,19 +86,26 @@ class ToncenterHttpProvider(HttpProvider):
|
|
|
85
86
|
),
|
|
86
87
|
)
|
|
87
88
|
|
|
88
|
-
async def
|
|
89
|
+
async def get_transactions(
|
|
89
90
|
self,
|
|
90
91
|
address: str,
|
|
91
92
|
limit: int = 100,
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
93
|
+
lt: t.Optional[int] = None,
|
|
94
|
+
from_hash: t.Optional[str] = None,
|
|
95
|
+
to_lt: t.Optional[int] = None,
|
|
96
|
+
) -> GetTransactionsResult:
|
|
97
|
+
params = {"address": address, "limit": limit, "archival": "true"}
|
|
98
|
+
|
|
99
|
+
# lt and hash must be used together
|
|
100
|
+
if lt is not None and from_hash is not None:
|
|
101
|
+
params["lt"] = lt
|
|
102
|
+
params["hash"] = from_hash
|
|
103
|
+
|
|
104
|
+
if to_lt is not None:
|
|
105
|
+
params["to_lt"] = to_lt
|
|
98
106
|
|
|
99
107
|
return self._model(
|
|
100
|
-
|
|
108
|
+
GetTransactionsResult,
|
|
101
109
|
await self.send_http_request(
|
|
102
110
|
"GET",
|
|
103
111
|
"/getTransactions",
|
tonutils/clients/limiter.py
CHANGED
|
@@ -4,11 +4,10 @@ import time
|
|
|
4
4
|
|
|
5
5
|
class RateLimiter:
|
|
6
6
|
"""
|
|
7
|
-
Asynchronous token-bucket rate limiter with
|
|
7
|
+
Asynchronous token-bucket rate limiter with priority support.
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
become available.
|
|
9
|
+
Priority requests are served before non-priority requests when
|
|
10
|
+
tokens become available.
|
|
12
11
|
"""
|
|
13
12
|
|
|
14
13
|
__slots__ = (
|
|
@@ -26,7 +25,7 @@ class RateLimiter:
|
|
|
26
25
|
|
|
27
26
|
:param max_rate: Maximum number of acquisitions allowed per period.
|
|
28
27
|
:param period: Period length in seconds.
|
|
29
|
-
:raises ValueError: If
|
|
28
|
+
:raises ValueError: If max_rate or period is not positive.
|
|
30
29
|
"""
|
|
31
30
|
if max_rate <= 0:
|
|
32
31
|
raise ValueError("max_rate must be > 0")
|
|
@@ -40,76 +39,79 @@ class RateLimiter:
|
|
|
40
39
|
self._cond = asyncio.Condition()
|
|
41
40
|
self._priority_waiters = 0
|
|
42
41
|
|
|
43
|
-
def
|
|
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
|
-
"""
|
|
42
|
+
def _refill(self) -> None:
|
|
43
|
+
"""Refill tokens based on elapsed time."""
|
|
50
44
|
now = time.monotonic()
|
|
51
|
-
|
|
52
|
-
if
|
|
45
|
+
elapsed = now - self._updated_at
|
|
46
|
+
if elapsed > 0:
|
|
47
|
+
rate = self._max_rate / self._period
|
|
48
|
+
self._tokens = min(float(self._max_rate), self._tokens + elapsed * rate)
|
|
49
|
+
self._updated_at = now
|
|
50
|
+
|
|
51
|
+
def _seconds_to_token(self) -> float:
|
|
52
|
+
"""Calculate seconds until next token is available."""
|
|
53
|
+
if self._tokens >= 1.0:
|
|
53
54
|
return 0.0
|
|
54
|
-
|
|
55
|
+
missing = 1.0 - self._tokens
|
|
56
|
+
rate = self._max_rate / self._period
|
|
57
|
+
return missing / rate
|
|
55
58
|
|
|
56
59
|
async def acquire(self, priority: bool = False) -> None:
|
|
57
60
|
"""
|
|
58
|
-
Acquire a single token, waiting
|
|
61
|
+
Acquire a single token, waiting if necessary.
|
|
59
62
|
|
|
60
|
-
Priority
|
|
61
|
-
|
|
63
|
+
Priority requests bypass non-priority waiters when tokens
|
|
64
|
+
become available.
|
|
62
65
|
|
|
63
|
-
:param priority: Whether to acquire
|
|
66
|
+
:param priority: Whether to acquire with priority.
|
|
64
67
|
"""
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
self._priority_waiters += 1
|
|
68
|
+
async with self._cond:
|
|
69
|
+
is_waiting = False
|
|
68
70
|
|
|
69
71
|
try:
|
|
70
|
-
|
|
72
|
+
while True:
|
|
73
|
+
self._refill()
|
|
74
|
+
|
|
75
|
+
# Check if token available
|
|
76
|
+
if self._tokens >= 1.0:
|
|
77
|
+
# Priority always takes; non-priority only if no priority waiting
|
|
78
|
+
if priority or self._priority_waiters == 0:
|
|
79
|
+
self._tokens -= 1.0
|
|
80
|
+
self._cond.notify_all()
|
|
81
|
+
return
|
|
82
|
+
|
|
83
|
+
# Register as priority waiter only when actually waiting
|
|
84
|
+
if priority and not is_waiting:
|
|
85
|
+
self._priority_waiters += 1
|
|
86
|
+
is_waiting = True
|
|
87
|
+
|
|
88
|
+
# Wait for token
|
|
89
|
+
wait_time = self._seconds_to_token()
|
|
90
|
+
if wait_time > 0:
|
|
91
|
+
try:
|
|
92
|
+
await asyncio.wait_for(self._cond.wait(), timeout=wait_time)
|
|
93
|
+
except asyncio.TimeoutError:
|
|
94
|
+
pass # Expected — token should be ready now
|
|
95
|
+
else:
|
|
96
|
+
# Token available but blocked by priority — wait for notify
|
|
97
|
+
await self._cond.wait()
|
|
98
|
+
|
|
71
99
|
finally:
|
|
72
|
-
|
|
100
|
+
if is_waiting:
|
|
73
101
|
self._priority_waiters -= 1
|
|
74
102
|
self._cond.notify_all()
|
|
75
|
-
return
|
|
76
103
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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()
|
|
104
|
+
def when_ready(self) -> float:
|
|
105
|
+
"""
|
|
106
|
+
Calculate delay until the next token becomes available.
|
|
95
107
|
|
|
96
|
-
|
|
108
|
+
:return: Seconds to wait, or 0 if ready immediately.
|
|
109
|
+
"""
|
|
110
|
+
now = time.monotonic()
|
|
97
111
|
elapsed = now - self._updated_at
|
|
98
|
-
if elapsed <= 0.0:
|
|
99
|
-
return self._tokens
|
|
100
|
-
|
|
101
112
|
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
|
|
113
|
+
tokens = min(float(self._max_rate), self._tokens + elapsed * rate)
|
|
108
114
|
|
|
109
|
-
|
|
110
|
-
missing = 1.0 - tokens
|
|
111
|
-
if missing <= 0.0:
|
|
115
|
+
if tokens >= 1.0:
|
|
112
116
|
return 0.0
|
|
113
|
-
|
|
114
|
-
rate = self._max_rate / self._period
|
|
115
|
-
return missing / rate
|
|
117
|
+
return (1.0 - tokens) / rate
|
tonutils/clients/protocol.py
CHANGED
|
@@ -55,12 +55,12 @@ class ClientProtocol(t.Protocol):
|
|
|
55
55
|
) -> ContractStateInfo:
|
|
56
56
|
"""Fetch basic contract state information."""
|
|
57
57
|
|
|
58
|
-
async def
|
|
58
|
+
async def get_transactions(
|
|
59
59
|
self,
|
|
60
60
|
address: AddressLike,
|
|
61
61
|
limit: int = 100,
|
|
62
62
|
from_lt: t.Optional[int] = None,
|
|
63
|
-
to_lt: int =
|
|
63
|
+
to_lt: t.Optional[int] = None,
|
|
64
64
|
) -> t.List[Transaction]:
|
|
65
65
|
"""Fetch contract transactions."""
|
|
66
66
|
|
|
@@ -29,7 +29,7 @@ from tonutils.types import (
|
|
|
29
29
|
WorkchainID,
|
|
30
30
|
DEFAULT_SENDMODE,
|
|
31
31
|
)
|
|
32
|
-
from tonutils.utils import
|
|
32
|
+
from tonutils.utils import to_cell
|
|
33
33
|
|
|
34
34
|
_D = t.TypeVar("_D", bound=BaseWalletData)
|
|
35
35
|
_C = t.TypeVar("_C", bound=BaseWalletConfig)
|
|
@@ -325,7 +325,7 @@ class BaseWallet(BaseContract, WalletProtocol[_D, _C, _P], abc.ABC):
|
|
|
325
325
|
"""
|
|
326
326
|
Build and send a transfer to a single destination.
|
|
327
327
|
|
|
328
|
-
:param destination: Recipient address
|
|
328
|
+
:param destination: Recipient address
|
|
329
329
|
:param amount: Amount to send in nanotons
|
|
330
330
|
:param body: Optional message body (Cell or text comment)
|
|
331
331
|
:param state_init: Optional StateInit for contract deployment
|
|
@@ -334,7 +334,6 @@ class BaseWallet(BaseContract, WalletProtocol[_D, _C, _P], abc.ABC):
|
|
|
334
334
|
:param params: Optional transaction parameters
|
|
335
335
|
:return: Signed external message that was sent
|
|
336
336
|
"""
|
|
337
|
-
destination = await resolve_wallet_address(self.client, destination)
|
|
338
337
|
message = TONTransferBuilder(
|
|
339
338
|
destination=destination,
|
|
340
339
|
amount=amount,
|
|
@@ -23,7 +23,6 @@ from tonutils.utils import (
|
|
|
23
23
|
cell_to_b64,
|
|
24
24
|
normalize_hash,
|
|
25
25
|
to_nano,
|
|
26
|
-
resolve_wallet_address,
|
|
27
26
|
)
|
|
28
27
|
|
|
29
28
|
|
|
@@ -191,7 +190,7 @@ class TONTransferBuilder(BaseMessageBuilder):
|
|
|
191
190
|
"""
|
|
192
191
|
Initialize TON transfer builder.
|
|
193
192
|
|
|
194
|
-
:param destination: Recipient address
|
|
193
|
+
:param destination: Recipient address
|
|
195
194
|
:param amount: Amount to send in nanotons
|
|
196
195
|
:param body: Optional message body (Cell or text comment string)
|
|
197
196
|
:param state_init: Optional StateInit for contract deployment
|
|
@@ -214,11 +213,10 @@ class TONTransferBuilder(BaseMessageBuilder):
|
|
|
214
213
|
:param wallet: Wallet instance to build message for
|
|
215
214
|
:return: WalletMessage with TON transfer
|
|
216
215
|
"""
|
|
217
|
-
destination = await resolve_wallet_address(wallet.client, self.destination)
|
|
218
216
|
return WalletMessage(
|
|
219
217
|
send_mode=self.send_mode,
|
|
220
218
|
message=InternalMessage(
|
|
221
|
-
dest=destination,
|
|
219
|
+
dest=self.destination,
|
|
222
220
|
value=self.amount,
|
|
223
221
|
body=self.body,
|
|
224
222
|
state_init=self.state_init,
|
|
@@ -277,9 +275,8 @@ class NFTTransferBuilder(BaseMessageBuilder):
|
|
|
277
275
|
:param wallet: Wallet instance to build message for
|
|
278
276
|
:return: WalletMessage with NFT transfer
|
|
279
277
|
"""
|
|
280
|
-
destination = await resolve_wallet_address(wallet.client, self.destination)
|
|
281
278
|
body = NFTTransferBody(
|
|
282
|
-
destination=destination,
|
|
279
|
+
destination=self.destination,
|
|
283
280
|
response_address=self.response_address or wallet.address,
|
|
284
281
|
custom_payload=self.custom_payload,
|
|
285
282
|
forward_payload=self.forward_payload,
|
|
@@ -358,7 +355,6 @@ class JettonTransferBuilder(BaseMessageBuilder):
|
|
|
358
355
|
:param wallet: Wallet instance to build message for
|
|
359
356
|
:return: WalletMessage with jetton transfer
|
|
360
357
|
"""
|
|
361
|
-
destination = await resolve_wallet_address(wallet.client, self.destination)
|
|
362
358
|
jetton_wallet_address = self.jetton_wallet_address
|
|
363
359
|
if self.jetton_wallet_address is None:
|
|
364
360
|
jetton_wallet_address = await get_wallet_address_get_method(
|
|
@@ -367,7 +363,7 @@ class JettonTransferBuilder(BaseMessageBuilder):
|
|
|
367
363
|
owner_address=wallet.address,
|
|
368
364
|
)
|
|
369
365
|
body = JettonTransferBody(
|
|
370
|
-
destination=destination,
|
|
366
|
+
destination=self.destination,
|
|
371
367
|
jetton_amount=self.jetton_amount,
|
|
372
368
|
response_address=self.response_address or wallet.address,
|
|
373
369
|
custom_payload=self.custom_payload,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|