tonutils 2.0.1b7__py3-none-any.whl → 2.0.1b8__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 +8 -13
- tonutils/cli.py +1 -1
- tonutils/clients/adnl/provider/config.py +7 -20
- tonutils/contracts/__init__.py +3 -0
- tonutils/contracts/telegram/tlb.py +1 -1
- tonutils/contracts/wallet/__init__.py +4 -0
- tonutils/contracts/wallet/tlb.py +18 -16
- tonutils/contracts/wallet/versions/v5.py +4 -4
- tonutils/exceptions.py +33 -106
- tonutils/types.py +22 -8
- tonutils/utils.py +47 -7
- {tonutils-2.0.1b7.dist-info → tonutils-2.0.1b8.dist-info}/METADATA +1 -3
- {tonutils-2.0.1b7.dist-info → tonutils-2.0.1b8.dist-info}/RECORD +17 -24
- {tonutils-2.0.1b7.dist-info → tonutils-2.0.1b8.dist-info}/WHEEL +1 -1
- tonutils/__meta__.py +0 -1
- tonutils/tonconnect/__init__.py +0 -0
- 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-2.0.1b7.dist-info → tonutils-2.0.1b8.dist-info}/entry_points.txt +0 -0
- {tonutils-2.0.1b7.dist-info → tonutils-2.0.1b8.dist-info}/licenses/LICENSE +0 -0
- {tonutils-2.0.1b7.dist-info → tonutils-2.0.1b8.dist-info}/top_level.txt +0 -0
tonutils/__init__.py
CHANGED
|
@@ -1,17 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
types,
|
|
6
|
-
utils,
|
|
7
|
-
)
|
|
8
|
-
from .__meta__ import __version__
|
|
1
|
+
# Copyright (c) 2024 Shon Ness
|
|
2
|
+
#
|
|
3
|
+
# This source code is licensed under the MIT License found in the
|
|
4
|
+
# LICENSE file in the root directory of this source tree.
|
|
9
5
|
|
|
10
6
|
__all__ = [
|
|
7
|
+
"__uri__",
|
|
11
8
|
"__version__",
|
|
12
|
-
"clients",
|
|
13
|
-
"contracts",
|
|
14
|
-
"exceptions",
|
|
15
|
-
"types",
|
|
16
|
-
"utils",
|
|
17
9
|
]
|
|
10
|
+
|
|
11
|
+
__version__ = "2.0.1b8"
|
|
12
|
+
__uri__ = "https://github.com/nessshon/tonutils"
|
tonutils/cli.py
CHANGED
|
@@ -1,33 +1,20 @@
|
|
|
1
|
-
import json
|
|
2
|
-
import urllib.request
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
from urllib.error import HTTPError, URLError
|
|
5
|
-
|
|
6
1
|
from pydantic import ValidationError
|
|
7
2
|
|
|
8
3
|
from tonutils.clients.adnl.provider.models import GlobalConfig
|
|
4
|
+
from tonutils.utils import load_json
|
|
9
5
|
|
|
10
6
|
|
|
11
7
|
def load_global_config(source: str) -> GlobalConfig:
|
|
8
|
+
"""
|
|
9
|
+
Fetch global configuration from source.
|
|
10
|
+
|
|
11
|
+
:return: Parsed GlobalConfig instance
|
|
12
|
+
"""
|
|
12
13
|
try:
|
|
13
|
-
|
|
14
|
-
with urllib.request.urlopen(source) as r:
|
|
15
|
-
data = json.loads(r.read().decode("utf-8"))
|
|
16
|
-
else:
|
|
17
|
-
data = json.loads(Path(source).read_text(encoding="utf-8"))
|
|
14
|
+
data = load_json(source)
|
|
18
15
|
return GlobalConfig.model_validate(data)
|
|
19
|
-
except HTTPError as e:
|
|
20
|
-
raise RuntimeError(f"Config fetch failed: {e} ({source})") from e
|
|
21
|
-
except URLError as e:
|
|
22
|
-
raise RuntimeError(f"Config fetch failed: {e.reason} ({source})") from e
|
|
23
|
-
except json.JSONDecodeError as e:
|
|
24
|
-
raise RuntimeError(f"Config JSON is invalid: {e.msg} ({source})") from e
|
|
25
16
|
except ValidationError as e:
|
|
26
17
|
raise RuntimeError(f"Config validation failed: {e} ({source})") from e
|
|
27
|
-
except OSError as e:
|
|
28
|
-
raise RuntimeError(f"Config read failed: {e} ({source})") from e
|
|
29
|
-
except Exception as e:
|
|
30
|
-
raise RuntimeError(f"Config load failed: {e} ({source})") from e
|
|
31
18
|
|
|
32
19
|
|
|
33
20
|
def get_mainnet_global_config() -> GlobalConfig:
|
tonutils/contracts/__init__.py
CHANGED
|
@@ -127,6 +127,8 @@ from .vanity import (
|
|
|
127
127
|
from .versions import ContractVersion
|
|
128
128
|
from .wallet import (
|
|
129
129
|
BaseMessageBuilder,
|
|
130
|
+
BaseWalletData,
|
|
131
|
+
BaseWalletParams,
|
|
130
132
|
EncryptedTextCommentBody,
|
|
131
133
|
ExternalMessage,
|
|
132
134
|
InternalMessage,
|
|
@@ -194,6 +196,7 @@ __all__ = [
|
|
|
194
196
|
"ALLOWED_DNS_ZONES",
|
|
195
197
|
"BaseContract",
|
|
196
198
|
"BaseMessageBuilder",
|
|
199
|
+
"BaseWalletData",
|
|
197
200
|
"ChangeDNSRecordBody",
|
|
198
201
|
"CONTRACT_CODES",
|
|
199
202
|
"ContractProtocol",
|
|
@@ -296,7 +296,7 @@ class TeleItemContent(TlbScheme):
|
|
|
296
296
|
"""
|
|
297
297
|
Initialize item content.
|
|
298
298
|
|
|
299
|
-
:param nft_content: Off-chain NFT metadata (image, description, etc)
|
|
299
|
+
:param nft_content: Off-chain NFT metadata (image, description, etc.)
|
|
300
300
|
:param dns: DNS records for the username/domain
|
|
301
301
|
:param token_info: Token name and domain information
|
|
302
302
|
"""
|
|
@@ -34,6 +34,7 @@ from .methods import (
|
|
|
34
34
|
seqno_get_method,
|
|
35
35
|
)
|
|
36
36
|
from .params import (
|
|
37
|
+
BaseWalletParams,
|
|
37
38
|
WalletHighloadV2Params,
|
|
38
39
|
WalletHighloadV3Params,
|
|
39
40
|
WalletPreprocessedV2Params,
|
|
@@ -46,6 +47,7 @@ from .params import (
|
|
|
46
47
|
)
|
|
47
48
|
from .protocol import WalletProtocol
|
|
48
49
|
from .tlb import (
|
|
50
|
+
BaseWalletData,
|
|
49
51
|
EncryptedTextCommentBody,
|
|
50
52
|
OutActionSendMsg,
|
|
51
53
|
TextCommentBody,
|
|
@@ -81,6 +83,8 @@ __all__ = [
|
|
|
81
83
|
"VALID_MNEMONIC_LENGTHS",
|
|
82
84
|
"BaseMessageBuilder",
|
|
83
85
|
"BaseWallet",
|
|
86
|
+
"BaseWalletData",
|
|
87
|
+
"BaseWalletParams",
|
|
84
88
|
"EncryptedTextCommentBody",
|
|
85
89
|
"ExternalMessage",
|
|
86
90
|
"InternalMessage",
|
tonutils/contracts/wallet/tlb.py
CHANGED
|
@@ -203,7 +203,7 @@ class WalletV5SubwalletID:
|
|
|
203
203
|
subwallet_number: int = 0,
|
|
204
204
|
workchain: WorkchainID = WorkchainID.BASECHAIN,
|
|
205
205
|
version: int = 0,
|
|
206
|
-
|
|
206
|
+
network: NetworkGlobalID = NetworkGlobalID.MAINNET,
|
|
207
207
|
) -> None:
|
|
208
208
|
"""
|
|
209
209
|
Initialize Wallet v5 subwallet ID.
|
|
@@ -211,19 +211,19 @@ class WalletV5SubwalletID:
|
|
|
211
211
|
:param subwallet_number: Subwallet number (0-32767)
|
|
212
212
|
:param workchain: Target workchain (default: BASECHAIN)
|
|
213
213
|
:param version: Wallet version identifier (default: 0)
|
|
214
|
-
:param
|
|
214
|
+
:param network: Network identifier (default: MAINNET)
|
|
215
215
|
"""
|
|
216
216
|
self.subwallet_number = subwallet_number
|
|
217
217
|
self.workchain = workchain
|
|
218
218
|
self.version = version
|
|
219
|
-
self.
|
|
219
|
+
self.network = network
|
|
220
220
|
|
|
221
221
|
def pack(self) -> int:
|
|
222
222
|
"""
|
|
223
223
|
Pack subwallet ID components into 32-bit integer.
|
|
224
224
|
|
|
225
225
|
Format: (1 << 31) | (workchain << 23) | (version << 15) | subwallet_number
|
|
226
|
-
XORed with
|
|
226
|
+
XORed with network for network isolation.
|
|
227
227
|
|
|
228
228
|
:return: Packed 32-bit subwallet ID
|
|
229
229
|
"""
|
|
@@ -232,22 +232,22 @@ class WalletV5SubwalletID:
|
|
|
232
232
|
ctx |= (self.workchain & 0xFF) << 23
|
|
233
233
|
ctx |= (self.version & 0xFF) << 15
|
|
234
234
|
ctx |= self.subwallet_number & 0x7FFF
|
|
235
|
-
return ctx ^ (self.
|
|
235
|
+
return ctx ^ (self.network & 0xFFFFFFFF)
|
|
236
236
|
|
|
237
237
|
@classmethod
|
|
238
238
|
def unpack(
|
|
239
239
|
cls,
|
|
240
240
|
value: int,
|
|
241
|
-
|
|
241
|
+
network: NetworkGlobalID,
|
|
242
242
|
) -> WalletV5SubwalletID:
|
|
243
243
|
"""
|
|
244
244
|
Unpack 32-bit integer into subwallet ID components.
|
|
245
245
|
|
|
246
246
|
:param value: Packed 32-bit subwallet ID
|
|
247
|
-
:param
|
|
247
|
+
:param network: Network identifier for XOR decoding
|
|
248
248
|
:return: Unpacked WalletV5SubwalletID instance
|
|
249
249
|
"""
|
|
250
|
-
ctx = (value ^
|
|
250
|
+
ctx = (value ^ network) & 0xFFFFFFFF
|
|
251
251
|
|
|
252
252
|
subwallet_number = ctx & 0x7FFF
|
|
253
253
|
version = (ctx >> 15) & 0xFF
|
|
@@ -258,7 +258,7 @@ class WalletV5SubwalletID:
|
|
|
258
258
|
subwallet_number=subwallet_number,
|
|
259
259
|
workchain=WorkchainID(workchain),
|
|
260
260
|
version=version,
|
|
261
|
-
|
|
261
|
+
network=network,
|
|
262
262
|
)
|
|
263
263
|
|
|
264
264
|
def __repr__(self) -> str:
|
|
@@ -298,7 +298,7 @@ class WalletV5BetaData(BaseWalletData):
|
|
|
298
298
|
|
|
299
299
|
:param builder: Cell builder to store to
|
|
300
300
|
"""
|
|
301
|
-
builder.store_int(self.subwallet_id.
|
|
301
|
+
builder.store_int(self.subwallet_id.network, 32)
|
|
302
302
|
builder.store_int(self.subwallet_id.workchain, 8)
|
|
303
303
|
builder.store_uint(self.subwallet_id.version, 8)
|
|
304
304
|
builder.store_uint(self.subwallet_id.subwallet_number, 32)
|
|
@@ -328,7 +328,7 @@ class WalletV5BetaData(BaseWalletData):
|
|
|
328
328
|
:return: Loaded WalletV5SubwalletID
|
|
329
329
|
"""
|
|
330
330
|
return WalletV5SubwalletID(
|
|
331
|
-
|
|
331
|
+
network=NetworkGlobalID(cs.load_int(32)),
|
|
332
332
|
workchain=WorkchainID(cs.load_int(8)),
|
|
333
333
|
version=cs.load_uint(8),
|
|
334
334
|
subwallet_number=cs.load_uint(32),
|
|
@@ -398,20 +398,22 @@ class WalletV5Data(BaseWalletData):
|
|
|
398
398
|
return cell.end_cell()
|
|
399
399
|
|
|
400
400
|
@classmethod
|
|
401
|
-
def deserialize(
|
|
401
|
+
def deserialize(
|
|
402
|
+
cls,
|
|
403
|
+
cs: Slice,
|
|
404
|
+
network: NetworkGlobalID = NetworkGlobalID.MAINNET,
|
|
405
|
+
) -> WalletV5Data:
|
|
402
406
|
"""
|
|
403
407
|
Deserialize wallet data from Cell slice.
|
|
404
408
|
|
|
405
409
|
:param cs: Cell slice to deserialize from
|
|
406
|
-
:param
|
|
410
|
+
:param network: Network ID for unpacking subwallet_id
|
|
407
411
|
:return: Deserialized WalletV5Data instance
|
|
408
412
|
"""
|
|
409
413
|
return cls(
|
|
410
414
|
is_signature_allowed=cs.load_bool(),
|
|
411
415
|
seqno=cs.load_uint(32),
|
|
412
|
-
subwallet_id=WalletV5SubwalletID.unpack(
|
|
413
|
-
cs.load_uint(32), network_global_id
|
|
414
|
-
),
|
|
416
|
+
subwallet_id=WalletV5SubwalletID.unpack(cs.load_uint(32), network),
|
|
415
417
|
public_key=PublicKey(cs.load_bytes(32)),
|
|
416
418
|
plugins=cs.load_maybe_ref(),
|
|
417
419
|
)
|
|
@@ -63,7 +63,7 @@ class _WalletV5(
|
|
|
63
63
|
cls._validate_config_type(config)
|
|
64
64
|
|
|
65
65
|
if config.subwallet_id is None:
|
|
66
|
-
config.subwallet_id = WalletV5SubwalletID(
|
|
66
|
+
config.subwallet_id = WalletV5SubwalletID(network=client.network)
|
|
67
67
|
|
|
68
68
|
return super().from_private_key(client, private_key, workchain, config)
|
|
69
69
|
|
|
@@ -163,7 +163,7 @@ class WalletV5Beta(
|
|
|
163
163
|
|
|
164
164
|
cell = begin_cell()
|
|
165
165
|
cell.store_uint(params.op_code, 32)
|
|
166
|
-
cell.store_int(subwallet_id.
|
|
166
|
+
cell.store_int(subwallet_id.network, 32)
|
|
167
167
|
cell.store_int(subwallet_id.workchain, 8)
|
|
168
168
|
cell.store_uint(subwallet_id.version, 8)
|
|
169
169
|
cell.store_uint(subwallet_id.subwallet_number, 32)
|
|
@@ -222,11 +222,11 @@ class WalletV5R1(
|
|
|
222
222
|
if not (self._info and self._info.data):
|
|
223
223
|
raise StateNotLoadedError(self, missing="state_data")
|
|
224
224
|
|
|
225
|
-
|
|
225
|
+
network = (
|
|
226
226
|
NetworkGlobalID.TESTNET if self.client.network else NetworkGlobalID.MAINNET
|
|
227
227
|
)
|
|
228
228
|
cs = self._info.data.begin_parse()
|
|
229
|
-
return self._data_model.deserialize(cs,
|
|
229
|
+
return self._data_model.deserialize(cs, network)
|
|
230
230
|
|
|
231
231
|
async def _build_msg_cell(
|
|
232
232
|
self,
|
tonutils/exceptions.py
CHANGED
|
@@ -23,26 +23,9 @@ class TonutilsError(Exception):
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
class TransportError(TonutilsError):
|
|
26
|
-
"""Transport-level failure
|
|
26
|
+
"""Transport-level failure (connect/handshake/send/recv)."""
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
:param endpoint: Endpoint identifier (URL or "host:port").
|
|
31
|
-
:param operation: What was attempted ("connect", "handshake", "send", "recv")
|
|
32
|
-
:param reason: Why it failed ("timeout 2.0s", "connection refused", etc.)
|
|
33
|
-
"""
|
|
34
|
-
|
|
35
|
-
endpoint: str
|
|
36
|
-
operation: str
|
|
37
|
-
reason: str
|
|
38
|
-
|
|
39
|
-
def __init__(
|
|
40
|
-
self,
|
|
41
|
-
*,
|
|
42
|
-
endpoint: str,
|
|
43
|
-
operation: str,
|
|
44
|
-
reason: str,
|
|
45
|
-
) -> None:
|
|
28
|
+
def __init__(self, *, endpoint: str, operation: str, reason: str) -> None:
|
|
46
29
|
self.endpoint = endpoint
|
|
47
30
|
self.operation = operation
|
|
48
31
|
self.reason = reason
|
|
@@ -50,23 +33,19 @@ class TransportError(TonutilsError):
|
|
|
50
33
|
|
|
51
34
|
|
|
52
35
|
class ProviderError(TonutilsError):
|
|
53
|
-
"""
|
|
36
|
+
"""Provider-level failure (protocol/parsing/backend/state)."""
|
|
54
37
|
|
|
55
38
|
|
|
56
39
|
class ClientError(TonutilsError):
|
|
57
|
-
"""
|
|
40
|
+
"""Client misuse, validation errors, or unsupported operations."""
|
|
58
41
|
|
|
59
42
|
|
|
60
43
|
class BalancerError(TonutilsError):
|
|
61
|
-
"""
|
|
44
|
+
"""Balancer failure (no alive backends, failover exhausted)."""
|
|
62
45
|
|
|
63
46
|
|
|
64
47
|
class NotConnectedError(TonutilsError, RuntimeError):
|
|
65
|
-
"""
|
|
66
|
-
|
|
67
|
-
component: str
|
|
68
|
-
endpoint: t.Optional[str]
|
|
69
|
-
operation: t.Optional[str]
|
|
48
|
+
"""Raised when an operation requires an active connection."""
|
|
70
49
|
|
|
71
50
|
def __init__(
|
|
72
51
|
self,
|
|
@@ -74,112 +53,69 @@ class NotConnectedError(TonutilsError, RuntimeError):
|
|
|
74
53
|
component: str = "client",
|
|
75
54
|
endpoint: t.Optional[str] = None,
|
|
76
55
|
operation: t.Optional[str] = None,
|
|
77
|
-
hint: t.Optional[str] = None,
|
|
78
56
|
) -> None:
|
|
79
57
|
self.component = component
|
|
80
58
|
self.endpoint = endpoint
|
|
81
59
|
self.operation = operation
|
|
82
60
|
|
|
83
|
-
if
|
|
84
|
-
hint = "Call connect() first or use an async context manager (`async with ...`)."
|
|
85
|
-
|
|
61
|
+
op = f"cannot `{operation}`: " if operation else ""
|
|
86
62
|
where = f" ({endpoint})" if endpoint else ""
|
|
87
|
-
|
|
88
|
-
super().__init__(f"{prefix}{component} is not connected{where}. {hint}")
|
|
63
|
+
super().__init__(f"{op}{component} is not connected{where}")
|
|
89
64
|
|
|
90
65
|
|
|
91
66
|
class ProviderTimeoutError(ProviderError, asyncio.TimeoutError):
|
|
92
|
-
"""
|
|
93
|
-
|
|
94
|
-
:param timeout: Timeout in seconds.
|
|
95
|
-
:param endpoint: Endpoint identifier (URL or host:port).
|
|
96
|
-
:param operation: Operation label (e.g. "request", "connect").
|
|
97
|
-
"""
|
|
98
|
-
|
|
99
|
-
timeout: float
|
|
100
|
-
endpoint: str
|
|
101
|
-
operation: str
|
|
67
|
+
"""Provider operation exceeded its timeout."""
|
|
102
68
|
|
|
103
69
|
def __init__(self, *, timeout: float, endpoint: str, operation: str) -> None:
|
|
104
|
-
self.timeout = timeout
|
|
70
|
+
self.timeout = float(timeout)
|
|
105
71
|
self.endpoint = endpoint
|
|
106
72
|
self.operation = operation
|
|
107
|
-
super().__init__(f"{operation} timed out after {timeout}s ({endpoint})")
|
|
73
|
+
super().__init__(f"{operation} timed out after {self.timeout}s ({endpoint})")
|
|
108
74
|
|
|
109
75
|
|
|
110
76
|
class ProviderResponseError(ProviderError):
|
|
111
|
-
"""
|
|
112
|
-
|
|
113
|
-
:param code: Backend code (HTTP status or lite-server code).
|
|
114
|
-
:param message: Backend error description.
|
|
115
|
-
:param endpoint: Endpoint identifier (URL or host:port).
|
|
116
|
-
"""
|
|
117
|
-
|
|
118
|
-
code: int
|
|
119
|
-
message: str
|
|
120
|
-
endpoint: str
|
|
77
|
+
"""Backend returned an error response."""
|
|
121
78
|
|
|
122
79
|
def __init__(self, *, code: int, message: str, endpoint: str) -> None:
|
|
123
|
-
self.code = code
|
|
80
|
+
self.code = int(code)
|
|
124
81
|
self.message = message
|
|
125
82
|
self.endpoint = endpoint
|
|
126
|
-
super().__init__(f"request failed: {code} {message} ({endpoint})")
|
|
83
|
+
super().__init__(f"request failed: {self.code} {self.message} ({endpoint})")
|
|
127
84
|
|
|
128
85
|
|
|
129
86
|
class RetryLimitError(ProviderError):
|
|
130
|
-
"""
|
|
131
|
-
|
|
132
|
-
:param attempts: Attempts already performed for the matched rule.
|
|
133
|
-
:param max_attempts: Maximum attempts allowed by the matched rule.
|
|
134
|
-
:param last_error: Last provider error that triggered a retry.
|
|
135
|
-
"""
|
|
136
|
-
|
|
137
|
-
attempts: int
|
|
138
|
-
max_attempts: int
|
|
139
|
-
last_error: ProviderError
|
|
87
|
+
"""Retry policy exhausted."""
|
|
140
88
|
|
|
141
89
|
def __init__(
|
|
142
90
|
self,
|
|
143
|
-
*,
|
|
144
91
|
attempts: int,
|
|
145
92
|
max_attempts: int,
|
|
146
93
|
last_error: ProviderError,
|
|
147
94
|
) -> None:
|
|
148
|
-
self.attempts = attempts
|
|
149
|
-
self.max_attempts = max_attempts
|
|
95
|
+
self.attempts = int(attempts)
|
|
96
|
+
self.max_attempts = int(max_attempts)
|
|
150
97
|
self.last_error = last_error
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
)
|
|
98
|
+
ratio = f"{self.attempts}/{self.max_attempts}"
|
|
99
|
+
super().__init__(f"retry limit reached {ratio}: {last_error}")
|
|
154
100
|
|
|
155
101
|
|
|
156
102
|
class ContractError(ClientError):
|
|
157
|
-
"""
|
|
158
|
-
|
|
159
|
-
:param target: Contract instance or contract class related to the failure.
|
|
160
|
-
:param message: Human-readable error message.
|
|
161
|
-
"""
|
|
162
|
-
|
|
163
|
-
target: t.Any
|
|
164
|
-
message: str
|
|
103
|
+
"""Contract wrapper operation failed."""
|
|
165
104
|
|
|
166
|
-
def __init__(self, target: t.Any,
|
|
105
|
+
def __init__(self, target: t.Any, details: str) -> None:
|
|
167
106
|
self.target = target
|
|
168
|
-
self.
|
|
169
|
-
name = (
|
|
170
|
-
target.__name__ if isinstance(target, type) else target.__class__.__name__
|
|
171
|
-
)
|
|
172
|
-
super().__init__(f"{name} failed: {message}")
|
|
107
|
+
self.details = details
|
|
173
108
|
|
|
109
|
+
if isinstance(target, type):
|
|
110
|
+
name = target.__name__
|
|
111
|
+
else:
|
|
112
|
+
name = target.__class__.__name__
|
|
174
113
|
|
|
175
|
-
|
|
176
|
-
"""Raise when a contract wrapper requires state that is not loaded.
|
|
114
|
+
super().__init__(f"{name} failed: {details}")
|
|
177
115
|
|
|
178
|
-
:param contract: Contract instance related to the failure.
|
|
179
|
-
:param missing: Missing field name (e.g. "info", "state_data").
|
|
180
|
-
"""
|
|
181
116
|
|
|
182
|
-
|
|
117
|
+
class StateNotLoadedError(ContractError):
|
|
118
|
+
"""Contract wrapper requires state that is not loaded."""
|
|
183
119
|
|
|
184
120
|
def __init__(self, contract: t.Any, *, missing: str) -> None:
|
|
185
121
|
self.missing = missing
|
|
@@ -188,23 +124,14 @@ class StateNotLoadedError(ContractError):
|
|
|
188
124
|
|
|
189
125
|
|
|
190
126
|
class RunGetMethodError(ClientError):
|
|
191
|
-
"""
|
|
192
|
-
|
|
193
|
-
:param address: Contract address (string form).
|
|
194
|
-
:param method_name: Get-method name.
|
|
195
|
-
:param exit_code: TVM exit code.
|
|
196
|
-
"""
|
|
197
|
-
|
|
198
|
-
address: str
|
|
199
|
-
method_name: str
|
|
200
|
-
exit_code: int
|
|
127
|
+
"""Contract get-method returned a non-zero TVM exit code."""
|
|
201
128
|
|
|
202
|
-
def __init__(self, *, address: str,
|
|
129
|
+
def __init__(self, *, address: str, exit_code: int, method_name: str) -> None:
|
|
203
130
|
self.address = address
|
|
131
|
+
self.exit_code = int(exit_code)
|
|
204
132
|
self.method_name = method_name
|
|
205
|
-
self.exit_code = exit_code
|
|
206
133
|
super().__init__(
|
|
207
|
-
f"get-method `{method_name}` failed: exit code {exit_code} ({address})"
|
|
134
|
+
f"get-method `{method_name}` failed: exit code {self.exit_code} ({address})"
|
|
208
135
|
)
|
|
209
136
|
|
|
210
137
|
|
tonutils/types.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import base64
|
|
4
|
+
import re
|
|
4
5
|
import typing as t
|
|
6
|
+
from contextlib import suppress
|
|
5
7
|
from dataclasses import dataclass
|
|
6
8
|
from enum import Enum
|
|
7
9
|
|
|
@@ -280,22 +282,34 @@ class Binary:
|
|
|
280
282
|
return self._size
|
|
281
283
|
|
|
282
284
|
def _parse(self, value: t.Any) -> bytes:
|
|
283
|
-
"""Parse input value into bytes."""
|
|
284
285
|
if isinstance(value, bytes):
|
|
285
286
|
return value
|
|
286
287
|
if isinstance(value, int):
|
|
287
288
|
length = max(1, (value.bit_length() + 7) // 8)
|
|
288
289
|
return value.to_bytes(length, "big")
|
|
290
|
+
|
|
289
291
|
if isinstance(value, str):
|
|
290
292
|
s = value.strip()
|
|
293
|
+
|
|
294
|
+
# 0x... hex
|
|
291
295
|
if s.lower().startswith("0x"):
|
|
292
|
-
return
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
296
|
+
return bytes.fromhex(s[2:])
|
|
297
|
+
|
|
298
|
+
# plain hex (common case for publicKey)
|
|
299
|
+
if len(s) % 2 == 0 and re.compile(r"^[0-9a-fA-F]+$").fullmatch(s):
|
|
300
|
+
if len(s) == self._size * 2:
|
|
301
|
+
return bytes.fromhex(s)
|
|
302
|
+
|
|
303
|
+
# base64 (strict)
|
|
304
|
+
with suppress(Exception):
|
|
305
|
+
b = base64.b64decode(s, validate=True)
|
|
306
|
+
return b
|
|
307
|
+
|
|
308
|
+
# decimal int as string fallback
|
|
309
|
+
n = int(s, 10)
|
|
310
|
+
length = max(1, (n.bit_length() + 7) // 8)
|
|
311
|
+
return n.to_bytes(length, "big")
|
|
312
|
+
|
|
299
313
|
raise ValueError(f"Invalid binary type: {type(value).__name__}.")
|
|
300
314
|
|
|
301
315
|
@property
|
tonutils/utils.py
CHANGED
|
@@ -5,9 +5,13 @@ import binascii
|
|
|
5
5
|
import decimal
|
|
6
6
|
import hashlib
|
|
7
7
|
import hmac
|
|
8
|
+
import json
|
|
8
9
|
import os
|
|
9
10
|
import time
|
|
10
11
|
import typing as t
|
|
12
|
+
import urllib.request
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from urllib.error import HTTPError, URLError
|
|
11
15
|
|
|
12
16
|
from Cryptodome.Cipher import AES
|
|
13
17
|
from nacl.bindings import (
|
|
@@ -40,6 +44,7 @@ __all__ = [
|
|
|
40
44
|
"cell_to_hex",
|
|
41
45
|
"decode_dns_name",
|
|
42
46
|
"encode_dns_name",
|
|
47
|
+
"load_json",
|
|
43
48
|
"maybe_stack_addr",
|
|
44
49
|
"norm_stack_cell",
|
|
45
50
|
"norm_stack_num",
|
|
@@ -387,15 +392,15 @@ class TextCipher:
|
|
|
387
392
|
if isinstance(payload, bytes):
|
|
388
393
|
return payload[:32], payload[32:48], payload[48:]
|
|
389
394
|
elif isinstance(payload, str):
|
|
395
|
+
# Try hex first; if that fails, try base64.
|
|
390
396
|
try:
|
|
391
|
-
|
|
397
|
+
data = bytes.fromhex(payload)
|
|
392
398
|
except ValueError:
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
return payload[:32], payload[32:48], payload[48:]
|
|
399
|
+
try:
|
|
400
|
+
data = base64.b64decode(payload, validate=True)
|
|
401
|
+
except (binascii.Error, ValueError):
|
|
402
|
+
raise ValueError("Invalid payload encoding: not hex or base64.")
|
|
403
|
+
return data[:32], data[32:48], data[48:]
|
|
399
404
|
|
|
400
405
|
cell = EncryptedTextCommentBody.deserialize(payload.begin_parse())
|
|
401
406
|
return cell.pub_xor, cell.msg_key, cell.ciphertext
|
|
@@ -501,3 +506,38 @@ class TextCipher:
|
|
|
501
506
|
|
|
502
507
|
comment = dec_data[padding_size:]
|
|
503
508
|
return comment.decode()
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
def load_json(source: str, timeout: float = 5.0) -> t.Any:
|
|
512
|
+
"""
|
|
513
|
+
Load and parse JSON from a URL or a local file.
|
|
514
|
+
|
|
515
|
+
:param source: URL or file path
|
|
516
|
+
:param timeout: Network timeout in seconds
|
|
517
|
+
:return: Parsed JSON object
|
|
518
|
+
"""
|
|
519
|
+
try:
|
|
520
|
+
if source.startswith(("http://", "https://")):
|
|
521
|
+
req = urllib.request.Request(
|
|
522
|
+
source,
|
|
523
|
+
method="GET",
|
|
524
|
+
headers={
|
|
525
|
+
"User-Agent": "tonutils (+https://github.com/nessshon/tonutils)",
|
|
526
|
+
"Accept": "application/json,text/plain,*/*",
|
|
527
|
+
},
|
|
528
|
+
)
|
|
529
|
+
with urllib.request.urlopen(req, timeout=timeout) as r:
|
|
530
|
+
return json.loads(r.read().decode("utf-8"))
|
|
531
|
+
|
|
532
|
+
return json.loads(Path(source).read_text(encoding="utf-8"))
|
|
533
|
+
|
|
534
|
+
except HTTPError as e:
|
|
535
|
+
raise RuntimeError(f"JSON fetch failed: {e} ({source})") from e
|
|
536
|
+
except URLError as e:
|
|
537
|
+
raise RuntimeError(f"JSON fetch failed: {e.reason} ({source})") from e
|
|
538
|
+
except json.JSONDecodeError as e:
|
|
539
|
+
raise RuntimeError(f"JSON is invalid: {e.msg} ({source})") from e
|
|
540
|
+
except OSError as e:
|
|
541
|
+
raise RuntimeError(f"JSON read failed: {e} ({source})") from e
|
|
542
|
+
except Exception as e:
|
|
543
|
+
raise RuntimeError(f"JSON load failed: {e} ({source})") from e
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tonutils
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.1b8
|
|
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
|
|
7
7
|
License-Expression: MIT
|
|
8
8
|
Project-URL: Homepage, https://github.com/nessshon/tonutils/
|
|
9
|
-
Project-URL: Documentation, https://nessshon.github.io/tonutils/
|
|
10
|
-
Project-URL: Repository, https://github.com/nessshon/tonutils/
|
|
11
9
|
Project-URL: Examples, https://github.com/nessshon/tonutils/tree/main/examples/
|
|
12
10
|
Keywords: AsyncIO,TON,TON blockchain,The Open Network,blockchain,crypto,smart contracts
|
|
13
11
|
Classifier: Development Status :: 4 - Beta
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
tonutils/__init__.py,sha256=
|
|
2
|
-
tonutils/
|
|
3
|
-
tonutils/
|
|
4
|
-
tonutils/exceptions.py,sha256=64TSU9LCTSEH70o1-qHTNIL-HXpxXv1xcsRklizJEGU,6929
|
|
1
|
+
tonutils/__init__.py,sha256=L-T_WYdCKByp5eNttc_saSyUumBCiupZNruAbbGxvxY,280
|
|
2
|
+
tonutils/cli.py,sha256=67QAK8NWr4BpC-zMua_iFs7w_O-6LrpSRR3jrhtHmpU,2636
|
|
3
|
+
tonutils/exceptions.py,sha256=atvVuRhbTMWMkQPzSFHVRQG2VRspsOEFN4mYZgt5mjk,4959
|
|
5
4
|
tonutils/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
|
-
tonutils/types.py,sha256=
|
|
7
|
-
tonutils/utils.py,sha256=
|
|
5
|
+
tonutils/types.py,sha256=XSW-9yOyN5xWtH3hbbNuD2N9AJTrTZZ3Cwqfx63S1Sc,14850
|
|
6
|
+
tonutils/utils.py,sha256=MjyZfz8i8r016vJxFCVj-59UnFaCxbJqWTZZARNPrVM,16542
|
|
8
7
|
tonutils/clients/__init__.py,sha256=F0aPLPOI8-HcqcweeCSG_fr2VArxiZxfsWHuzHDwE4k,521
|
|
9
8
|
tonutils/clients/base.py,sha256=FHKDJCUDNoLUuE4W0J3SGEtgJudW_6zFIAaP6zTKr_4,8207
|
|
10
9
|
tonutils/clients/limiter.py,sha256=lSUo6AhEuWUlo0y2sUc-P4YltRI2zjbicelYKkMTrls,3896
|
|
@@ -15,7 +14,7 @@ tonutils/clients/adnl/client.py,sha256=2tUr6YxxoNXdc0c0iRkyT1xpZkIKIM-DwgNTKJn-q
|
|
|
15
14
|
tonutils/clients/adnl/mixin.py,sha256=GVakCui_0yso0VCOsycZsvq55Ng6JyIdotrQ_0bIohM,8260
|
|
16
15
|
tonutils/clients/adnl/utils.py,sha256=pyrU0vLuR9qnOFlHtheOfch8u1FP4Q_5Ah2UuX_m7Jk,5129
|
|
17
16
|
tonutils/clients/adnl/provider/__init__.py,sha256=lLwFEEgCifMlYozwSzQbvq97R6Du7TzgGFnTgUnk4gw,63
|
|
18
|
-
tonutils/clients/adnl/provider/config.py,sha256=
|
|
17
|
+
tonutils/clients/adnl/provider/config.py,sha256=phylkEdmKtfJWp9VgOYpzTuG4SUIHV7G42rYfqcgfDU,943
|
|
19
18
|
tonutils/clients/adnl/provider/models.py,sha256=3dn4oZv7PIgiwlP2lGs0O6VckC19ejxCFlSPhzU0wX8,4537
|
|
20
19
|
tonutils/clients/adnl/provider/provider.py,sha256=J7wIGtv_ZeS5j6AdYafAQ0m3q4z8Sd5V_LcpfeSi7JM,25633
|
|
21
20
|
tonutils/clients/adnl/provider/transport.py,sha256=bT_a7h0nCBGKCfPNOg79CW04UIdyZCfQ_7-nFvpfWK0,11241
|
|
@@ -38,7 +37,7 @@ tonutils/clients/http/provider/base.py,sha256=JqhtFwLLGyTbTVXO6V8_TJf-nZtYjFrSXR
|
|
|
38
37
|
tonutils/clients/http/provider/models.py,sha256=HFB3xL9B7DHdahw9-qCdMv0pVCvOsVaklTM0Yz0pU-M,3510
|
|
39
38
|
tonutils/clients/http/provider/tonapi.py,sha256=IgEiz8VwZ3rKLAVB8Aq3RxUDcC2HMD2NIX4m_j1tk6U,3495
|
|
40
39
|
tonutils/clients/http/provider/toncenter.py,sha256=m_wLe778ibe0LMFBV2iIuULbrSxSoPOd-OmTNSXm5lE,3461
|
|
41
|
-
tonutils/contracts/__init__.py,sha256=
|
|
40
|
+
tonutils/contracts/__init__.py,sha256=vlvrI_Xsa-tt4dZW3YMz2ezJLL50T8OweHmxpFc3Mkc,9625
|
|
42
41
|
tonutils/contracts/base.py,sha256=CvTKWVJEvm07HThCenJ0T-QDYne_CgRD02Nyo29Exfg,10332
|
|
43
42
|
tonutils/contracts/codes.py,sha256=1Sbbs_izHZHd-i1cjWHRP8VN3Xc2BDPr4pnjojj6mZc,37741
|
|
44
43
|
tonutils/contracts/opcodes.py,sha256=niPd-pDmtXiEpYX8xvorFmd7O4vkY0i8nX71e3iaJ1s,1001
|
|
@@ -63,19 +62,19 @@ tonutils/contracts/telegram/__init__.py,sha256=nr0O0rzr2UJu1KrtrqbBqLOuUfMSLSWo4
|
|
|
63
62
|
tonutils/contracts/telegram/collection.py,sha256=CM3T_8eWmrO_Ovq-crZgesfJ4h6JV4DhyggjV6nBY-U,3925
|
|
64
63
|
tonutils/contracts/telegram/item.py,sha256=DhNs5WFjhI1vqMfUg-cm311JTIvSaznc3aTqtLOCr9w,3887
|
|
65
64
|
tonutils/contracts/telegram/methods.py,sha256=rcZsEYTDwbT92NFhBhs-8TYruQWmxYgF6KElvaRnJPg,4403
|
|
66
|
-
tonutils/contracts/telegram/tlb.py,sha256=
|
|
65
|
+
tonutils/contracts/telegram/tlb.py,sha256=TXbQfQT7OEjeoahHAavvZBCEq79qFWuVsyL5rllMXiI,17849
|
|
67
66
|
tonutils/contracts/vanity/__init__.py,sha256=6LvJQxpmtrE3-ju44IsrkYQTx4HSq8nRb3fLyJFwrgE,288
|
|
68
67
|
tonutils/contracts/vanity/models.py,sha256=B6W1TN4CyrMs4SfBDAjuQ8QP-wn5QFhNpcSzO99DCbY,815
|
|
69
68
|
tonutils/contracts/vanity/tlb.py,sha256=gcNYEGPWMUHYbg_Je9QbBUlmVXF5RmobL-FoCMCF1HA,1078
|
|
70
69
|
tonutils/contracts/vanity/vanity.py,sha256=uYH1zybcOTQQBPciUFxAS6wksBwawKx06YES2tLuguI,1242
|
|
71
|
-
tonutils/contracts/wallet/__init__.py,sha256=
|
|
70
|
+
tonutils/contracts/wallet/__init__.py,sha256=vNw1tj8sN9jgd_FK2Bclphqi3x5nyFHozHttldWRLFg,3383
|
|
72
71
|
tonutils/contracts/wallet/base.py,sha256=Ue0WMTtjQ55s3o1_67KHdqGIsjjFrgdoLjCiMwA1y1Q,15806
|
|
73
72
|
tonutils/contracts/wallet/configs.py,sha256=yQfuCEGL_fBuc5qGJ93rPIUATTR8V1wpYscgrWb7cEQ,4082
|
|
74
73
|
tonutils/contracts/wallet/messages.py,sha256=MDzM3HdRYIc_vhmsjeHZcF9z1zpcfSdSh3_s4ecSx7o,13610
|
|
75
74
|
tonutils/contracts/wallet/methods.py,sha256=x7aPt71v3PUFNYHStWQrjLK7_SWPC2MiTG0c15X5TRI,10732
|
|
76
75
|
tonutils/contracts/wallet/params.py,sha256=hqinZJmhWiZUywDcmolvRxB0HYJgMAPDWYJiTmgjZ7w,4569
|
|
77
76
|
tonutils/contracts/wallet/protocol.py,sha256=DCu3CNbcZJ_wwROEK3GlpnwxNY2ZLdE0D2Z23WiyVDY,6200
|
|
78
|
-
tonutils/contracts/wallet/tlb.py,sha256=
|
|
77
|
+
tonutils/contracts/wallet/tlb.py,sha256=RzUcHTzFPnf19ajt_WP3_17c4xKVCu_c8cLOgBZBsy8,21738
|
|
79
78
|
tonutils/contracts/wallet/versions/__init__.py,sha256=DOHAEpx1mOlHdyTg2K0Mj8ZkcjabSdpLvIQY9_Pk6gw,592
|
|
80
79
|
tonutils/contracts/wallet/versions/hw.py,sha256=9kc_mlCegBo1O4_MvRQGLE3ZqLHsvqGN2F1IgnJeBOE,8348
|
|
81
80
|
tonutils/contracts/wallet/versions/pp.py,sha256=QfTqd4TAJHFMuatIwe0lg80uixno2JqSZFmQboxhca0,3793
|
|
@@ -83,13 +82,7 @@ tonutils/contracts/wallet/versions/v1.py,sha256=BYRWXdM0OlSeCfayHAUBj_wM4bb3WTVY
|
|
|
83
82
|
tonutils/contracts/wallet/versions/v2.py,sha256=pwrlan-utZo_WmnzDwSbnzV8ibkPEWx2WU1uOjkdrrA,2452
|
|
84
83
|
tonutils/contracts/wallet/versions/v3.py,sha256=d7cM8wjmW-1H7jGuY3AuUd7eTY3wq9ZYpJ4f5OeYX08,2470
|
|
85
84
|
tonutils/contracts/wallet/versions/v4.py,sha256=2sAsjJ8_3oYAj5JwWH3PiMyoGbgl6-f7-p6T5X7MGTI,2713
|
|
86
|
-
tonutils/contracts/wallet/versions/v5.py,sha256
|
|
87
|
-
tonutils/tonconnect/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
88
|
-
tonutils/tonconnect/events.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
89
|
-
tonutils/tonconnect/storage.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
90
|
-
tonutils/tonconnect/tonconnect.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
91
|
-
tonutils/tonconnect/bridge/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
92
|
-
tonutils/tonconnect/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
85
|
+
tonutils/contracts/wallet/versions/v5.py,sha256=-JjuFgLjKQIlKjr5RsUZ-qMWlkUs1y02t1n_4QSQkrk,8832
|
|
93
86
|
tonutils/tools/__init__.py,sha256=QYOVuGY50FFkWlgIvHc2RPU3xiEWSbwnwZ6wuZPQnCA,102
|
|
94
87
|
tonutils/tools/block_scanner/__init__.py,sha256=yYARZYo4LlePo7DWrrYlejGE4NLInfNeU9Ipln8oFFA,209
|
|
95
88
|
tonutils/tools/block_scanner/events.py,sha256=02K85PR3Jfe6qK-Ve1Mbukk4AWkxCJv1d-C-0tGdH_s,1881
|
|
@@ -99,9 +92,9 @@ tonutils/tools/status_monitor/__init__.py,sha256=QnMlA0IDLtCGgXsEgB9q3EJTBo2s5js
|
|
|
99
92
|
tonutils/tools/status_monitor/console.py,sha256=UX3BzjjzeS_nKFGg4NkZJpu9fR_IAJZdQUMz0HcJCdg,5036
|
|
100
93
|
tonutils/tools/status_monitor/models.py,sha256=yHuiEuij4h2kVoOK3sbhNq6SwiGDW_evZmzUwMy1GQs,608
|
|
101
94
|
tonutils/tools/status_monitor/monitor.py,sha256=OXs-J5RCUp4VbnBZuGd-4LythGUGakxwGSYM1Ipw-4s,10065
|
|
102
|
-
tonutils-2.0.
|
|
103
|
-
tonutils-2.0.
|
|
104
|
-
tonutils-2.0.
|
|
105
|
-
tonutils-2.0.
|
|
106
|
-
tonutils-2.0.
|
|
107
|
-
tonutils-2.0.
|
|
95
|
+
tonutils-2.0.1b8.dist-info/licenses/LICENSE,sha256=fG-yM-8DSkOTaJ558P7uF5PNXBmineVO9-HC12YbIxs,1060
|
|
96
|
+
tonutils-2.0.1b8.dist-info/METADATA,sha256=vvOdq7wiIe6Y-RlNzLykw1JvThRvJgTQAFzqLJNLt8w,3391
|
|
97
|
+
tonutils-2.0.1b8.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
98
|
+
tonutils-2.0.1b8.dist-info/entry_points.txt,sha256=qijo1cqvbbzLVbXp-PCYh19Pgmd7duH6yljmnUPd55I,47
|
|
99
|
+
tonutils-2.0.1b8.dist-info/top_level.txt,sha256=-7H_mGl8S9HKQrkUiTLmEbtMM-knzRzd_a0cZZnuZIU,9
|
|
100
|
+
tonutils-2.0.1b8.dist-info/RECORD,,
|
tonutils/__meta__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "2.0.1b7"
|
tonutils/tonconnect/__init__.py
DELETED
|
File without changes
|
|
File without changes
|
tonutils/tonconnect/events.py
DELETED
|
File without changes
|
|
File without changes
|
tonutils/tonconnect/storage.py
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|