tonutils 2.0.1b3__py3-none-any.whl → 2.0.1b4__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.
@@ -0,0 +1,151 @@
1
+ from __future__ import annotations
2
+
3
+ import typing as t
4
+
5
+ from pytoniq_core import Address
6
+
7
+ from tonutils.tools.block_scanner import TransactionEvent
8
+ from tonutils.tools.block_scanner.annotations import TEvent
9
+ from tonutils.types import AddressLike
10
+
11
+
12
+ class Where(t.Generic[TEvent]):
13
+ __slots__ = ()
14
+
15
+ def __call__(self, event: TEvent) -> bool:
16
+ raise NotImplementedError
17
+
18
+ def __and__(self, other: Where[TEvent]) -> _And[TEvent]:
19
+ return _And(self, other)
20
+
21
+ def __or__(self, other: Where[TEvent]) -> _Or[TEvent]:
22
+ return _Or(self, other)
23
+
24
+ def __invert__(self) -> _Not[TEvent]:
25
+ return _Not(self)
26
+
27
+
28
+ class _And(Where[TEvent]):
29
+ __slots__ = ("_a", "_b")
30
+
31
+ def __init__(self, a: Where[TEvent], b: Where[TEvent]) -> None:
32
+ self._a = a
33
+ self._b = b
34
+
35
+ def __call__(self, event: TEvent) -> bool:
36
+ return self._a(event) and self._b(event)
37
+
38
+
39
+ class _Or(Where[TEvent]):
40
+ __slots__ = ("_a", "_b")
41
+
42
+ def __init__(self, a: Where[TEvent], b: Where[TEvent]) -> None:
43
+ self._a = a
44
+ self._b = b
45
+
46
+ def __call__(self, event: TEvent) -> bool:
47
+ return self._a(event) or self._b(event)
48
+
49
+
50
+ class _Not(Where[TEvent]):
51
+ __slots__ = ("_f",)
52
+
53
+ def __init__(self, f: Where[TEvent]) -> None:
54
+ self._f = f
55
+
56
+ def __call__(self, event: TEvent) -> bool:
57
+ return not self._f(event)
58
+
59
+
60
+ class _Opcode(Where[TransactionEvent]):
61
+ __slots__ = ("_ops",)
62
+
63
+ def __init__(self, *ops: int) -> None:
64
+ self._ops = frozenset(ops)
65
+
66
+ def __call__(self, event: TransactionEvent) -> bool:
67
+ msg = event.transaction.in_msg
68
+ if msg is None or msg.body is None:
69
+ return False
70
+
71
+ if len(msg.body.bits) < 32:
72
+ return False
73
+
74
+ op = msg.body.begin_parse().load_uint(32)
75
+ return op in self._ops
76
+
77
+
78
+ class _Comment(Where[TransactionEvent]):
79
+ __slots__ = ("_texts", "_any")
80
+
81
+ def __init__(self, *texts: str) -> None:
82
+ self._texts = frozenset(texts)
83
+ self._any = len(texts) == 0
84
+
85
+ def __call__(self, event: TransactionEvent) -> bool:
86
+ msg = event.transaction.in_msg
87
+ if msg is None or msg.body is None:
88
+ return False
89
+
90
+ body = msg.body.begin_parse()
91
+ if len(body.bits) < 32:
92
+ return False
93
+
94
+ op = body.load_uint(32)
95
+ if op != 0:
96
+ return False
97
+
98
+ if self._any:
99
+ return True
100
+ try:
101
+ text = body.load_snake_string()
102
+ except (Exception,):
103
+ return False
104
+
105
+ return text in self._texts
106
+
107
+
108
+ class _Sender(Where[TransactionEvent]):
109
+ __slots__ = ("_addrs",)
110
+
111
+ def __init__(self, *addrs: AddressLike) -> None:
112
+ self._addrs = frozenset(Address(a) if isinstance(a, str) else a for a in addrs)
113
+
114
+ def __call__(self, event: TransactionEvent) -> bool:
115
+ msg = event.transaction.in_msg
116
+ if msg is None:
117
+ return False
118
+
119
+ src = msg.info.src
120
+ return src is not None and src in self._addrs
121
+
122
+
123
+ class _Destination(Where[TransactionEvent]):
124
+ __slots__ = ("_addrs",)
125
+
126
+ def __init__(self, *addrs: AddressLike) -> None:
127
+ self._addrs = frozenset(Address(a) if isinstance(a, str) else a for a in addrs)
128
+
129
+ def __call__(self, event: TransactionEvent) -> bool:
130
+ msg = event.transaction.in_msg
131
+ if msg is None:
132
+ return False
133
+
134
+ dest = msg.info.dest
135
+ return dest is not None and dest in self._addrs
136
+
137
+
138
+ def opcode(*ops: int) -> Where[TransactionEvent]:
139
+ return _Opcode(*ops)
140
+
141
+
142
+ def comment(*texts: str) -> Where[TransactionEvent]:
143
+ return _Comment(*texts)
144
+
145
+
146
+ def sender(*addresses: AddressLike) -> Where[TransactionEvent]:
147
+ return _Sender(*addresses)
148
+
149
+
150
+ def destination(*addresses: AddressLike) -> Where[TransactionEvent]:
151
+ return _Destination(*addresses)
@@ -0,0 +1,3 @@
1
+ from .monitor import LiteServerMonitor
2
+
3
+ __all__ = ["LiteServerMonitor"]
@@ -0,0 +1,157 @@
1
+ import sys
2
+ import typing as t
3
+ from collections import deque
4
+ from datetime import datetime
5
+
6
+ from tonutils.tools.status_monitor.models import BlockInfo, LiteServerStatus
7
+
8
+ _ENTER_ALT_SCREEN = "\033[?1049h"
9
+ _EXIT_ALT_SCREEN = "\033[?1049l"
10
+ _HIDE_CURSOR = "\033[?25l"
11
+ _SHOW_CURSOR = "\033[?25h"
12
+ _MOVE_HOME = "\033[H"
13
+ _CLEAR_SCREEN = "\033[2J"
14
+ _CLEAR_LINE = "\033[K"
15
+
16
+
17
+ class Console:
18
+ HEADERS = [
19
+ "LS",
20
+ "HOST",
21
+ "PORT",
22
+ "Version",
23
+ "Time",
24
+ "Ping",
25
+ "Connect RTT",
26
+ "Request RTT",
27
+ "Last MC Block",
28
+ "Last BC Block",
29
+ "Archive From",
30
+ ]
31
+ WIDTHS = [2, 15, 5, 7, 19, 7, 11, 11, 16, 16, 12]
32
+
33
+ TABLE_TITLE = "Lite Server Status"
34
+ ERROR_TITLE = "Error Log"
35
+ MAX_ERROR_LOGS = 10
36
+
37
+ def __init__(self) -> None:
38
+ self._index_width = 2
39
+ self._is_tty = sys.stdout.isatty()
40
+ self._error_log: deque[str] = deque(maxlen=self.MAX_ERROR_LOGS)
41
+ self._prev_errors: t.Dict[int, t.Optional[str]] = {}
42
+
43
+ def enter(self) -> None:
44
+ if self._is_tty:
45
+ sys.stdout.write(_ENTER_ALT_SCREEN + _HIDE_CURSOR + _CLEAR_SCREEN)
46
+ sys.stdout.flush()
47
+
48
+ def exit(self) -> None:
49
+ if self._is_tty:
50
+ sys.stdout.write(_SHOW_CURSOR + _EXIT_ALT_SCREEN)
51
+ sys.stdout.flush()
52
+
53
+ def render(self, statuses: t.List[LiteServerStatus]) -> None:
54
+ self._update_state(statuses)
55
+ self._home()
56
+ self._draw(statuses)
57
+
58
+ def _home(self) -> None:
59
+ if self._is_tty:
60
+ sys.stdout.write(_MOVE_HOME)
61
+ sys.stdout.flush()
62
+
63
+ def _update_state(self, statuses: t.List[LiteServerStatus]) -> None:
64
+ self._update_index_width(statuses)
65
+ self._update_error_log(statuses)
66
+
67
+ def _update_index_width(self, statuses: t.List[LiteServerStatus]) -> None:
68
+ if statuses:
69
+ self._index_width = max(2, len(str(len(statuses) - 1)))
70
+
71
+ def _update_error_log(self, statuses: t.List[LiteServerStatus]) -> None:
72
+ now = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
73
+ for status in statuses:
74
+ prev = self._prev_errors.get(status.server.index)
75
+ if status.last_error and status.last_error != prev:
76
+ idx = str(status.server.index).rjust(self._index_width)
77
+ self._error_log.appendleft(f" {now} [LS {idx}]: {status.last_error}")
78
+ self._prev_errors[status.server.index] = status.last_error
79
+
80
+ def _get_table_width(self) -> int:
81
+ return sum(self.WIDTHS) + (len(self.WIDTHS) - 1) * 3
82
+
83
+ def _draw(self, statuses: t.List[LiteServerStatus]) -> None:
84
+ table_width = self._get_table_width()
85
+ padding = (table_width - len(self.TABLE_TITLE)) // 2
86
+
87
+ lines = [
88
+ "═" * table_width,
89
+ " " * padding + self.TABLE_TITLE,
90
+ "═" * table_width,
91
+ self._format_header(),
92
+ self._format_separator(),
93
+ ]
94
+
95
+ for status in statuses:
96
+ lines.append(self._format_row(status))
97
+
98
+ lines.append("")
99
+
100
+ if self._error_log:
101
+ lines.append("─" * table_width)
102
+ lines.append(f" {self.ERROR_TITLE}:")
103
+ lines.extend(self._error_log)
104
+
105
+ output = (_CLEAR_LINE + "\n").join(lines) + _CLEAR_LINE
106
+
107
+ output += "\n" + _CLEAR_LINE
108
+ sys.stdout.write(output)
109
+ sys.stdout.flush()
110
+
111
+ def _format_header(self) -> str:
112
+ return " │ ".join(h.ljust(w) for h, w in zip(self.HEADERS, self.WIDTHS))
113
+
114
+ def _format_separator(self) -> str:
115
+ return "─┼─".join("─" * w for w in self.WIDTHS)
116
+
117
+ def _format_row(self, status: LiteServerStatus) -> str:
118
+ cells = [
119
+ str(status.server.index),
120
+ status.server.host,
121
+ str(status.server.port),
122
+ self._fmt_int(status.version),
123
+ self._fmt_datetime(status.time),
124
+ self._fmt_ms(status.ping_ms),
125
+ self._fmt_ms(status.connect_ms),
126
+ self._fmt_ms(status.request_ms),
127
+ self._fmt_block(status.last_mc_block),
128
+ self._fmt_block(status.last_bc_block),
129
+ self._fmt_date(status.archive_from),
130
+ ]
131
+ return " │ ".join(c.ljust(w) for c, w in zip(cells, self.WIDTHS))
132
+
133
+ @staticmethod
134
+ def _fmt_int(value: t.Optional[int]) -> str:
135
+ return str(value) if value is not None else "-"
136
+
137
+ @staticmethod
138
+ def _fmt_ms(value: t.Optional[int]) -> str:
139
+ return f"{value}ms" if value is not None else "-"
140
+
141
+ @staticmethod
142
+ def _fmt_block(block: t.Optional[BlockInfo]) -> str:
143
+ if block is None:
144
+ return "-"
145
+ return f"{block.seqno} / {block.txs_count}"
146
+
147
+ @staticmethod
148
+ def _fmt_date(ts: t.Optional[int]) -> str:
149
+ if ts is None:
150
+ return "-"
151
+ return datetime.fromtimestamp(ts).strftime("%Y-%m-%d")
152
+
153
+ @staticmethod
154
+ def _fmt_datetime(ts: t.Optional[int]) -> str:
155
+ if ts is None:
156
+ return "-"
157
+ return datetime.fromtimestamp(ts).strftime("%Y-%m-%d %H:%M:%S")
@@ -0,0 +1,27 @@
1
+ import typing as t
2
+
3
+ from pydantic import BaseModel
4
+
5
+
6
+ class LiteServer(BaseModel):
7
+ index: int
8
+ host: str
9
+ port: int
10
+
11
+
12
+ class BlockInfo(BaseModel):
13
+ seqno: int
14
+ txs_count: int
15
+
16
+
17
+ class LiteServerStatus(BaseModel):
18
+ server: LiteServer
19
+ version: t.Optional[int] = None
20
+ time: t.Optional[int] = None
21
+ ping_ms: t.Optional[int] = None
22
+ connect_ms: t.Optional[int] = None
23
+ request_ms: t.Optional[int] = None
24
+ last_mc_block: t.Optional[BlockInfo] = None
25
+ last_bc_block: t.Optional[BlockInfo] = None
26
+ archive_from: t.Optional[int] = None
27
+ last_error: t.Optional[str] = None
@@ -0,0 +1,295 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import time
5
+ import typing as t
6
+
7
+ from tonutils.clients import LiteClient
8
+ from tonutils.clients.adnl.provider.models import GlobalConfig
9
+ from tonutils.tools.status_monitor.console import Console
10
+ from tonutils.tools.status_monitor.models import (
11
+ BlockInfo,
12
+ LiteServerStatus,
13
+ LiteServer,
14
+ )
15
+ from tonutils.types import (
16
+ NetworkGlobalID,
17
+ WorkchainID,
18
+ MAINNET_GENESIS_UTIME,
19
+ MASTERCHAIN_SHARD,
20
+ )
21
+
22
+
23
+ class LiteServerMonitor:
24
+ RENDER_INTERVAL = 0.1
25
+ RECONNECT_INTERVAL = 30.0
26
+
27
+ FAST_UPDATE_INTERVAL = 0.3
28
+ MEDIUM_UPDATE_INTERVAL = 3.0
29
+ SLOW_UPDATE_INTERVAL = 10.0
30
+
31
+ def __init__(self, clients: t.List[LiteClient]) -> None:
32
+ self._clients = clients
33
+ self._console = Console()
34
+
35
+ self._archive_cache: t.Dict[int, int] = {}
36
+ self._statuses: t.Dict[int, LiteServerStatus] = {}
37
+ self._last_connect: t.Dict[int, float] = {}
38
+
39
+ self._tasks: t.List[asyncio.Task[None]] = []
40
+ self._stop = asyncio.Event()
41
+
42
+ self._locks: t.Dict[int, asyncio.Lock] = {}
43
+
44
+ @classmethod
45
+ def from_config(
46
+ cls,
47
+ config: GlobalConfig,
48
+ network: NetworkGlobalID,
49
+ rps_limit: t.Optional[int] = 100,
50
+ ) -> LiteServerMonitor:
51
+ return cls(
52
+ [
53
+ LiteClient(
54
+ network=network,
55
+ ip=server.host,
56
+ port=server.port,
57
+ public_key=server.id,
58
+ rps_limit=rps_limit,
59
+ )
60
+ for server in config.liteservers
61
+ ]
62
+ )
63
+
64
+ @property
65
+ def statuses(self) -> t.List[LiteServerStatus]:
66
+ return list(self._statuses.values())
67
+
68
+ async def run(self) -> None:
69
+ self._console.enter()
70
+ self._init_statuses()
71
+ self._start_update_loops()
72
+
73
+ try:
74
+ while not self._stop.is_set():
75
+ self._console.render(self.statuses)
76
+ await self._sleep(self.RENDER_INTERVAL)
77
+ finally:
78
+ self._console.exit()
79
+
80
+ async def stop(self) -> None:
81
+ if self._stop.is_set():
82
+ return
83
+ self._stop.set()
84
+
85
+ for task in self._tasks:
86
+ task.cancel()
87
+ await asyncio.gather(*self._tasks, return_exceptions=True)
88
+
89
+ close_tasks = [client.close() for client in self._clients]
90
+ await asyncio.gather(*close_tasks, return_exceptions=True)
91
+
92
+ def _init_statuses(self) -> None:
93
+ for index, client in enumerate(self._clients):
94
+ server = LiteServer(
95
+ index=index,
96
+ host=client.provider.node.host,
97
+ port=client.provider.node.port,
98
+ )
99
+ self._statuses[index] = LiteServerStatus(server=server)
100
+ self._locks[index] = asyncio.Lock()
101
+
102
+ def _start_update_loops(self) -> None:
103
+ if self._tasks:
104
+ return
105
+
106
+ for index, client in enumerate(self._clients):
107
+ fast = self._fast_update_loop(index, client)
108
+ self._tasks.append(asyncio.create_task(fast))
109
+
110
+ medium = self._medium_update_loop(index, client)
111
+ self._tasks.append(asyncio.create_task(medium))
112
+
113
+ slow = self._slow_update_loop(index, client)
114
+ self._tasks.append(asyncio.create_task(slow))
115
+
116
+ async def _ensure_connected(self, index: int, client: LiteClient) -> bool:
117
+ if client.provider.is_connected:
118
+ return True
119
+
120
+ now = time.monotonic()
121
+ last_attempt = self._last_connect.get(index, 0.0)
122
+ if now - last_attempt < self.RECONNECT_INTERVAL:
123
+ return False
124
+
125
+ self._last_connect[index] = now
126
+ await self._connect(index, client)
127
+ return client.provider.is_connected
128
+
129
+ async def _fast_update_loop(self, index: int, client: LiteClient) -> None:
130
+ while not self._stop.is_set():
131
+ if not await self._ensure_connected(index, client):
132
+ await self._sleep(1.0)
133
+ continue
134
+
135
+ await asyncio.gather(
136
+ self._update_time(index, client),
137
+ self._update_last_blocks(index, client),
138
+ return_exceptions=True,
139
+ )
140
+ await self._sleep(self.FAST_UPDATE_INTERVAL)
141
+
142
+ async def _medium_update_loop(self, index: int, client: LiteClient) -> None:
143
+ while not self._stop.is_set():
144
+ if not client.is_connected:
145
+ await self._sleep(1.0)
146
+ continue
147
+
148
+ await asyncio.gather(
149
+ self._update_ping_ms(index, client),
150
+ self._update_request_ms(index, client),
151
+ return_exceptions=True,
152
+ )
153
+ await self._sleep(self.MEDIUM_UPDATE_INTERVAL)
154
+
155
+ async def _slow_update_loop(self, index: int, client: LiteClient) -> None:
156
+ while not self._stop.is_set():
157
+ if not client.is_connected:
158
+ await self._sleep(1.0)
159
+ continue
160
+
161
+ await asyncio.gather(
162
+ self._update_version(index, client),
163
+ self._update_archive_from(index, client),
164
+ return_exceptions=True,
165
+ )
166
+ await self._sleep(self.SLOW_UPDATE_INTERVAL)
167
+
168
+ async def _sleep(self, seconds: float) -> None:
169
+ try:
170
+ await asyncio.wait_for(self._stop.wait(), timeout=seconds)
171
+ except asyncio.TimeoutError:
172
+ pass
173
+
174
+ async def _set_status(self, index: int, **kwargs: t.Any) -> None:
175
+ async with self._locks[index]:
176
+ current = self._statuses[index]
177
+ self._statuses[index] = current.model_copy(update=kwargs)
178
+
179
+ async def _connect(self, index: int, client: LiteClient) -> None:
180
+ try:
181
+ start = time.perf_counter()
182
+ await client.connect()
183
+ connect_ms = int((time.perf_counter() - start) * 1000)
184
+ await self._set_status(index, connect_ms=connect_ms, last_error=None)
185
+ except Exception as e:
186
+ await self._set_status(index, last_error=str(e))
187
+
188
+ async def _update_version(self, index: int, client: LiteClient) -> None:
189
+ try:
190
+ version = await client.get_version()
191
+ await self._set_status(index, version=version)
192
+ except Exception as e:
193
+ await self._set_status(index, last_error=str(e))
194
+
195
+ async def _update_time(self, index: int, client: LiteClient) -> None:
196
+ try:
197
+ server_time = await client.get_time()
198
+ await self._set_status(index, time=server_time)
199
+ except Exception as e:
200
+ await self._set_status(index, last_error=str(e))
201
+
202
+ async def _update_ping_ms(self, index: int, client: LiteClient) -> None:
203
+ try:
204
+ ping_ms = client.provider.last_ping_ms
205
+ if ping_ms is not None:
206
+ await self._set_status(index, ping_ms=ping_ms)
207
+ except Exception as e:
208
+ await self._set_status(index, last_error=str(e))
209
+
210
+ async def _update_request_ms(self, index: int, client: LiteClient) -> None:
211
+ try:
212
+ start = time.perf_counter()
213
+ await client.get_masterchain_info()
214
+ request_ms = int((time.perf_counter() - start) * 1000)
215
+ await self._set_status(index, request_ms=request_ms)
216
+ except Exception as e:
217
+ await self._set_status(index, last_error=str(e))
218
+
219
+ async def _update_last_blocks(self, index: int, client: LiteClient) -> None:
220
+ try:
221
+ mc_block = client.provider.last_mc_block
222
+ if mc_block is None:
223
+ return
224
+
225
+ mc_txs, shards = await asyncio.gather(
226
+ client.get_block_transactions_ext(mc_block),
227
+ client.get_all_shards_info(mc_block),
228
+ )
229
+ last_mc_block = BlockInfo(seqno=mc_block.seqno, txs_count=len(mc_txs))
230
+
231
+ if shards:
232
+ bc_block = max(shards, key=lambda b: b.seqno)
233
+ bc_txs = await client.get_block_transactions_ext(bc_block)
234
+ last_bc_block = BlockInfo(seqno=bc_block.seqno, txs_count=len(bc_txs))
235
+ await self._set_status(
236
+ index,
237
+ last_mc_block=last_mc_block,
238
+ last_bc_block=last_bc_block,
239
+ )
240
+ else:
241
+ await self._set_status(index, last_mc_block=last_mc_block)
242
+
243
+ except Exception as e:
244
+ await self._set_status(index, last_error=str(e))
245
+
246
+ async def _update_archive_from(self, index: int, client: LiteClient) -> None:
247
+ try:
248
+ now = int(time.time())
249
+ result = await self._find_archive_depth(
250
+ client, now, self._archive_cache.get(index)
251
+ )
252
+ self._archive_cache[index] = result
253
+ await self._set_status(index, archive_from=result)
254
+ except Exception as e:
255
+ await self._set_status(index, last_error=str(e))
256
+
257
+ @staticmethod
258
+ async def _find_archive_depth(
259
+ client: LiteClient,
260
+ now: int,
261
+ cached: t.Optional[int] = None,
262
+ ) -> int:
263
+ seconds_per_day = 86400
264
+ seconds_diff = now - MAINNET_GENESIS_UTIME
265
+ right = seconds_diff // seconds_per_day
266
+
267
+ if cached is not None:
268
+ cached_days = (now - cached) // seconds_per_day
269
+ left = cached_days
270
+ best_days = cached_days
271
+ else:
272
+ left = 0
273
+ best_days = 0
274
+
275
+ async def probe(days: int) -> bool:
276
+ utime = now - days * seconds_per_day
277
+ try:
278
+ await client.provider.lookup_block(
279
+ workchain=WorkchainID.MASTERCHAIN,
280
+ shard=MASTERCHAIN_SHARD,
281
+ utime=utime,
282
+ )
283
+ return True
284
+ except (Exception,):
285
+ return False
286
+
287
+ while left <= right:
288
+ mid = (left + right) // 2
289
+ if await probe(mid):
290
+ best_days = mid
291
+ left = mid + 1
292
+ else:
293
+ right = mid - 1
294
+
295
+ return now - best_days * seconds_per_day
tonutils/types.py CHANGED
@@ -17,10 +17,6 @@ __all__ = [
17
17
  "ClientType",
18
18
  "ContractState",
19
19
  "ContractStateInfo",
20
- "DEFAULT_ADNL_RETRY_POLICY",
21
- "DEFAULT_HTTP_RETRY_POLICY",
22
- "DEFAULT_SENDMODE",
23
- "DEFAULT_SUBWALLET_ID",
24
20
  "DNSCategory",
25
21
  "DNSPrefix",
26
22
  "MetadataPrefix",
@@ -34,6 +30,12 @@ __all__ = [
34
30
  "StackItems",
35
31
  "StackTag",
36
32
  "WorkchainID",
33
+ "DEFAULT_ADNL_RETRY_POLICY",
34
+ "DEFAULT_HTTP_RETRY_POLICY",
35
+ "DEFAULT_SENDMODE",
36
+ "DEFAULT_SUBWALLET_ID",
37
+ "MAINNET_GENESIS_UTIME",
38
+ "MASTERCHAIN_SHARD",
37
39
  ]
38
40
 
39
41
  from tonutils.exceptions import CDN_CHALLENGE_MARKERS
@@ -495,3 +497,9 @@ DEFAULT_SUBWALLET_ID = 698983191
495
497
 
496
498
  DEFAULT_SENDMODE = SendMode.PAY_GAS_SEPARATELY | SendMode.IGNORE_ERRORS
497
499
  """Default send mode: pay fees separately and ignore errors."""
500
+
501
+ MASTERCHAIN_SHARD = -9223372036854775808
502
+ """Shard identifier for the masterchain (-2^63)."""
503
+
504
+ MAINNET_GENESIS_UTIME = 1573822385
505
+ """Unix timestamp of the TON mainnet genesis block (November 15, 2019)."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tonutils
3
- Version: 2.0.1b3
3
+ Version: 2.0.1b4
4
4
  Summary: Tonutils is a high-level, object-oriented Python library designed to facilitate seamless interactions with the TON blockchain.
5
5
  Author: nessshon
6
6
  Maintainer: nessshon
@@ -26,11 +26,8 @@ Requires-Python: <3.15,>=3.10
26
26
  Description-Content-Type: text/markdown
27
27
  License-File: LICENSE
28
28
  Requires-Dist: aiohttp>=3.7.0
29
- Requires-Dist: pycryptodomex~=3.23.0
30
29
  Requires-Dist: pydantic<3.0,>=2.0
31
- Requires-Dist: pynacl~=1.6.0
32
- Requires-Dist: pytoniq-core~=0.1.45
33
- Requires-Dist: requests>=2.31.0
30
+ Requires-Dist: pytoniq-core~=0.1.46
34
31
  Dynamic: license-file
35
32
 
36
33
  # 📦 Tonutils 2.0 [BETA]