diff --git a/pytonapi/__init__.py b/pytonapi/__init__.py index 01df426..b1158b9 100644 --- a/pytonapi/__init__.py +++ b/pytonapi/__init__.py @@ -1,7 +1,5 @@ -from pytonapi.async_tonapi import AsyncTonapi -from pytonapi.tonapi import Tonapi +from pytonapi.tonapi import AsyncTonapi __all__ = [ "AsyncTonapi", - "Tonapi", ] diff --git a/pytonapi/async_tonapi/client.py b/pytonapi/async_tonapi/client.py deleted file mode 100644 index abade0b..0000000 --- a/pytonapi/async_tonapi/client.py +++ /dev/null @@ -1,332 +0,0 @@ -import asyncio -import json -from typing import Any, Dict, Optional, AsyncGenerator - -import httpx -import websockets -from httpx import URL, QueryParams - -from pytonapi.exceptions import ( - TONAPIBadRequestError, - TONAPIError, - TONAPIInternalServerError, - TONAPINotFoundError, - TONAPIUnauthorizedError, - TONAPITooManyRequestsError, - TONAPINotImplementedError, - TONAPISSEError, -) -from pytonapi.logger_config import setup_logging - - -class AsyncTonapiClientBase: - """ - Asynchronous TON API Client. - """ - - def __init__( - self, - api_key: str, - is_testnet: bool = False, - max_retries: int = 0, - base_url: Optional[str] = None, - websocket_url: Optional[str] = None, - headers: Optional[Dict[str, Any]] = None, - timeout: Optional[float] = None, - debug: bool = False, - **kwargs, - ) -> None: - """ - Initialize the AsyncTonapiClient. - - :param api_key: The API key. - :param is_testnet: Use True if using the testnet. - :param max_retries: Maximum number of retries per request if rate limit is reached. - :param base_url: The base URL for the API. - :param websocket_url: The URL for the WebSocket server. - :param headers: Additional headers to include in requests. - :param timeout: Request timeout in seconds. - :param debug: Enable debug mode. - """ - self.api_key = api_key - self.is_testnet = is_testnet - self.timeout = timeout - self.max_retries = max_retries - - self.base_url = base_url or "https://tonapi.io/" if not is_testnet else "https://testnet.tonapi.io/" - self.websocket_url = websocket_url or "wss://tonapi.io/v2/websocket" - self.headers = headers or {"Authorization": f"Bearer {api_key}"} - - self.debug = debug - self.logger = setup_logging(self.debug) - - @staticmethod - async def __read_content(response: httpx.Response) -> Any: - """ - Read the response content. - - :param response: The HTTP response object. - :return: The response content. - """ - try: - data = await response.aread() - try: - content = json.loads(data.decode()) - except json.JSONDecodeError: - content = data.decode() - except httpx.ResponseNotRead: - content = {"error": response.text} - except httpx.ReadError as read_error: - content = {"error": f"Read error occurred: {read_error}"} - except Exception as e: - raise TONAPIError(f"Failed to read response content: {e}") - - return content - - async def __process_response(self, response: httpx.Response) -> Dict[str, Any]: - """ - Process the HTTP response and handle errors. - - :param response: The HTTP response object. - :return: The response content as a dictionary. - :raises TONAPIError: If there is an error status code in the response. - """ - content = await self.__read_content(response) - - if response.status_code != 200: - error_map = { - 400: TONAPIBadRequestError, - 401: TONAPIUnauthorizedError, - 403: TONAPIInternalServerError, - 404: TONAPINotFoundError, - 429: TONAPITooManyRequestsError, - 500: TONAPIInternalServerError, - 501: TONAPINotImplementedError, - } - error_class = error_map.get(response.status_code, TONAPIError) - - if isinstance(content, dict): - content = content.get("error") or content.get("Error") - self.logger.error(f"Error response received: {content}") - raise error_class(content) - - return content - - async def _subscribe( - self, - method: str, - params: Dict[str, Any], - ) -> AsyncGenerator[str, None]: - """ - Subscribe to an SSE event stream. - - :param method: The API method to subscribe to. - :param params: Optional parameters for the API method. - """ - url = self.base_url + method - timeout = httpx.Timeout(timeout=self.timeout) - - self.logger.debug(f"Subscribing to SSE with URL: {url} and params: {params}") - async with httpx.AsyncClient() as client: - try: - async with client.stream( - method="GET", - url=url, - headers=self.headers, - params=params or {}, - timeout=timeout, - ) as response: - response.raise_for_status() - - async for line in response.aiter_lines(): - try: - key, value = line.split(": ", 1) - except ValueError: - self.logger.debug(f"Skipped line due to ValueError: {line}") - continue - if value == "heartbeat": - self.logger.debug("Received heartbeat") - continue - if key == "data": - self.logger.debug(f"Received SSE data: {value}") - yield value - except httpx.LocalProtocolError: - self.logger.error("Local protocol error during SSE subscription.") - raise TONAPIUnauthorizedError - except httpx.HTTPStatusError as e: - self.logger.error(f"HTTP status error during SSE subscription: {e}") - raise TONAPISSEError(e) - - async def _subscribe_websocket( - self, - method: str, - params: Any, - ) -> AsyncGenerator[Dict[str, Any], None]: - """ - Subscribe to a WebSocket event stream. - - :param method: The API method to subscribe to. - :param params: Parameters for the API method. - :return: An asynchronous generator yielding WebSocket event data. - """ - payload = { - "id": 1, - "jsonrpc": "2.0", - "method": method, - "params": params, - } - self.logger.debug("Subscribing to WebSocket with payload: {payload}") - async with websockets.connect(self.websocket_url) as websocket: - try: - await websocket.send(json.dumps(payload)) - while True: - message = await websocket.recv() - message_json = json.loads(message) - if "params" in message_json: - value = message_json["params"] - self.logger.debug(f"Received WebSocket message: {value}") - yield value - except websockets.exceptions.ConnectionClosed as e: - self.logger.error(f"WebSocket connection closed unexpectedly: {e}") - raise TONAPIError(e) - - async def _request( - self, - method: str, - path: str, - headers: Optional[Dict[str, Any]] = None, - params: Optional[Dict[str, Any]] = None, - body: Optional[Dict[str, Any]] = None, - ) -> Dict[str, Any]: - """ - Make an HTTP request. - - :param method: The HTTP method (GET or POST). - :param path: The API path. - :param headers: Optional headers to include in the request. - :param params: Optional query parameters. - :param body: Optional request body data. - :return: The response content as a dictionary. - """ - url = self.base_url + path - self.headers.update(headers or {}) - timeout = httpx.Timeout(timeout=self.timeout) - - self.logger.debug(f"Request {method}: {URL(url).copy_merge_params(QueryParams(params))}") - self.logger.debug(f"Request headers: {self.headers}") - if params: - self.logger.debug(f"Request params: {params}") - if body: - self.logger.debug(f"Request body: {body}") - try: - async with httpx.AsyncClient(headers=self.headers, timeout=timeout) as session: - response = await session.request( - method=method, - url=url, - params=params or {}, - json=body or {}, - ) - response_content = await self.__process_response(response) - self.logger.debug(f"Response received - Status code: {response.status_code}") - self.logger.debug(f"Response headers: {response.headers}") - self.logger.debug(f"Response content: {response_content}") - return response_content - - except httpx.LocalProtocolError: - self.logger.error("Local protocol error occurred during request.") - raise TONAPIUnauthorizedError - except httpx.HTTPStatusError as e: - self.logger.error(f"HTTP status error during request: {e}") - raise TONAPIError(e) - - async def _request_retries( - self, - method: str, - path: str, - headers: Optional[Dict[str, Any]] = None, - params: Optional[Dict[str, Any]] = None, - body: Optional[Dict[str, Any]] = None, - ) -> Dict[str, Any]: - """ - Make an HTTP request with retries if rate limit is reached. - - :param method: The HTTP method (GET or POST). - :param path: The API path. - :param headers: Optional headers to include in the request. - :param params: Optional query parameters. - :param body: Optional request body data. - :return: The response content as a dictionary. - """ - for i in range(self.max_retries): - try: - return await self._request( - method=method, - path=path, - headers=headers, - params=params, - body=body, - ) - except TONAPITooManyRequestsError: - self.logger.warning(f"Rate limit exceeded. Retrying {i + 1}/{self.max_retries} in 1 second.") - await asyncio.sleep(1) - - self.logger.error("Max retries exceeded while making request") - raise TONAPITooManyRequestsError - - async def _get( - self, - method: str, - params: Optional[Dict[str, Any]] = None, - headers: Optional[Dict[str, Any]] = None, - ) -> Dict[str, Any]: - """ - Make a GET request. - - :param method: The API method. - :param params: Optional query parameters. - :param headers: Optional headers to include in the request. - :return: The response content as a dictionary. - """ - request = self._request - if self.max_retries > 0: - request = self._request_retries - return await request("GET", method, headers, params=params) - - async def _post( - self, - method: str, - params: Optional[Dict[str, Any]] = None, - body: Optional[Dict[str, Any]] = None, - headers: Optional[Dict[str, Any]] = None, - ) -> Dict[str, Any]: - """ - Make a POST request. - - :param method: The API method. - :param body: The request body data. - :param headers: Optional headers to include in the request. - :return: The response content as a dictionary. - """ - request = self._request - if self.max_retries > 0: - request = self._request_retries - return await request("POST", method, headers, params=params, body=body) - - async def _delete( - self, - method: str, - params: Optional[Dict[str, Any]] = None, - headers: Optional[Dict[str, Any]] = None, - ) -> Dict[str, Any]: - """ - Make a DELETE request. - - :param method: The API method. - :param params: Optional query parameters. - :param headers: Optional headers to include in the request. - :return: The response content as a dictionary. - """ - request = self._request - if self.max_retries > 0: - request = self._request_retries - return await request("DELETE", method, headers, params=params) diff --git a/pytonapi/base.py b/pytonapi/base.py new file mode 100644 index 0000000..18ac5df --- /dev/null +++ b/pytonapi/base.py @@ -0,0 +1,293 @@ +import asyncio +import json +from typing import Any, Dict, Optional, AsyncGenerator + +import aiohttp + +from pytonapi.exceptions import ( + TONAPIBadRequestError, + TONAPIError, + TONAPIInternalServerError, + TONAPINotFoundError, + TONAPIUnauthorizedError, + TONAPITooManyRequestsError, + TONAPINotImplementedError, +) +from pytonapi.logger import setup_logging + + +class AsyncTonapiClientBase: + """ + Asynchronous TON API Client. + """ + + def __init__( + self, + api_key: str, + is_testnet: bool = False, + max_retries: int = 0, + headers: Optional[Dict[str, Any]] = None, + base_url: Optional[str] = None, + websocket_url: Optional[str] = None, + timeout: Optional[float] = None, + debug: bool = False, + **kwargs, + ) -> None: + """ + Initialize the AsyncTonapiClient. + + :param api_key: The API key. + :param is_testnet: Use True if using the testnet. + :param max_retries: Maximum number of retries per request if rate limit is reached. + :param headers: Additional headers to include in requests. + :param base_url: The base URL for the API. + :param websocket_url: The URL for the WebSocket server. + :param timeout: Request timeout in seconds. + :param debug: Enable debug mode. + """ + self.api_key = api_key + self.is_testnet = is_testnet + + self._timeout = timeout + self._max_retries = ( + max_retries + if max_retries >= 0 else + 0 + ) + self._headers = ( + headers + if headers else + {"Authorization": f"Bearer {api_key}"} + ) + self._base_url = ( + base_url + if base_url else + "https://tonapi.io/" if not is_testnet else "https://testnet.tonapi.io/" + ) + self._websocket_url = ( + websocket_url + if websocket_url else + "wss://tonapi.io/v2/websocket" + ) + + self._debug = debug + self._logger = setup_logging(self._debug) + + @staticmethod + async def __read_content(response: aiohttp.ClientResponse) -> Dict[str, Any]: + """ + Read the content of a response. + + :param response: The aiohttp response object. + :return: The response content as a dictionary or a string. + """ + try: + content = await response.text() + except Exception as e: + raise TONAPIError(f"Failed to read response content: {e}") + + try: + data = json.loads(content) + return data.get("error", data) + except json.JSONDecodeError: + return {"error": content} + + async def __raise_for_status(self, response: aiohttp.ClientResponse) -> None: + """ + Raise an exception if the response status code is not 200. + + :param response: The aiohttp response object. + :raises TONAPIError: If the response status code is not 200. + """ + if response.status == 200: + return + + error_map = { + 400: TONAPIBadRequestError, + 401: TONAPIUnauthorizedError, + 403: TONAPIInternalServerError, + 404: TONAPINotFoundError, + 429: TONAPITooManyRequestsError, + 500: TONAPIInternalServerError, + 501: TONAPINotImplementedError, + } + + error_text = await self.__read_content(response) + error_class = error_map.get(response.status, TONAPIError) + + self._logger.error(f"Error response received: {error_text}") + raise error_class(error_text) + + async def _subscribe( + self, + method: str, + params: Dict[str, Any], + ) -> AsyncGenerator[Dict[str, Any], None]: + """ + Subscribe to an SSE event stream. + + :param method: The API method to subscribe to. + :param params: Optional parameters for the API method. + """ + url = self._base_url + method + self._logger.debug(f"Subscribing to SSE with URL: {url} and params: {params}") + + try: + async with aiohttp.ClientSession(headers=self._headers) as session: + async with session.get(url, params=params or {}, timeout=self._timeout) as response: + await self.__raise_for_status(response) + + async for line in response.content: + decoded_line = line.decode("utf-8") + line_string = decoded_line.strip() + if not line_string: + continue + + try: + key, value = line_string.split(": ", 1) + except ValueError: + self._logger.debug(f"Skipped line due to ValueError: {line_string}") + continue + + if value == "heartbeat": + self._logger.debug("Received heartbeat") + continue + if key == "data": + self._logger.debug(f"Received SSE data: {value}") + data = json.loads(value) + yield data + + except aiohttp.ClientError as e: + self._logger.error(f"Error subscribing to SSE: {e}") + raise TONAPIError(e) + + async def _subscribe_websocket( + self, + method: str, + params: Any, + ) -> AsyncGenerator[Dict[str, Any], None]: + """ + Subscribe to a WebSocket event stream. + + :param method: The API method to subscribe to. + :param params: Parameters for the API method. + :return: An asynchronous generator yielding WebSocket event data. + """ + payload = { + "id": 1, + "jsonrpc": "2.0", + "method": method, + "params": params, + } + self._logger.debug(f"Subscribing to WebSocket with payload: {payload}") + + try: + async with aiohttp.ClientSession() as session: + async with session.ws_connect(self._websocket_url) as ws: + await ws.send_json(payload) + + async for msg in ws: + if isinstance(msg, aiohttp.WSMessage): + if msg.type == aiohttp.WSMsgType.TEXT: + message_json = json.loads(msg.data) + self._logger.debug(f"Received WebSocket message: {message_json}") + if "params" in message_json: + params = message_json["params"] + self._logger.debug(f"Received WebSocket params: {params}") + yield params + elif "result" in message_json: + result = message_json["result"] + if not result.startswith("success"): + raise TONAPIError(result) + elif msg.type == aiohttp.WSMsgType.CLOSED: + self._logger.warning("WebSocket connection closed") + break + elif msg.type == aiohttp.WSMsgType.ERROR: + self._logger.error(f"WebSocket error: {ws.exception()}") + raise TONAPIError(f"WebSocket error: {ws.exception()}") + else: + raise TONAPIError(f"Unexpected WebSocket message type") + + except aiohttp.ClientError as e: + self._logger.error(f"WebSocket connection failed: {e}") + raise TONAPIError(e) + finally: + if not ws.closed: + await ws.close() + + async def _request( + self, + method: str, + path: str, + headers: Optional[Dict[str, Any]] = None, + params: Optional[Dict[str, Any]] = None, + body: Optional[Dict[str, Any]] = None, + ) -> Dict[str, Any]: + """ + Make an HTTP request. + + :param method: The HTTP method (GET, POST, DELETE). + :param path: The API path. + :param headers: Optional headers to include in the request. + :param params: Optional query parameters. + :param body: Optional request body data. + :return: The response content as a dictionary. + """ + url = self._base_url + path + headers = {**self._headers, **(headers or {})} + if params: + params = {k: str(v).lower() if isinstance(v, bool) else v for k, v in params.items()} + + self._logger.debug(f"Request {method}: {url}") + self._logger.debug(f"Headers: {headers}, Params: {params}, Body: {body}") + + timeout = aiohttp.ClientTimeout(total=self._timeout) + + async with aiohttp.ClientSession(timeout=timeout) as session: + for attempt in range(self._max_retries + 1): + try: + async with session.request( + method=method, + url=url, + headers=headers, + params=params, + json=body, + ) as response: + await self.__raise_for_status(response) + return await self.__read_content(response) + except aiohttp.ClientResponseError as e: + self._logger.error(f"Request failed (attempt {attempt}): {e}") + if attempt < self._max_retries: + await asyncio.sleep(1) + else: + raise TONAPIError(e) + else: + raise TONAPIError("Max retries exceeded") + + async def _get( + self, + method: str, + params: Optional[Dict[str, Any]] = None, + headers: Optional[Dict[str, Any]] = None, + ) -> Dict[str, Any]: + """Make a GET request.""" + return await self._request("GET", method, params=params, headers=headers) + + async def _post( + self, + method: str, + params: Optional[Dict[str, Any]] = None, + body: Optional[Dict[str, Any]] = None, + headers: Optional[Dict[str, Any]] = None, + ) -> Dict[str, Any]: + """Make a POST request.""" + return await self._request("POST", method, params=params, body=body, headers=headers) + + async def _delete( + self, + method: str, + params: Optional[Dict[str, Any]] = None, + headers: Optional[Dict[str, Any]] = None, + ) -> Dict[str, Any]: + """Make a DELETE request.""" + return await self._request("DELETE", method, params=params, headers=headers) diff --git a/pytonapi/logger_config.py b/pytonapi/logger.py similarity index 100% rename from pytonapi/logger_config.py rename to pytonapi/logger.py diff --git a/pytonapi/async_tonapi/methods/__init__.py b/pytonapi/methods/__init__.py similarity index 100% rename from pytonapi/async_tonapi/methods/__init__.py rename to pytonapi/methods/__init__.py diff --git a/pytonapi/async_tonapi/methods/accounts.py b/pytonapi/methods/accounts.py similarity index 92% rename from pytonapi/async_tonapi/methods/accounts.py rename to pytonapi/methods/accounts.py index 91732b0..573444d 100644 --- a/pytonapi/async_tonapi/methods/accounts.py +++ b/pytonapi/methods/accounts.py @@ -1,6 +1,6 @@ from typing import Any, Dict, List, Optional -from pytonapi.async_tonapi.client import AsyncTonapiClientBase +from pytonapi.base import AsyncTonapiClientBase from pytonapi.schema.accounts import ( Account, Accounts, @@ -14,7 +14,7 @@ from pytonapi.schema.events import AccountEvents, AccountEvent from pytonapi.schema.jettons import JettonBalance, JettonsBalances from pytonapi.schema.multisig import Multisigs -from pytonapi.schema.nft import NftItems, NftItem +from pytonapi.schema.nft import NftItems from pytonapi.schema.traces import TraceIds @@ -203,40 +203,6 @@ async def get_nfts( return NftItems(**response) - async def get_all_nfts( - self, - account_id: str, - collection: Optional[str] = None, - indirect_ownership: bool = True, - ) -> NftItems: - """ - Get all NFT items by owner address. - - :param account_id: account ID - :param collection: filter NFT by collection address - :param indirect_ownership: Selling nft items in ton implemented usually via transfer items - to special selling account. This option enables including items which owned not directly. - :return: :class:`NftItems` - """ - nft_items: List[NftItem] = [] - offset, limit = 0, 1000 - - while True: - result = await self.get_nfts( - account_id=account_id, - limit=limit, - offset=offset, - collection=collection, - indirect_ownership=indirect_ownership, - ) - nft_items += result.nft_items - offset += limit - - if len(result.nft_items) == 0: - break - - return NftItems(nft_items=nft_items) - async def get_events( self, account_id: str, @@ -369,7 +335,7 @@ async def get_subscriptions(self, account_id: str) -> Subscriptions: return Subscriptions(**response) - async def reindex(self, account_id: str) -> bool: + async def reindex(self, account_id: str) -> None: """ Update internal cache for a particular account @@ -377,9 +343,7 @@ async def reindex(self, account_id: str) -> bool: :return: :class:`bool` """ method = f"v2/accounts/{account_id}/reindex" - response = await self._post(method=method) - - return bool(response) + await self._post(method=method) async def search_by_domain(self, name: str) -> FoundAccounts: """ diff --git a/pytonapi/async_tonapi/methods/blockchain.py b/pytonapi/methods/blockchain.py similarity index 97% rename from pytonapi/async_tonapi/methods/blockchain.py rename to pytonapi/methods/blockchain.py index 23c88c0..c29f47e 100644 --- a/pytonapi/async_tonapi/methods/blockchain.py +++ b/pytonapi/methods/blockchain.py @@ -1,6 +1,6 @@ from typing import Optional, Dict, Any -from pytonapi.async_tonapi.client import AsyncTonapiClientBase +from pytonapi.base import AsyncTonapiClientBase from pytonapi.schema.blockchain import ( Transactions, Transaction, @@ -230,7 +230,7 @@ async def execute_get_method( return MethodExecutionResult(**response) - async def send_message(self, body: Dict[str, Any]) -> bool: + async def send_message(self, body: Dict[str, Any]) -> None: """ Send message to blockchain. @@ -238,9 +238,7 @@ async def send_message(self, body: Dict[str, Any]) -> bool: :return: bool """ method = "v2/blockchain/message" - response = await self._post(method=method, body=body) - - return bool(response) + await self._post(method=method, body=body) async def get_config(self) -> BlockchainConfig: """ diff --git a/pytonapi/async_tonapi/methods/dns.py b/pytonapi/methods/dns.py similarity index 96% rename from pytonapi/async_tonapi/methods/dns.py rename to pytonapi/methods/dns.py index fced867..0bee11f 100644 --- a/pytonapi/async_tonapi/methods/dns.py +++ b/pytonapi/methods/dns.py @@ -1,4 +1,4 @@ -from pytonapi.async_tonapi.client import AsyncTonapiClientBase +from pytonapi.base import AsyncTonapiClientBase from pytonapi.schema.dns import DNSRecord, Auctions from pytonapi.schema.domains import DomainBids, DomainInfo diff --git a/pytonapi/async_tonapi/methods/emulate.py b/pytonapi/methods/emulate.py similarity index 98% rename from pytonapi/async_tonapi/methods/emulate.py rename to pytonapi/methods/emulate.py index ab5b4de..51c9cc4 100644 --- a/pytonapi/async_tonapi/methods/emulate.py +++ b/pytonapi/methods/emulate.py @@ -1,6 +1,6 @@ from typing import Dict, Any, Optional -from pytonapi.async_tonapi.client import AsyncTonapiClientBase +from pytonapi.base import AsyncTonapiClientBase from pytonapi.schema.blockchain import DecodedMessage from pytonapi.schema.events import Event, AccountEvent, MessageConsequences from pytonapi.schema.traces import Trace diff --git a/pytonapi/async_tonapi/methods/events.py b/pytonapi/methods/events.py similarity index 97% rename from pytonapi/async_tonapi/methods/events.py rename to pytonapi/methods/events.py index 08fed28..7db2233 100644 --- a/pytonapi/async_tonapi/methods/events.py +++ b/pytonapi/methods/events.py @@ -2,7 +2,7 @@ import binascii from typing import Any, Dict, Optional -from pytonapi.async_tonapi.client import AsyncTonapiClientBase +from pytonapi.base import AsyncTonapiClientBase from pytonapi.schema.events import Event diff --git a/pytonapi/async_tonapi/methods/gasless.py b/pytonapi/methods/gasless.py similarity index 88% rename from pytonapi/async_tonapi/methods/gasless.py rename to pytonapi/methods/gasless.py index 1a46916..bfbeae3 100644 --- a/pytonapi/async_tonapi/methods/gasless.py +++ b/pytonapi/methods/gasless.py @@ -1,6 +1,6 @@ from typing import Dict, Any -from pytonapi.async_tonapi.client import AsyncTonapiClientBase +from pytonapi.base import AsyncTonapiClientBase from pytonapi.schema.gasless import GaslessConfig, SignRawParams @@ -39,7 +39,7 @@ async def estimate_gas_price(self, master_id: str, body: Dict[str, Any]) -> Sign return SignRawParams(**response) - async def send(self, body: Dict[str, Any]) -> bool: + async def send(self, body: Dict[str, Any]) -> None: """ Send message to blockchain. @@ -51,6 +51,4 @@ async def send(self, body: Dict[str, Any]) -> bool: :return: bool """ method = "v2/gasless/send" - response = await self._post(method=method, body=body) - - return bool(response) + await self._post(method=method, body=body) diff --git a/pytonapi/async_tonapi/methods/inscriptions.py b/pytonapi/methods/inscriptions.py similarity index 98% rename from pytonapi/async_tonapi/methods/inscriptions.py rename to pytonapi/methods/inscriptions.py index 53a05a4..0dd6b6b 100644 --- a/pytonapi/async_tonapi/methods/inscriptions.py +++ b/pytonapi/methods/inscriptions.py @@ -1,6 +1,6 @@ from typing import Dict, Optional, Literal -from pytonapi.async_tonapi.client import AsyncTonapiClientBase +from pytonapi.base import AsyncTonapiClientBase from pytonapi.schema.events import AccountEvents from pytonapi.schema.inscriptions import InscriptionBalances diff --git a/pytonapi/async_tonapi/methods/jettons.py b/pytonapi/methods/jettons.py similarity index 75% rename from pytonapi/async_tonapi/methods/jettons.py rename to pytonapi/methods/jettons.py index 2c22aff..f53f644 100644 --- a/pytonapi/async_tonapi/methods/jettons.py +++ b/pytonapi/methods/jettons.py @@ -1,8 +1,6 @@ -from typing import List - -from pytonapi.async_tonapi.client import AsyncTonapiClientBase +from pytonapi.base import AsyncTonapiClientBase from pytonapi.schema.events import Event -from pytonapi.schema.jettons import JettonInfo, JettonHolders, Jettons, JettonHolder, JettonTransferPayload +from pytonapi.schema.jettons import JettonInfo, JettonHolders, Jettons, JettonTransferPayload class JettonsMethod(AsyncTonapiClientBase): @@ -34,28 +32,6 @@ async def get_holders(self, account_id: str, limit: int = 1000, offset: int = 0) return JettonHolders(**response) - async def get_all_holders(self, account_id: str) -> JettonHolders: - """ - Get all jetton's holders. - - :param account_id: Account ID - :return: :class:`JettonHolders` - """ - jetton_holders: List[JettonHolder] = [] - offset, limit = 0, 1000 - - while True: - result = await self.get_holders( - account_id=account_id, limit=limit, offset=offset, - ) - jetton_holders += result.addresses - offset += limit - - if len(result.addresses) == 0: - break - - return JettonHolders(addresses=jetton_holders, total=result.total) - async def get_all_jettons(self, limit: int = 100, offset: int = 0) -> Jettons: """ Get a list of all indexed jetton masters in the blockchain. diff --git a/pytonapi/async_tonapi/methods/liteserver.py b/pytonapi/methods/liteserver.py similarity index 99% rename from pytonapi/async_tonapi/methods/liteserver.py rename to pytonapi/methods/liteserver.py index 859de0b..c3aa317 100644 --- a/pytonapi/async_tonapi/methods/liteserver.py +++ b/pytonapi/methods/liteserver.py @@ -1,6 +1,6 @@ from typing import Dict, Any, Optional, Union -from pytonapi.async_tonapi.client import AsyncTonapiClientBase +from pytonapi.base import AsyncTonapiClientBase from pytonapi.schema.liteserver import ( RawMasterChainInfo, RawMasterChainInfoExt, diff --git a/pytonapi/async_tonapi/methods/multisig.py b/pytonapi/methods/multisig.py similarity index 87% rename from pytonapi/async_tonapi/methods/multisig.py rename to pytonapi/methods/multisig.py index 2309853..6a5a22f 100644 --- a/pytonapi/async_tonapi/methods/multisig.py +++ b/pytonapi/methods/multisig.py @@ -1,4 +1,4 @@ -from pytonapi.async_tonapi.client import AsyncTonapiClientBase +from pytonapi.base import AsyncTonapiClientBase from pytonapi.schema.multisig import Multisig diff --git a/pytonapi/async_tonapi/methods/nft.py b/pytonapi/methods/nft.py similarity index 83% rename from pytonapi/async_tonapi/methods/nft.py rename to pytonapi/methods/nft.py index 32f31e7..855b5fa 100644 --- a/pytonapi/async_tonapi/methods/nft.py +++ b/pytonapi/methods/nft.py @@ -1,6 +1,6 @@ from typing import List, Optional -from pytonapi.async_tonapi.client import AsyncTonapiClientBase +from pytonapi.base import AsyncTonapiClientBase from pytonapi.schema.events import AccountEvents from pytonapi.schema.nft import NftCollections, NftCollection, NftItems, NftItem @@ -53,28 +53,6 @@ async def get_items_by_collection_address( return NftItems(**response) - async def get_all_items_by_collection_address(self, account_id: str) -> NftItems: - """ - Get all NFT items from collection by collection address. - - :param account_id: Account ID - :return: :class:`NftItems` - """ - nft_items: List[NftItem] = [] - offset, limit = 0, 1000 - - while True: - result = await self.get_items_by_collection_address( - account_id=account_id, limit=limit, offset=offset, - ) - nft_items += result.nft_items - offset += limit - - if len(result.nft_items) == 0: - break - - return NftItems(nft_items=nft_items) - async def get_item_by_address(self, account_id: str) -> NftItem: """ Get NFT item by its address. diff --git a/pytonapi/async_tonapi/methods/rates.py b/pytonapi/methods/rates.py similarity index 97% rename from pytonapi/async_tonapi/methods/rates.py rename to pytonapi/methods/rates.py index 6af76b3..daac3ac 100644 --- a/pytonapi/async_tonapi/methods/rates.py +++ b/pytonapi/methods/rates.py @@ -1,6 +1,6 @@ from typing import List, Optional -from pytonapi.async_tonapi.client import AsyncTonapiClientBase +from pytonapi.base import AsyncTonapiClientBase from pytonapi.schema.rates import ChartRates, Rates, MarketsTonRates diff --git a/pytonapi/async_tonapi/methods/sse.py b/pytonapi/methods/sse.py similarity index 82% rename from pytonapi/async_tonapi/methods/sse.py rename to pytonapi/methods/sse.py index ec1c8fd..7db25f9 100644 --- a/pytonapi/async_tonapi/methods/sse.py +++ b/pytonapi/methods/sse.py @@ -1,7 +1,6 @@ -import json from typing import List, Callable, Any, Awaitable, Tuple, Optional -from pytonapi.async_tonapi.client import AsyncTonapiClientBase +from pytonapi.base import AsyncTonapiClientBase from pytonapi.schema.events import TransactionEventData, TraceEventData, MempoolEventData, BlockEventData @@ -13,7 +12,7 @@ async def subscribe_to_transactions( accounts: List[str], operations: Optional[List[str]] = None, args: Tuple = (), - ) -> Any: + ) -> None: """ Subscribes to transactions SSE events for the specified accounts. @@ -37,17 +36,15 @@ async def subscribe_to_transactions( params["operations"] = ",".join(operations) async for data in self._subscribe(method=method, params=params): - event = TransactionEventData(**json.loads(data)) - result = await handler(event, *args) - if result is not None: - return result + event = TransactionEventData(**data) + await handler(event, *args) async def subscribe_to_traces( self, handler: Callable[[TraceEventData, Any], Awaitable[Any]], accounts: List[str], args: Tuple = (), - ) -> Any: + ) -> None: """ Subscribes to traces SSE events for the specified accounts. @@ -57,17 +54,15 @@ async def subscribe_to_traces( method = "v2/sse/accounts/traces" params = {"accounts": ",".join(accounts)} async for data in self._subscribe(method=method, params=params): - event = TraceEventData(**json.loads(data)) - result = await handler(event, *args) - if result is not None: - return result + event = TraceEventData(**data) + await handler(event, *args) async def subscribe_to_mempool( self, handler: Callable[[MempoolEventData, Any], Awaitable[Any]], accounts: List[str], args: Tuple = (), - ) -> Any: + ) -> None: """ Subscribes to mempool SSE events for the specified accounts. @@ -77,17 +72,15 @@ async def subscribe_to_mempool( method = "v2/sse/mempool" params = {"accounts": ",".join(accounts)} async for data in self._subscribe(method=method, params=params): - event = MempoolEventData(**json.loads(data)) - result = await handler(event, *args) - if result is not None: - return result + event = MempoolEventData(**data) + await handler(event, *args) async def subscribe_to_blocks( self, handler: Callable[[BlockEventData, Any], Awaitable[Any]], workchain: Optional[int] = None, args: Tuple = (), - ) -> Any: + ) -> None: """ Subscribes to blocks SSE events for the specified workchains. @@ -97,7 +90,5 @@ async def subscribe_to_blocks( method = "v2/sse/blocks" params = {} if workchain is None else {"workchain": workchain} async for data in self._subscribe(method=method, params=params): - event = BlockEventData(**json.loads(data)) - result = await handler(event, *args) - if result is not None: - return result + event = BlockEventData(**data) + await handler(event, *args) diff --git a/pytonapi/async_tonapi/methods/staking.py b/pytonapi/methods/staking.py similarity index 97% rename from pytonapi/async_tonapi/methods/staking.py rename to pytonapi/methods/staking.py index 6fc4e97..30b349d 100644 --- a/pytonapi/async_tonapi/methods/staking.py +++ b/pytonapi/methods/staking.py @@ -1,6 +1,6 @@ from typing import Optional -from pytonapi.async_tonapi.client import AsyncTonapiClientBase +from pytonapi.base import AsyncTonapiClientBase from pytonapi.schema.staking import ( AccountStaking, StakingPoolInfo, diff --git a/pytonapi/async_tonapi/methods/storage.py b/pytonapi/methods/storage.py similarity index 86% rename from pytonapi/async_tonapi/methods/storage.py rename to pytonapi/methods/storage.py index 2bdeaf1..8fd93bf 100644 --- a/pytonapi/async_tonapi/methods/storage.py +++ b/pytonapi/methods/storage.py @@ -1,4 +1,4 @@ -from pytonapi.async_tonapi.client import AsyncTonapiClientBase +from pytonapi.base import AsyncTonapiClientBase from pytonapi.schema.storage import StorageProviders diff --git a/pytonapi/async_tonapi/methods/tonconnect.py b/pytonapi/methods/tonconnect.py similarity index 93% rename from pytonapi/async_tonapi/methods/tonconnect.py rename to pytonapi/methods/tonconnect.py index 4705e77..b2157dd 100644 --- a/pytonapi/async_tonapi/methods/tonconnect.py +++ b/pytonapi/methods/tonconnect.py @@ -1,4 +1,4 @@ -from pytonapi.async_tonapi.client import AsyncTonapiClientBase +from pytonapi.base import AsyncTonapiClientBase from pytonapi.schema.tonconnect import TonconnectPayload, AccountInfoByStateInit diff --git a/pytonapi/async_tonapi/methods/traces.py b/pytonapi/methods/traces.py similarity index 95% rename from pytonapi/async_tonapi/methods/traces.py rename to pytonapi/methods/traces.py index be1bd1e..2929761 100644 --- a/pytonapi/async_tonapi/methods/traces.py +++ b/pytonapi/methods/traces.py @@ -2,7 +2,7 @@ import binascii from typing import Any, Dict, Optional -from pytonapi.async_tonapi.client import AsyncTonapiClientBase +from pytonapi.base import AsyncTonapiClientBase from pytonapi.schema.traces import Trace diff --git a/pytonapi/async_tonapi/methods/utilites.py b/pytonapi/methods/utilites.py similarity index 92% rename from pytonapi/async_tonapi/methods/utilites.py rename to pytonapi/methods/utilites.py index d830766..ee950d3 100644 --- a/pytonapi/async_tonapi/methods/utilites.py +++ b/pytonapi/methods/utilites.py @@ -1,4 +1,4 @@ -from pytonapi.async_tonapi.client import AsyncTonapiClientBase +from pytonapi.base import AsyncTonapiClientBase from pytonapi.schema.utilites import AddressForm, ServiceStatus diff --git a/pytonapi/async_tonapi/methods/wallet.py b/pytonapi/methods/wallet.py similarity index 98% rename from pytonapi/async_tonapi/methods/wallet.py rename to pytonapi/methods/wallet.py index d2bb2c8..1d7a3f2 100644 --- a/pytonapi/async_tonapi/methods/wallet.py +++ b/pytonapi/methods/wallet.py @@ -1,6 +1,6 @@ from typing import Dict, Any, Union -from pytonapi.async_tonapi.client import AsyncTonapiClientBase +from pytonapi.base import AsyncTonapiClientBase from pytonapi.schema.accounts import Accounts from pytonapi.schema.events import MessageConsequences diff --git a/pytonapi/async_tonapi/methods/webhooks.py b/pytonapi/methods/webhooks.py similarity index 98% rename from pytonapi/async_tonapi/methods/webhooks.py rename to pytonapi/methods/webhooks.py index b94532a..b6741cd 100644 --- a/pytonapi/async_tonapi/methods/webhooks.py +++ b/pytonapi/methods/webhooks.py @@ -1,6 +1,6 @@ from typing import List -from pytonapi.async_tonapi.client import AsyncTonapiClientBase +from pytonapi.base import AsyncTonapiClientBase from pytonapi.schema.webhooks import WebhookCreate, WebhookList, AccountSubscriptions diff --git a/pytonapi/async_tonapi/methods/websocket.py b/pytonapi/methods/websocket.py similarity index 94% rename from pytonapi/async_tonapi/methods/websocket.py rename to pytonapi/methods/websocket.py index 484935b..e8a1da4 100644 --- a/pytonapi/async_tonapi/methods/websocket.py +++ b/pytonapi/methods/websocket.py @@ -1,6 +1,6 @@ from typing import List, Callable, Any, Awaitable, Tuple -from pytonapi.async_tonapi.client import AsyncTonapiClientBase +from pytonapi.base import AsyncTonapiClientBase from pytonapi.schema.events import TransactionEventData, TraceEventData, MempoolEventData @@ -55,8 +55,8 @@ async def subscribe_to_mempool( :handler: A callable function to handle the WSMessage :accounts: A list of account addresses to subscribe to """ - method = "mempool_message" - params = accounts + method = "subscribe_mempool" + params = ["accounts=" + ",".join(accounts)] async for data in self._subscribe_websocket(method=method, params=params): event = MempoolEventData(**data) await handler(event, *args) diff --git a/pytonapi/schema/accounts.py b/pytonapi/schema/accounts.py index 19aecc4..04415b2 100644 --- a/pytonapi/schema/accounts.py +++ b/pytonapi/schema/accounts.py @@ -78,4 +78,5 @@ class PublicKey(BaseModel): class BalanceChange(BaseModel): balance_change: Balance + from pytonapi.schema.nft import NftItem diff --git a/pytonapi/schema/inscriptions.py b/pytonapi/schema/inscriptions.py index 34f61a7..2e7812d 100644 --- a/pytonapi/schema/inscriptions.py +++ b/pytonapi/schema/inscriptions.py @@ -1,4 +1,5 @@ from typing import List + from pydantic import BaseModel diff --git a/pytonapi/schema/webhooks.py b/pytonapi/schema/webhooks.py index 30ad989..53f96d2 100644 --- a/pytonapi/schema/webhooks.py +++ b/pytonapi/schema/webhooks.py @@ -20,7 +20,6 @@ class AccountSubscription(BaseModel): accounts: List[dict] - class AccountTxSubscription(BaseModel): account_id: str last_delivered_lt: int diff --git a/pytonapi/async_tonapi/__init__.py b/pytonapi/tonapi.py similarity index 97% rename from pytonapi/async_tonapi/__init__.py rename to pytonapi/tonapi.py index 5b31957..d803637 100644 --- a/pytonapi/async_tonapi/__init__.py +++ b/pytonapi/tonapi.py @@ -1,7 +1,7 @@ from typing import Any, Dict, Optional -from pytonapi.async_tonapi import methods -from pytonapi.async_tonapi.client import AsyncTonapiClientBase +from pytonapi import methods +from pytonapi.base import AsyncTonapiClientBase __all__ = [ "AsyncTonapi", diff --git a/pytonapi/tonapi/__init__.py b/pytonapi/tonapi/__init__.py deleted file mode 100644 index d35a608..0000000 --- a/pytonapi/tonapi/__init__.py +++ /dev/null @@ -1,120 +0,0 @@ -from typing import Any, Dict, Optional - -from pytonapi.tonapi import methods -from pytonapi.tonapi.client import TonapiClientBase - -__all__ = [ - "Tonapi", - "TonapiClientBase" -] - - -class Tonapi(TonapiClientBase): - - def __init__( - self, - api_key: str, - is_testnet: bool = False, - max_retries: int = 0, - base_url: Optional[str] = None, - headers: Optional[Dict[str, Any]] = None, - timeout: Optional[float] = None, - debug: bool = False, - **kwargs, - ) -> None: - """ - Initialize the TonapiClient. - - :param api_key: The API key. - :param base_url: The base URL for the API. - :param is_testnet: Use True if using the testnet. - :param timeout: Request timeout in seconds. - :param headers: Additional headers to include in requests. - :param max_retries: Maximum number of retries per request if rate limit is reached. - """ - super().__init__( - api_key=api_key, - is_testnet=is_testnet, - max_retries=max_retries, - base_url=base_url, - headers=headers, - timeout=timeout, - debug=debug, - **kwargs - ) - - @property - def blockchain(self) -> methods.BlockchainMethod: - return methods.BlockchainMethod(**self.__dict__) - - @property - def accounts(self) -> methods.AccountsMethod: - return methods.AccountsMethod(**self.__dict__) - - @property - def jettons(self) -> methods.JettonsMethod: - return methods.JettonsMethod(**self.__dict__) - - @property - def liteserver(self) -> methods.LiteserverMethod: - return methods.LiteserverMethod(**self.__dict__) - - @property - def multisig(self) -> methods.MultisigMethod: - return methods.MultisigMethod(**self.__dict__) - - @property - def dns(self) -> methods.DnsMethod: - return methods.DnsMethod(**self.__dict__) - - @property - def emulate(self) -> methods.EmulateMethod: - return methods.EmulateMethod(**self.__dict__) - - @property - def events(self) -> methods.EventsMethod: - return methods.EventsMethod(**self.__dict__) - - @property - def inscriptions(self) -> methods.InscriptionsMethod: - return methods.InscriptionsMethod(**self.__dict__) - - @property - def nft(self) -> methods.NftMethod: - return methods.NftMethod(**self.__dict__) - - @property - def rates(self) -> methods.RatesMethod: - return methods.RatesMethod(**self.__dict__) - - @property - def sse(self) -> methods.SSEMethod: - return methods.SSEMethod(**self.__dict__) - - @property - def staking(self) -> methods.StakingMethod: - return methods.StakingMethod(**self.__dict__) - - @property - def storage(self) -> methods.StorageMethod: - return methods.StorageMethod(**self.__dict__) - - @property - def tonconnect(self) -> methods.TonconnectMethod: - return methods.TonconnectMethod(**self.__dict__) - - @property - def traces(self) -> methods.TracesMethod: - return methods.TracesMethod(**self.__dict__) - - @property - def utilities(self) -> methods.UtilitiesMethod: - return methods.UtilitiesMethod(**self.__dict__) - - @property - def wallet(self) -> methods.WalletMethod: - return methods.WalletMethod(**self.__dict__) - - @property - def webhooks(self) -> methods.WebhooksMethod: - return methods.WebhooksMethod(**self.__dict__) diff --git a/pytonapi/tonapi/client.py b/pytonapi/tonapi/client.py deleted file mode 100644 index 1285da8..0000000 --- a/pytonapi/tonapi/client.py +++ /dev/null @@ -1,287 +0,0 @@ -import json -import time -from typing import Any, Dict, Optional, Generator - -import httpx -from httpx import URL, QueryParams - -from pytonapi.exceptions import ( - TONAPIBadRequestError, - TONAPIError, - TONAPIInternalServerError, - TONAPINotFoundError, - TONAPIUnauthorizedError, - TONAPITooManyRequestsError, - TONAPINotImplementedError, TONAPISSEError -) -from pytonapi.logger_config import setup_logging - - -class TonapiClientBase: - """ - Synchronous TON API Client. - """ - - def __init__( - self, - api_key: str, - is_testnet: bool = False, - max_retries: int = 0, - base_url: Optional[str] = None, - headers: Optional[Dict[str, Any]] = None, - timeout: Optional[float] = None, - debug: bool = False, - **kwargs, - ) -> None: - """ - Initialize the TonapiClient. - - :param api_key: The API key. - :param is_testnet: Use True if using the testnet. - :param max_retries: Maximum number of retries per request if rate limit is reached. - :param base_url: The base URL for the API. - :param headers: Additional headers to include in requests. - :param timeout: Request timeout in seconds. - :param debug: Enable debug mode. - """ - self.api_key = api_key - self.is_testnet = is_testnet - self.timeout = timeout - self.max_retries = max_retries - - self.base_url = base_url or "https://tonapi.io/" if not is_testnet else "https://testnet.tonapi.io/" - self.headers = headers or {"Authorization": f"Bearer {api_key}"} - - self.debug = debug - self.logger = setup_logging(self.debug) - - @staticmethod - def __read_content(response: httpx.Response) -> Any: - """ - Read the response content. - - :param response: The HTTP response object. - :return: The response content. - """ - try: - content = response.json() - except (httpx.ResponseNotRead, json.JSONDecodeError): - content = {"error": response.text} - except Exception as e: - raise TONAPIError(f"Failed to read response content: {e}") - - return content - - def __process_response(self, response: httpx.Response) -> Dict[str, Any]: - """ - Process the HTTP response and handle errors. - - :param response: The HTTP response object. - :return: The response content as a dictionary. - :raises TONAPIError: If there is an error status code in the response. - """ - content = self.__read_content(response) - - if response.status_code != 200: - error_map = { - 400: TONAPIBadRequestError, - 401: TONAPIUnauthorizedError, - 403: TONAPIInternalServerError, - 404: TONAPINotFoundError, - 429: TONAPITooManyRequestsError, - 500: TONAPIInternalServerError, - 501: TONAPINotImplementedError, - } - error_class = error_map.get(response.status_code, TONAPIError) - - if isinstance(content, dict): - content = content.get("error") or content.get("Error") - self.logger.error(f"Error response received: {content}") - raise error_class(content) - - return content - - def _subscribe( - self, - method: str, - params: Dict[str, Any], - ) -> Generator[str, None, None]: - """ - Subscribe to an SSE event stream. - - :param method: The API method to subscribe to. - :param params: Optional parameters for the API method. - """ - url = self.base_url + method - timeout = httpx.Timeout(timeout=self.timeout) - - self.logger.debug(f"Subscribing to SSE with URL: {url} and params: {params}") - try: - with httpx.stream( - method="GET", - url=url, - headers=self.headers, - params=params or {}, - timeout=timeout, - ) as response: - if response.status_code != 200: - self.__process_response(response) - for line in response.iter_lines(): - try: - key, value = line.split(": ", 1) - except ValueError: - self.logger.debug(f"Skipped line due to ValueError: {line}") - continue - if value == "heartbeat": - self.logger.debug("Received heartbeat") - continue - if key == "data": - self.logger.debug(f"Received SSE data: {value}") - yield value - except httpx.LocalProtocolError: - self.logger.error("Local protocol error during SSE subscription.") - raise TONAPIUnauthorizedError - except httpx.HTTPStatusError as e: - self.logger.error(f"HTTP status error during SSE subscription: {e}") - raise TONAPISSEError(e) - - def _request( - self, - method: str, - path: str, - headers: Optional[Dict[str, Any]] = None, - params: Optional[Dict[str, Any]] = None, - body: Optional[Dict[str, Any]] = None, - ) -> Dict[str, Any]: - """ - Make an HTTP request. - - :param method: The HTTP method (GET or POST). - :param path: The API path. - :param headers: Optional headers to include in the request. - :param params: Optional query parameters. - :param body: Optional request body data. - :return: The response content as a dictionary. - """ - url = self.base_url + path - self.headers.update(headers or {}) - timeout = httpx.Timeout(timeout=self.timeout) - - self.logger.debug(f"Request {method}: {URL(url).copy_merge_params(QueryParams(params))}") - self.logger.debug(f"Request headers: {self.headers}") - if params: - self.logger.debug(f"Request params: {params}") - if body: - self.logger.debug(f"Request body: {body}") - try: - with httpx.Client(headers=self.headers, timeout=timeout) as session: - response = session.request( - method=method, - url=url, - params=params or {}, - json=body or {}, - ) - response_content = self.__process_response(response) - self.logger.debug(f"Response received - Status code: {response.status_code}") - self.logger.debug(f"Response headers: {response.headers}") - self.logger.debug(f"Response content: {response_content}") - return response_content - - except httpx.LocalProtocolError: - self.logger.error("Local protocol error occurred during request.") - raise TONAPIUnauthorizedError - except httpx.HTTPStatusError as e: - self.logger.error(f"HTTP status error during request: {e}") - raise TONAPIError(e) - - def _request_retries( - self, - method: str, - path: str, - headers: Optional[Dict[str, Any]] = None, - params: Optional[Dict[str, Any]] = None, - body: Optional[Dict[str, Any]] = None, - ) -> Dict[str, Any]: - """ - Make an HTTP request with retries if rate limit is reached. - - :param method: The HTTP method (GET or POST). - :param path: The API path. - :param headers: Optional headers to include in the request. - :param params: Optional query parameters. - :param body: Optional request body data. - :return: The response content as a dictionary. - """ - for i in range(self.max_retries): - try: - return self._request( - method=method, - path=path, - headers=headers, - params=params, - body=body, - ) - except TONAPITooManyRequestsError: - self.logger.warning(f"Rate limit exceeded. Retrying {i + 1}/{self.max_retries} in 1 second.") - time.sleep(1) - - self.logger.error("Max retries exceeded while making request") - raise TONAPITooManyRequestsError - - def _get( - self, - method: str, - params: Optional[Dict[str, Any]] = None, - headers: Optional[Dict[str, Any]] = None, - ) -> Dict[str, Any]: - """ - Make a GET request. - - :param method: The API method. - :param params: Optional query parameters. - :param headers: Optional headers to include in the request. - :return: The response content as a dictionary. - """ - request = self._request - if self.max_retries > 0: - request = self._request_retries - return request("GET", method, headers, params=params) - - def _post( - self, - method: str, - params: Optional[Dict[str, Any]] = None, - body: Optional[Dict[str, Any]] = None, - headers: Optional[Dict[str, Any]] = None, - ) -> Dict[str, Any]: - """ - Make a POST request. - - :param method: The API method. - :param body: The request body data. - :param headers: Optional headers to include in the request. - :return: The response content as a dictionary. - """ - request = self._request - if self.max_retries > 0: - request = self._request_retries - return request("POST", method, headers, params=params, body=body) - - def _delete( - self, - method: str, - params: Optional[Dict[str, Any]] = None, - headers: Optional[Dict[str, Any]] = None, - ) -> Dict[str, Any]: - """ - Make a DELETE request. - - :param method: The API method. - :param params: Optional query parameters. - :param headers: Optional headers to include in the request. - :return: The response content as a dictionary. - """ - request = self._request - if self.max_retries > 0: - request = self._request_retries - return request("DELETE", method, headers, params=params) diff --git a/pytonapi/tonapi/methods/__init__.py b/pytonapi/tonapi/methods/__init__.py deleted file mode 100644 index 1be5599..0000000 --- a/pytonapi/tonapi/methods/__init__.py +++ /dev/null @@ -1,43 +0,0 @@ -from .accounts import AccountsMethod -from .blockchain import BlockchainMethod -from .dns import DnsMethod -from .emulate import EmulateMethod -from .events import EventsMethod -from .gasless import GaslessMethod -from .inscriptions import InscriptionsMethod -from .jettons import JettonsMethod -from .liteserver import LiteserverMethod -from .multisig import MultisigMethod -from .nft import NftMethod -from .rates import RatesMethod -from .sse import SSEMethod -from .staking import StakingMethod -from .storage import StorageMethod -from .tonconnect import TonconnectMethod -from .traces import TracesMethod -from .utilites import UtilitiesMethod -from .wallet import WalletMethod -from .webhooks import WebhooksMethod - -__all__ = [ - "AccountsMethod", - "BlockchainMethod", - "DnsMethod", - "EmulateMethod", - "EventsMethod", - "GaslessMethod", - "InscriptionsMethod", - "JettonsMethod", - "LiteserverMethod", - "MultisigMethod", - "NftMethod", - "RatesMethod", - "SSEMethod", - "StakingMethod", - "StorageMethod", - "TonconnectMethod", - "TracesMethod", - "UtilitiesMethod", - "WalletMethod", - "WebhooksMethod", -] diff --git a/pytonapi/tonapi/methods/accounts.py b/pytonapi/tonapi/methods/accounts.py deleted file mode 100644 index da1f11b..0000000 --- a/pytonapi/tonapi/methods/accounts.py +++ /dev/null @@ -1,478 +0,0 @@ -from typing import Any, Dict, List, Optional - -from pytonapi.schema.accounts import ( - Account, - Accounts, - FoundAccounts, - Subscriptions, - DnsExpiring, - PublicKey, - BalanceChange, -) -from pytonapi.schema.domains import DomainNames -from pytonapi.schema.events import AccountEvents, AccountEvent -from pytonapi.schema.jettons import JettonBalance, JettonsBalances -from pytonapi.schema.multisig import Multisigs -from pytonapi.schema.nft import NftItems, NftItem -from pytonapi.schema.traces import TraceIds -from pytonapi.tonapi.client import TonapiClientBase - - -class AccountsMethod(TonapiClientBase): - - def get_bulk_info(self, account_ids: List[str]) -> Accounts: - """ - Get human-friendly information about multiple accounts without low-level details. - - :param account_ids: List of account IDs - :return: :class:`Accounts` - """ - method = f"v2/accounts/_bulk" - params = {"account_ids": account_ids} - response = self._post(method=method, body=params) - - return Accounts(**response) - - def get_info(self, account_id: str) -> Account: - """ - Get human-friendly information about an account without low-level details. - - :param account_id: Account ID - :return: :class:`Account` - """ - method = f"v2/accounts/{account_id}" - response = self._get(method=method) - - return Account(**response) - - def get_domains(self, account_id: str) -> DomainNames: - """ - Get domains for wallet account. - - :param account_id: account ID - :return: :class:`DomainNames` - """ - method = f"v2/accounts/{account_id}/dns/backresolve" - response = self._get(method=method) - - return DomainNames(**response) - - def get_jettons_balances( - self, - account_id: str, - currencies: Optional[List[str]] = None, - supported_extensions: Optional[List[str]] = None, - ) -> JettonsBalances: - """ - Get all Jettons balances by owner address. - - :param account_id: account ID - :param currencies: accept ton and all possible fiat currencies, separated by commas - :param supported_extensions: comma separated list supported extensions - :return: :class:`JettonsBalances` - """ - method = f"v2/accounts/{account_id}/jettons" - params = {"supported_extensions": ",".join(supported_extensions)} if supported_extensions else {} - if currencies: - params["currencies"] = ",".join(currencies) - response = self._get(method=method, params=params) - - return JettonsBalances(**response) - - def get_jetton_balance( - self, - account_id: str, - jetton_id: str, - currencies: Optional[List[str]] = None, - supported_extensions: Optional[List[str]] = None, - ) -> JettonBalance: - """ - Get Jetton balance by owner address - - :param account_id: account ID - :param jetton_id: jetton ID - :param currencies: accept ton and all possible fiat currencies, separated by commas - :param supported_extensions: comma separated list supported extensions - :return: :class:`JettonBalance` - """ - method = f"v2/accounts/{account_id}/jettons/{jetton_id}" - params = {"supported_extensions": ",".join(supported_extensions)} if supported_extensions else {} - if currencies: - params["currencies"] = ",".join(currencies) - response = self._get(method=method, params=params) - - return JettonBalance(**response) - - def get_jettons_history( - self, - account_id: str, - limit: int = 100, - before_lt: Optional[int] = None, - accept_language: str = "en", - start_date: Optional[int] = None, - end_date: Optional[int] = None, - ) -> AccountEvents: - """ - Get the transfer jettons history for account. - - :param account_id: account ID - :param limit: Default value: 100 - :param before_lt: omit this parameter to get last events - :param accept_language: Default value: en - :param start_date: Default value: None - :param end_date: Default value: None - :return: :class:`AccountEvents` - """ - method = f"v2/accounts/{account_id}/jettons/history" - params = {"limit": limit} - if before_lt is not None: - params["before_lt"] = before_lt - if start_date: - params["start_date"] = start_date - if end_date: - params["end_date"] = end_date - headers = {"Accept-Language": accept_language} - response = self._get(method=method, params=params, headers=headers) - - return AccountEvents(**response) - - def get_jettons_history_by_jetton( - self, - account_id: str, - jetton_id: str, - limit: int = 100, - before_lt: Optional[int] = None, - accept_language: str = "en", - start_date: Optional[int] = None, - end_date: Optional[int] = None, - ) -> AccountEvents: - """ - Get the transfer jetton history for account and jetton. - - - :param account_id: account ID - :param jetton_id: jetton ID - :param limit: Default value: 100 - :param before_lt: omit this parameter to get last events - :param accept_language: Default value: en - :param start_date: Default value: None - :param end_date: Default value: None - :return: :class:`AccountEvents` - """ - method = f"v2/accounts/{account_id}/jettons/{jetton_id}/history" - params = {"limit": limit} - if before_lt is not None: - params["before_lt"] = before_lt - if start_date: - params["start_date"] = start_date - if end_date: - params["end_date"] = end_date - headers = {"Accept-Language": accept_language} - response = self._get(method=method, params=params, headers=headers) - - return AccountEvents(**response) - - def get_nfts( - self, - account_id: str, - limit: int = 1000, - offset: int = 0, - collection: Optional[str] = None, - indirect_ownership: bool = False, - ) -> NftItems: - """ - Get NFT items by owner address. - - :param account_id: account ID - :param limit: Default value: 1000 - :param collection: filter NFT by collection address - :param offset: Default value: 0 - :param indirect_ownership: Selling nft items in ton implemented usually via transfer items - to special selling account. This option enables including items which owned not directly. - :return: :class:`NftItems` - """ - method = f"v2/accounts/{account_id}/nfts" - params = { - "limit": limit, - "offset": offset, - "indirect_ownership": "true" if indirect_ownership else "false" - } - if collection: - params["collection"] = collection - response = self._get(method=method, params=params) - - return NftItems(**response) - - def get_all_nfts( - self, - account_id: str, - collection: Optional[str] = None, - indirect_ownership: bool = True, - ) -> NftItems: - """ - Get all NFT items by owner address. - - :param account_id: account ID - :param collection: filter NFT by collection address - :param indirect_ownership: Selling nft items in ton implemented usually via transfer items - to special selling account. This option enables including items which owned not directly. - :return: :class:`NftItems` - """ - nft_items: List[NftItem] = [] - offset, limit = 0, 1000 - - while True: - result = self.get_nfts( - account_id=account_id, - limit=limit, - offset=offset, - collection=collection, - indirect_ownership=indirect_ownership, - ) - nft_items += result.nft_items - offset += limit - - if len(result.nft_items) == 0: - break - - return NftItems(nft_items=nft_items) - - def get_events( - self, - account_id: str, - limit: int = 100, - before_lt: Optional[int] = None, - accept_language: str = "en", - initiator: bool = False, - subject_only: bool = False, - start_date: Optional[int] = None, - end_date: Optional[int] = None, - ) -> AccountEvents: - """ - Get events for an account. Each event is built on top of a trace which is a series of transactions - caused by one inbound message. TonAPI looks for known patterns inside the trace and splits the trace - into actions, where a single action represents a meaningful high-level operation like a Jetton - Transfer or an NFT Purchase. Actions are expected to be shown to users. It is advised not to build - any logic on top of actions because actions can be changed at any time. - - :param account_id: account ID - :param limit: Default value: 100 - :param before_lt: omit this parameter to get last events - :param initiator: Show only events that are initiated by this account. Default value : false - :param accept_language: Default value: en - :param subject_only: filter actions where requested account is not real subject - (for example sender or receiver jettons). Default value: False - :param start_date: Default value: None - :param end_date: Default value: None - :return: :class:`AccountEvents` - """ - method = f"v2/accounts/{account_id}/events" - params = { - "limit": limit, - "initiator": initiator, - "subject_only": subject_only - } - if before_lt: - params["before_lt"] = before_lt - if start_date: - params["start_date"] = start_date - if end_date: - params["end_date"] = end_date - headers = {"Accept-Language": accept_language} - response = self._get(method=method, params=params, headers=headers) - - return AccountEvents(**response) - - def get_event( - self, - account_id: str, - event_id: str, - accept_language: str = "en", - subject_only: Optional[bool] = False, - ) -> AccountEvent: - """ - Get event for an account by event_id - - :param account_id: account ID - :param event_id: event ID - :param accept_language: Default value: en - :param subject_only: Default value: False - :return: :class:`AccountEvent` - """ - method = f"v2/accounts/{account_id}/events/{event_id}" - params = {"subject_only": subject_only} if subject_only else {} - headers = {"Accept-Language": accept_language} - response = self._get(method=method, params=params, headers=headers) - - return AccountEvent(**response) - - def get_traces(self, account_id: str, limit: int = 100, before_lt: Optional[int] = None) -> TraceIds: - """ - Get traces for account. - - :param account_id: account ID - :param limit: Default value: 100 - :param before_lt: omit this parameter to get last events - :return: :class:`TraceIds` - """ - method = f"v2/accounts/{account_id}/traces" - params = {"limit": limit} - if before_lt is not None: - params["before_lt"] = before_lt - response = self._get(method=method, params=params) - - return TraceIds(**response) - - def get_nft_history( - self, - account_id: str, - limit: int = 100, - before_lt: Optional[int] = None, - accept_language: str = "en", - start_date: Optional[int] = None, - end_date: Optional[int] = None, - ) -> AccountEvents: - """ - Get the transfer nft history. - - :param account_id: account ID - :param limit: Default value: 100 - :param before_lt: omit this parameter to get last events - :param accept_language: Default value: en - :param start_date: Default value: None - :param end_date: Default value: None - :return: :class:`AccountEvents` - """ - method = f"v2/accounts/{account_id}/nfts/history" - params = {"limit": limit} - if before_lt is not None: - params["before_lt"] = before_lt - if start_date: - params["start_date"] = start_date - if end_date: - params["end_date"] = end_date - - headers = {"Accept-Language": accept_language} - response = self._get(method=method, params=params, headers=headers) - - return AccountEvents(**response) - - def get_subscriptions(self, account_id: str) -> Subscriptions: - """ - Get all subscriptions by wallet address - - :param account_id: account ID - :return: :class:`Subscriptions` - """ - method = f"v2/accounts/{account_id}/subscriptions" - response = self._get(method=method) - - return Subscriptions(**response) - - def reindex(self, account_id: str) -> bool: - """ - Update internal cache for a particular account - - :param account_id: account ID - :return: :class:`bool` - """ - method = f"v2/accounts/{account_id}/reindex" - response = self._post(method=method) - - return bool(response) - - def search_by_domain(self, name: str) -> FoundAccounts: - """ - Search by account domain name. - - :param name: domain name - :return: :class:`FoundAccounts` - """ - method = f"v2/accounts/search" - params = {"name": name} - response = self._get(method=method, params=params) - - return FoundAccounts(**response) - - def get_expiring_dns(self, account_id: str, period: Optional[int] = None) -> DnsExpiring: - """ - Get expiring account .ton dns. - - :param account_id: account ID - :param period: number of days before expiration - :return: :class:`DnsExpiring` - """ - method = f"v2/accounts/{account_id}/dns/expiring" - params = {"period": period} if period else {} - response = self._get(method=method, params=params) - - return DnsExpiring(**response) - - def get_public_key(self, account_id: str) -> PublicKey: - """ - Get public key by account id. - - :param account_id: account ID - :return: :class:`PublicKey` - """ - method = f"v2/accounts/{account_id}/publickey" - response = self._get(method=method) - - return PublicKey(**response) - - def get_account_multisigs(self, account_id: str) -> Multisigs: - """ - Get account's multisigs. - - :param account_id: account ID - :return: :class:`PublicKey` - """ - method = f"v2/accounts/{account_id}/multisigs" - response = self._get(method=method) - - return Multisigs(**response) - - def get_balance_change( - self, - account_id: str, - start_date: int, - end_date: int, - ) -> BalanceChange: - """ - Get account's balance change. - - :param account_id: account ID - :param start_date: start date - :param end_date: end date - :return: :class:`BalanceChange` - """ - method = f"v2/accounts/{account_id}/diff" - params = {"start_date": start_date, "end_date": end_date} - response = self._get(method=method, params=params) - - return BalanceChange(**response) - - def emulate_event( - self, - account_id: str, - body: Dict[str, Any], - accept_language: str = "en", - ignore_signature_check: Optional[bool] = None, - ) -> AccountEvent: - """ - Emulate sending message to blockchain. - - :param account_id: account ID - :param body: Request body with `boc`: both a single boc and a batch of boc serialized in base64 are accepted. - { - "boc": "base64 string" - } - :param accept_language: Default value: en - :param ignore_signature_check: Default value: None - """ - method = f"v2/accounts/{account_id}/events/emulate" - params = {"ignore_signature_check": ignore_signature_check} if ignore_signature_check is not None else {} - headers = {"Accept-Language": accept_language} - response = self._post(method=method, params=params, body=body, headers=headers) - - return AccountEvent(**response) diff --git a/pytonapi/tonapi/methods/blockchain.py b/pytonapi/tonapi/methods/blockchain.py deleted file mode 100644 index c5ffc45..0000000 --- a/pytonapi/tonapi/methods/blockchain.py +++ /dev/null @@ -1,271 +0,0 @@ -from typing import Optional, Dict, Any - -from pytonapi.schema.blockchain import ( - Transactions, - Transaction, - Validators, - BlockchainBlock, - BlockchainBlocks, - BlockchainBlockShards, - BlockchainAccountInspect, - BlockchainConfig, - BlockchainRawAccount, - MethodExecutionResult, - RawBlockchainConfig, - ReducedBlocks, -) -from pytonapi.tonapi.client import TonapiClientBase - - -class BlockchainMethod(TonapiClientBase): - - def get_reduced_blocks(self, from_: int, to_: int) -> ReducedBlocks: - """ - Get reduced blockchain blocks data. - - :param from_: from - :param to_: to - :return: :class:`ReducedBlocks` - """ - method = f"v2/blockchain/reduced/blocks" - params = {"from": from_, "to": to_} - response = self._get(method=method, params=params) - - return ReducedBlocks(**response) - - def get_block_data(self, block_id: str) -> BlockchainBlock: - """ - Get block data. - - :param block_id: block ID (string), example: "(-1,8000000000000000,4234234)" - :return: :class:`BlockchainBlock` - """ - method = f"v2/blockchain/blocks/{block_id}" - response = self._get(method=method) - - return BlockchainBlock(**response) - - def get_block(self, masterchain_seqno: int) -> BlockchainBlockShards: - """ - Get blockchain block shards. - - :param masterchain_seqno: masterchain block seqno - :return: :class:`BlockchainBlockShards` - """ - method = f"v2/blockchain/masterchain/{masterchain_seqno}/shards" - response = self._get(method=method) - - return BlockchainBlockShards(**response) - - def get_blocks(self, masterchain_seqno: int) -> BlockchainBlocks: - """ - Get all blocks in all shards and workchains between target - and previous masterchain block according to shards last blocks snapshot in masterchain. - We don't recommend to build your app around this method because - it has problem with scalability and will work very slow in the future. - - :param masterchain_seqno: masterchain block seqno - :return: :class:`BlockchainBlocks` - """ - method = f"v2/blockchain/masterchain/{masterchain_seqno}/blocks" - response = self._get(method=method) - - return BlockchainBlocks(**response) - - def get_transactions_shards(self, masterchain_seqno: int) -> Transactions: - """ - Get all transactions in all shards and workchains between target - and previous masterchain block according to shards last blocks snapshot in masterchain. - We don't recommend to build your app around this method because - it has problem with scalability and will work very slow in the future. - - :param masterchain_seqno: masterchain block seqno - :return: :class:`Transactions` - """ - method = f"v2/blockchain/masterchain/{masterchain_seqno}/transactions" - response = self._get(method=method) - - return Transactions(**response) - - def get_blockchain_config(self, masterchain_seqno: int) -> BlockchainConfig: - """ - Get blockchain config from a specific block, if present. - - :param masterchain_seqno: masterchain block seqno - :return: :class:`BlockchainConfig` - """ - method = f"v2/blockchain/masterchain/{masterchain_seqno}/config" - response = self._get(method=method) - - return BlockchainConfig(**response) - - def get_raw_blockchain_config(self, masterchain_seqno: int) -> RawBlockchainConfig: - """ - Get raw blockchain config from a specific block, if present. - - :param masterchain_seqno: masterchain block seqno - :return: :class:`RawBlockchainConfig` - """ - method = f"v2/blockchain/masterchain/{masterchain_seqno}/config/raw" - response = self._get(method=method) - - return RawBlockchainConfig(**response) - - def get_transaction_from_block(self, block_id: str) -> Transactions: - """ - Get transactions from block. - - :param block_id: block ID (string), example: "(-1,8000000000000000,4234234)" - :return: :class:`Transactions` - """ - method = f"v2/blockchain/blocks/{block_id}/transactions" - response = self._get(method=method) - - return Transactions(**response) - - def get_transaction_data(self, transaction_id: str) -> Transaction: - """ - Get transaction data. - - :param transaction_id: Transaction_id ID (string), - example: "97264395BD65A255A429B11326C84128B7D70FFED7949ABAE3036D506BA38621" - :return: :class:`Transaction` - """ - method = f"v2/blockchain/transactions/{transaction_id}" - response = self._get(method=method) - - return Transaction(**response) - - def get_transaction_by_message(self, msg_id: str) -> Transaction: - """ - Get transaction data by message hash - - :param msg_id: message ID - :return: :class:`Transaction` - """ - method = f"v2/blockchain/messages/{msg_id}/transaction" - response = self._get(method=method) - - return Transaction(**response) - - def get_validators(self) -> Validators: - """ - Get blockchain validators. - - :return: :class:`Validators` - """ - method = f"v2/blockchain/validators" - response = self._get(method=method) - - return Validators(**response) - - def get_last_masterchain_block(self) -> BlockchainBlock: - """ - Get last known masterchain block. - - :return: :class:`BlockchainBlock` - """ - method = f"v2/blockchain/masterchain-head" - response = self._get(method=method) - - return BlockchainBlock(**response) - - def get_account_info(self, account_id: str) -> BlockchainRawAccount: - """ - Get low-level information about an account taken directly from the blockchain. - - :param account_id: Account ID - :return: :class:`BlockchainRawAccount` - """ - method = f"v2/blockchain/accounts/{account_id}" - response = self._get(method=method) - - return BlockchainRawAccount(**response) - - def get_account_transactions( - self, - account_id: str, - after_lt: Optional[int] = None, - before_lt: Optional[int] = None, - limit: int = 100, - ) -> Transactions: - """ - Get account transactions. - - :param account_id: account ID - :param after_lt: omit this parameter to get last transactions - :param before_lt: omit this parameter to get last transactions - :param limit: Default value : 100 - :return: :class:`Transactions` - """ - method = f"v2/blockchain/accounts/{account_id}/transactions" - params = {"limit": limit} - if before_lt is not None: params["before_lt"] = before_lt # noqa E701 - if after_lt is not None: params["after_lt"] = after_lt # noqa E701 - response = self._get(method=method, params=params) - - return Transactions(**response) - - def execute_get_method( - self, - account_id: str, - method_name: str, - *args: Optional[str], - ) -> MethodExecutionResult: - """ - Execute get method for account. - - :param account_id: account ID - :param method_name: contract get method name - :param args: contract get method args - """ - method = f"v2/blockchain/accounts/{account_id}/methods/{method_name}" - query_params = "&".join(f"args={arg}" for arg in args) - if query_params: - method += f"?{query_params}" - response = self._get(method=method) - - return MethodExecutionResult(**response) - - def send_message(self, body: Dict[str, Any]) -> bool: - """ - Send message to blockchain. - - :param body: both a single boc and a batch of boc serialized in base64 are accepted - """ - method = "v2/blockchain/message" - response = self._post(method=method, body=body) - - return bool(response) - - def get_config(self) -> BlockchainConfig: - """ - Get blockchain config. - """ - method = "v2/blockchain/config" - response = self._get(method=method) - - return BlockchainConfig(**response) - - def get_raw_config(self) -> RawBlockchainConfig: - """ - Get raw blockchain config. - - :return: :class:`RawBlockchainConfig` - """ - method = "v2/blockchain/config/raw" - response = self._get(method=method) - - return RawBlockchainConfig(**response) - - def inspect_account(self, account_id: str) -> BlockchainAccountInspect: - """ - Blockchain account inspect. - - :param account_id: account ID - :return: :class:`BlockchainAccountInspect` - """ - method = f"v2/blockchain/accounts/{account_id}/inspect" - response = self._get(method=method) - - return BlockchainAccountInspect(**response) diff --git a/pytonapi/tonapi/methods/dns.py b/pytonapi/tonapi/methods/dns.py deleted file mode 100644 index f9fd53a..0000000 --- a/pytonapi/tonapi/methods/dns.py +++ /dev/null @@ -1,56 +0,0 @@ -from pytonapi.tonapi.client import TonapiClientBase - -from pytonapi.schema.dns import DNSRecord, Auctions -from pytonapi.schema.domains import DomainBids, DomainInfo - - -class DnsMethod(TonapiClientBase): - - def get_info(self, domain_name: str) -> DomainInfo: - """ - Get full information about domain name. - - :param domain_name: domain name with .ton or .t.me - :return: :class:`DomainInfo` - """ - method = f"v2/dns/{domain_name}" - response = self._get(method=method) - - return DomainInfo(**response) - - def resolve(self, domain_name: str) -> DNSRecord: - """ - DNS resolve for domain name. - - :param domain_name: domain name with .ton or .t.me - :return: :class:`DNSRecord` - """ - method = f"v2/dns/{domain_name}/resolve" - response = self._get(method=method) - - return DNSRecord(**response) - - def bids(self, domain_name: str) -> DomainBids: - """ - Get domain bids. - - :param domain_name: domain name with .ton or .t.me - :return: :class:`DomainBids` - """ - method = f"v2/dns/{domain_name}/bids" - response = self._get(method=method) - - return DomainBids(**response) - - def get_auctions(self, tld: str = "ton") -> Auctions: - """ - Get all auctions. - - :param tld: domain filter for current auctions "ton" or "t.me" - :return: :class:`Auctions` - """ - method = f"v2/dns/auctions" - params = {"tld": tld} - response = self._get(method=method, params=params) - - return Auctions(**response) diff --git a/pytonapi/tonapi/methods/emulate.py b/pytonapi/tonapi/methods/emulate.py deleted file mode 100644 index 4d90900..0000000 --- a/pytonapi/tonapi/methods/emulate.py +++ /dev/null @@ -1,139 +0,0 @@ -from typing import Dict, Any, Optional - -from pytonapi.schema.blockchain import DecodedMessage -from pytonapi.tonapi.client import TonapiClientBase -from pytonapi.schema.events import Event, AccountEvent, MessageConsequences -from pytonapi.schema.traces import Trace - - -class EmulateMethod(TonapiClientBase): - - def decode_message(self, body: Dict[str, Any]) -> DecodedMessage: - """ - Decode a given message. Only external incoming messages can be decoded currently. - - :param body: bag-of-cells serialized to base64 - example value: - { - "boc": "te6ccgECBQEAARUAAkWIAWTtae+KgtbrX26Bep8JSq8lFLfGOoyGR/xwdjfvpvEaHg" - } - :return: :class: `DecodedMessage` - """ - method = "v2/message/decode" - response = self._post( - method=method, - body=body, - ) - return DecodedMessage(**response) - - def emulate_events( - self, - body: Dict[str, Any], - accept_language: str = "en", - ignore_signature_check: Optional[bool] = None, - ) -> Event: - """ - Emulate sending message to blockchain. - - :param body: bag-of-cells serialized to base64 - example value: - { - "boc": "te6ccgECBQEAARUAAkWIAWTtae+KgtbrX26Bep8JSq8lFLfGOoyGR/xwdjfvpvEaHg" - } - :param accept_language: Default value : en - :param ignore_signature_check: Default value : None - :return: :class: `Event` - """ - method = "v2/events/emulate" - params = {"ignore_signature_check": ignore_signature_check} if ignore_signature_check else None - headers = {"Accept-Language": accept_language} - response = self._post( - method=method, - params=params, - body=body, - headers=headers, - ) - return Event(**response) - - def emulate_traces( - self, - body: Dict[str, Any], - ignore_signature_check: Optional[bool] = None, - ) -> Trace: - """ - Emulate sending message to blockchain. - - :param body: bag-of-cells serialized to base64 - example value: - { - "boc": "te6ccgECBQEAARUAAkWIAWTtae+KgtbrX26Bep8JSq8lFLfGOoyGR/xwdjfvpvEaHg" - } - :param ignore_signature_check: Default value : None - :return: :class: `Trace` - """ - method = "v2/traces/emulate" - params = {"ignore_signature_check": ignore_signature_check} if ignore_signature_check else None - response = self._post( - method=method, - params=params, - body=body, - ) - return Trace(**response) - - def emulate_wallet( - self, - body: Dict[str, Any], - accept_language: str = "en", - ) -> MessageConsequences: - """ - Emulate sending message to blockchain. - - :param body: bag-of-cells serialized to base64 and additional parameters to configure emulation - example value: - { - "boc": "te6ccgECBQEAARUAAkWIAWTtae+KgtbrX26Bep8JSq8lFLfGOoyGR/xwdjfvpvEaHg", - "params": [ - { - "address": "0:97146a46acc2654y27947f14c4a4b14273e954f78bc017790b41208b0043200b", - "balance": 10000000000 - } - ] - } - :param accept_language: Default value : en - :return: :class: `MessageConsequences` - """ - method = "v2/wallet/emulate" - headers = {"Accept-Language": accept_language} - response = self._post( - method=method, - body=body, - headers=headers, - ) - return MessageConsequences(**response) - - def emulate_account_event( - self, - account_id: str, - body: Dict[str, Any], - accept_language: str = "en", - ) -> AccountEvent: - """ - Emulate sending message to blockchain. - - :param account_id: account ID - :param body: bag-of-cells serialized to base64 - example value: - { - "boc": "te6ccgECBQEAARUAAkWIAWTtae+KgtbrX26Bep8JSq8lFLfGOoyGR/xwdjfvpvEaHg" - } - :param accept_language: Default value : en - :return: :class: `AccountEvent` - """ - method = f"v2/accounts/{account_id}/events/emulate" - headers = {"Accept-Language": accept_language} - response = self._post( - method=method, - body=body, - headers=headers, - ) - return AccountEvent(**response) diff --git a/pytonapi/tonapi/methods/events.py b/pytonapi/tonapi/methods/events.py deleted file mode 100644 index 388e1b4..0000000 --- a/pytonapi/tonapi/methods/events.py +++ /dev/null @@ -1,56 +0,0 @@ -import base64 -import binascii -from typing import Any, Dict, Optional - -from pytonapi.schema.events import Event -from pytonapi.tonapi.client import TonapiClientBase - - -class EventsMethod(TonapiClientBase): - - def get_event(self, event_id: str, accept_language: str = "en") -> Event: - """ - Get an event either by event ID or a hash of any transaction in a trace. - An event is built on top of a trace which is a series of transactions caused - by one inbound message. TonAPI looks for known patterns inside the trace and - splits the trace into actions, where a single action represents a meaningful - high-level operation like a Jetton Transfer or an NFT Purchase. - Actions are expected to be shown to users. It is advised not to build any logic - on top of actions because actions can be changed at any time. - - :param event_id: event ID - :param accept_language: Default value : en - :return: :class:`Event` - """ - if len(event_id) == 44: - decoded = base64.urlsafe_b64decode(event_id + "=" * (-len(event_id) % 4)) - event_id = binascii.hexlify(decoded).decode("utf-8") - method = f"v2/events/{event_id}" - headers = {"Accept-Language": accept_language} - response = self._get(method=method, headers=headers) - - return Event(**response) - - def emulate( - self, body: Dict[str, Any], - accept_language: str = "en", - ignore_signature_check: Optional[bool] = None, - ) -> Event: - """ - Emulate sending message to blockchain. - - :param body: bag-of-cells serialized to base64 - example value: - { - "boc": "te6ccgECBQEAARUAAkWIAWTtae+KgtbrX26Bep8JSq8lFLfGOoyGR/xwdjfvpvEaHg" - } - :param accept_language: Default value : en - :param ignore_signature_check: Default value : None - :return: :class: `Event` - """ - method = "v2/events/emulate" - params = {"ignore_signature_check": ignore_signature_check} if ignore_signature_check else {} - headers = {"Accept-Language": accept_language} - response = self._post(method=method, params=params, body=body, headers=headers) - - return Event(**response) diff --git a/pytonapi/tonapi/methods/gasless.py b/pytonapi/tonapi/methods/gasless.py deleted file mode 100644 index 17ef30c..0000000 --- a/pytonapi/tonapi/methods/gasless.py +++ /dev/null @@ -1,56 +0,0 @@ -from typing import Dict, Any - -from pytonapi.schema.gasless import GaslessConfig, SignRawParams -from pytonapi.tonapi.client import TonapiClientBase - - -class GaslessMethod(TonapiClientBase): - - def get_config(self) -> GaslessConfig: - """ - Returns configuration of gasless transfers. - - :return: :class:`GaslessConfig` - """ - method = "v2/gasless/config" - response = self._get(method=method) - - return GaslessConfig(**response) - - def estimate_gas_price(self, master_id: str, body: Dict[str, Any]) -> SignRawParams: - """ - Returns estimated gas price. - - :param master_id: Jetton Master ID. - :param body: The body should contain a JSON object with the following structure: - { - "wallet_address": "string", - "wallet_public_key": "string", - "messages": [ - { - "boc": "string" - } - ] - } - :return: :class:`int` - """ - method = f"v2/gasless/estimate/{master_id}" - response = self._post(method=method, body=body) - - return SignRawParams(**response) - - def send(self, body: Dict[str, Any]) -> bool: - """ - Send message to blockchain. - - :param body: The body should contain a JSON object with the following structure: - { - "wallet_public_key": "string", # The public key of the wallet. - "boc": "string" # A single BOC or a batch of BOCs serialized in base64. - } - :return: bool - """ - method = "v2/gasless/send" - response = self._post(method=method, body=body) - - return bool(response) diff --git a/pytonapi/tonapi/methods/inscriptions.py b/pytonapi/tonapi/methods/inscriptions.py deleted file mode 100644 index 55ab0cc..0000000 --- a/pytonapi/tonapi/methods/inscriptions.py +++ /dev/null @@ -1,116 +0,0 @@ -from typing import Dict, Optional, Literal - -from pytonapi.schema.events import AccountEvents -from pytonapi.schema.inscriptions import InscriptionBalances -from pytonapi.tonapi.client import TonapiClientBase - - -class InscriptionsMethod(TonapiClientBase): - - def get_all_inscriptions( - self, - account_id: str, - limit: int = 1000, - offset: int = 0, - ) -> InscriptionBalances: - """ - Get all inscriptions by owner address. It's experimental API and can be dropped in the future. - - :param account_id: account address - :param limit: Default value : 1000 - :param offset: Default value : 0 - :return: :class:`InscriptionBalances` - """ - method = f"v2/experimental/accounts/{account_id}/inscriptions" - params = {"limit": limit, "offset": offset} - response = self._get(method=method, params=params) - - return InscriptionBalances(**response) - - def get_inscription_history( - self, - account_id: str, - before_lt: Optional[int] = None, - limit: int = 100, - accept_language: str = "en", - ) -> AccountEvents: - """ - Get the transfer inscriptions history for account. It's experimental API and can be dropped in the future. - - :param account_id: account address - :param before_lt: Default value : 0 - :param limit: Default value : 100 - :param accept_language: Default value : en - """ - method = f"v2/experimental/accounts/{account_id}/inscriptions/history" - params = {"limit": limit} - if before_lt is not None: - params["before_lt"] = before_lt - headers = {"Accept-Language": accept_language} - response = self._get(method=method, params=params, headers=headers) - - return AccountEvents(**response) - - def get_inscription_history_by_ticker( - self, - account_id: str, - ticker: str, - before_lt: Optional[int] = None, - limit: int = 100, - accept_language: str = "en", - ) -> AccountEvents: - """ - Get the transfer inscriptions history for account. It's experimental API and can be dropped in the future. - - :param account_id: account address - :param ticker: token ticker - :param before_lt: Default value : 0 - :param limit: Default value : 100 - :param accept_language: Default value : en - """ - method = f"v2/experimental/accounts/{account_id}/inscriptions/{ticker}/history" - params = {"limit": limit} - if before_lt is not None: - params["before_lt"] = before_lt - headers = {"Accept-Language": accept_language} - response = self._get(method=method, params=params, headers=headers) - - return AccountEvents(**response) - - def create_inscription_comment( - self, - who: str, - amount: int, - type_: Literal["ton20", "gram20"] = "ton20", - destination: Optional[str] = None, - comment: Optional[str] = None, - operation: Literal["transfer"] = "transfer", - ticker: str = "nano", - ) -> Dict[str, str]: - """ - Return comment for making operation with inscription. please don"t use it if you don"t know what you are doing. - - :param who: account address - :param amount: amount of tokens - :param type_: Default value : ton20 - :param destination: Default value : None - :param comment: Default value : None - :param operation: Default value : transfer - :param ticker: Default value : nano - :return: Dict[str, str] - """ - method = "v2/experimental/inscriptions/op-template" - params = { - "who": who, - "amount": amount, - "type": type_, - "operation": operation, - "ticker": ticker, - } - if destination: - params["destination"] = destination - if comment: - params["comment"] = comment - response = self._get(method=method, params=params) - - return response diff --git a/pytonapi/tonapi/methods/jettons.py b/pytonapi/tonapi/methods/jettons.py deleted file mode 100644 index e5fdfeb..0000000 --- a/pytonapi/tonapi/methods/jettons.py +++ /dev/null @@ -1,96 +0,0 @@ -from typing import List - -from pytonapi.schema.events import Event -from pytonapi.schema.jettons import JettonInfo, JettonHolders, Jettons, JettonHolder, JettonTransferPayload -from pytonapi.tonapi.client import TonapiClientBase - - -class JettonsMethod(TonapiClientBase): - - def get_info(self, account_id: str) -> JettonInfo: - """ - Get jetton metadata by jetton master address. - - :param account_id: Account ID - :return: JettonInfo - """ - method = f"v2/jettons/{account_id}" - response = self._get(method=method) - - return JettonInfo(**response) - - def get_holders(self, account_id: str, limit: int = 1000, offset: int = 0) -> JettonHolders: - """ - Get jetton"s holders. - - :param account_id: Account ID - :param limit: Default value - 1000 - :param offset: Default value - 0 - :return: JettonHolders - """ - method = f"v2/jettons/{account_id}/holders" - params = {"limit": limit, "offset": offset} - response = self._get(method=method, params=params) - - return JettonHolders(**response) - - def get_all_holders(self, account_id: str) -> JettonHolders: - """ - Get all jetton's holders. - - :param account_id: Account ID - :return: :class:`JettonHolders` - """ - jetton_holders: List[JettonHolder] = [] - offset, limit = 0, 1000 - - while True: - result = self.get_holders( - account_id=account_id, limit=limit, offset=offset, - ) - jetton_holders += result.addresses - offset += limit - - if len(result.addresses) == 0: - break - - return JettonHolders(addresses=jetton_holders, total=result.total) - - def get_all_jettons(self, limit: int = 100, offset: int = 0) -> Jettons: - """ - Get a list of all indexed jetton masters in the blockchain. - - :param limit: Default value - 100 - :param offset: Default value - 0 - :return: :class:`Jettons` - """ - method = "v2/jettons" - params = {"limit": limit, "offset": offset} - response = self._get(method=method, params=params) - - return Jettons(**response) - - def get_jetton_transfer_event(self, event_id: str) -> Event: - """ - Get only jetton transfers in the event. - - :param event_id: event ID or transaction hash in hex (without 0x) or base64url format - :return: :class:`Event` - """ - method = f"v2/events/{event_id}/jettons" - response = self._get(method=method) - - return Event(**response) - - def get_jetton_transfer_payload(self, jetton_id: str, account_id: str) -> JettonTransferPayload: - """ - Get jetton's custom payload and state init required for transfer. - - :param jetton_id: jetton ID - :param account_id: account ID - :return: :class:`Event` - """ - method = f"v2/jettons/{jetton_id}/transfer/{account_id}/payload" - response = self._get(method=method) - - return JettonTransferPayload(**response) diff --git a/pytonapi/tonapi/methods/liteserver.py b/pytonapi/tonapi/methods/liteserver.py deleted file mode 100644 index 9441889..0000000 --- a/pytonapi/tonapi/methods/liteserver.py +++ /dev/null @@ -1,273 +0,0 @@ -from typing import Dict, Any, Optional, Union - -from pytonapi.schema.liteserver import ( - RawMasterChainInfo, - RawMasterChainInfoExt, - RawGetBlock, - RawBlockState, - RawBlockHeader, - RawAccountState, - RawShardInfo, - RawShardsInfo, - RawTransactions, - RawListBlockTransactions, - RawBlockProof, - RawConfig, - RawShardProof, - OutMsgQueueSize, -) -from pytonapi.tonapi.client import TonapiClientBase - - -class LiteserverMethod(TonapiClientBase): - - def get_masterchain_info(self) -> RawMasterChainInfo: - """ - Get raw masterchain info. - - :return: :class:`RawMasterChainInfo` - """ - method = "v2/liteserver/get_masterchain_info" - response = self._get(method=method) - - return RawMasterChainInfo(**response) - - def get_masterchain_info_ext(self, mode: int) -> RawMasterChainInfoExt: - """ - Get raw masterchain info ext - - :return: :class:`RawMasterChainInfoExt` - """ - method = "v2/liteserver/get_masterchain_info_ext" - params = {"mode": mode} - response = self._get(method, params=params) - - return RawMasterChainInfoExt(**response) - - def get_time(self) -> Union[int, None]: - """ - Get raw time. - - :return: :class:`int` time - """ - method = "v2/liteserver/get_time" - response = self._get(method=method) - - return response.get("time", None) - - def get_raw_block(self, block_id: str) -> RawGetBlock: - """ - Get raw blockchain block. - - :param block_id: block ID: (workchain,shard,seqno,root_hash,file_hash) - :return: :class:`RawGetBlock` - """ - method = f"v2/liteserver/get_block/{block_id}" - response = self._get(method=method) - - return RawGetBlock(**response) - - def get_raw_state(self, block_id: str) -> RawBlockState: - """ - Get raw blockchain block state. - - :param block_id: block ID: (workchain,shard,seqno,root_hash,file_hash) - :return: :class:`RawBlockState` - """ - method = f"v2/liteserver/get_state/{block_id}" - response = self._get(method=method) - - return RawBlockState(**response) - - def get_raw_header(self, block_id: str) -> RawBlockHeader: - """ - Get raw blockchain block header. - - :param block_id: block ID: (workchain,shard,seqno,root_hash,file_hash) - :return: :class:`RawBlockHeader` - """ - method = f"v2/liteserver/get_block_header/{block_id}" - response = self._get(method=method) - - return RawBlockHeader(**response) - - def send_message(self, body: Dict[str, Any]) -> Union[int, None]: - """ - Send raw message to blockchain. - - :param body: Data that is expected - :return: :class:`int` code - """ - method = "v2/liteserver/send_message" - response = self._post(method=method, body=body) - - return response.get("code", None) - - def get_account_state( - self, - account_address: str, - target_block: Optional[str] = None, - ) -> RawAccountState: - """ - Get raw account state. - - :param account_address: account ID - :param target_block: target block: (workchain,shard,seqno,root_hash,file_hash) - :return: :class:`RawAccountState` - """ - method = f"v2/liteserver/get_account_state/{account_address}" - params = {"target_block": target_block} if target_block else {} - response = self._get(method=method, params=params) - - return RawAccountState(**response) - - def get_shard_info( - self, - block_id: str, - workchain: int, - shard: int, - exact: bool = False, - ) -> RawShardInfo: - """ - Get raw shard info. - - :param block_id: block ID: (workchain,shard,seqno,root_hash,file_hash) - :param workchain: workchain - :param shard: shard - :param exact: exact flag - :return: :class:`RawShardInfo` - """ - method = f"v2/liteserver/get_shard_info/{block_id}" - params = {"workchain": workchain, "shard": shard, "exact": exact} - response = self._get(method=method, params=params) - - return RawShardInfo(**response) - - def get_all_raw_shards_info(self, block_id: str) -> RawShardsInfo: - """ - Get all raw shards info. - - :param block_id: block ID: (workchain,shard,seqno,root_hash,file_hash) - :return: :class:`RawShardsInfo` - """ - method = f"v2/liteserver/get_all_shards_info/{block_id}" - response = self._get(method=method) - - return RawShardsInfo(**response) - - def get_raw_transactions( - self, - account_id: str, - lt: int, - hash_: str, - count: int = 100, - ) -> RawTransactions: - """ - Get raw transactions. - - :param account_id: account ID - :param lt: lt - :param hash_: hash - :param count: count - :return: :class:`RawTransactions` - """ - method = f"v2/liteserver/get_transactions/{account_id}" - params = {"lt": lt, "hash": hash_, "count": count} - response = self._get(method=method, params=params) - - return RawTransactions(**response) - - def get_raw_list_block_transaction( - self, - block_id: str, - mode: int, - count: int = 100, - account_id: Optional[str] = None, - lt: Optional[int] = None, - ) -> RawListBlockTransactions: - """ - Get raw list block transaction. - - :param block_id: block ID: (workchain,shard,seqno,root_hash,file_hash) - :param mode: mode - :param count: count - :param account_id: account ID - :param lt: lt - :return: :class:`RawListBlockTransactions` - """ - method = f"v2/liteserver/get_block_transactions/{block_id}" - params = { - "mode": mode, - "count": count, - "account_id": account_id, - "lt": lt - } - response = self._get(method=method, params=params) - - return RawListBlockTransactions(**response) - - def get_block_proof( - self, - know_block: str, - mode: int = 0, - target_block: Optional[str] = None, - ) -> RawBlockProof: - """ - Get raw block proof. - - :param know_block: know block: (workchain,shard,seqno,root_hash,file_hash) - :param mode: mode 0 - :param target_block: target block: (workchain,shard,seqno,root_hash,file_hash) - :return: :class:`RawBlockProof` - """ - method = f"v2/liteserver/get_block_proof/{know_block}" - params = { - "know_block": know_block, - "mode": mode, - } - if target_block: - params["target_block"] = target_block - response = self._get(method=method, params=params) - - return RawBlockProof(**response) - - def get_config_all( - self, - block_id: str, - mode: int = 0, - ) -> RawConfig: - """ - Get raw config. - - :param block_id: block ID: (workchain,shard,seqno,root_hash,file_hash) - :param mode: mode - :return: :class:`RawConfig` - """ - method = f"v2/liteserver/get_config_all/{block_id}" - params = {"mode": mode} - response = self._get(method=method, params=params) - - return RawConfig(**response) - - def get_shard_block_proof(self, block_id: str) -> RawShardProof: - """ - Get raw shard block proof. - - :param block_id: block ID: (workchain,shard,seqno,root_hash,file_hash) - :return: :class:`RawShardProof` - """ - method = f"v2/liteserver/get_shard_block_proof/{block_id}" - response = self._get(method=method) - - return RawShardProof(**response) - - def get_out_msg_queue_size(self) -> OutMsgQueueSize: - """ - Get out message queue sizeGet out msg queue sizes. - - :return: :class:`OutMsgQueueSize` size - """ - method = "v2/liteserver/get_out_msg_queue_size" - response = self._get(method=method) - - return OutMsgQueueSize(**response) diff --git a/pytonapi/tonapi/methods/multisig.py b/pytonapi/tonapi/methods/multisig.py deleted file mode 100644 index e946ea5..0000000 --- a/pytonapi/tonapi/methods/multisig.py +++ /dev/null @@ -1,17 +0,0 @@ -from pytonapi.schema.multisig import Multisig -from pytonapi.tonapi.client import TonapiClientBase - - -class MultisigMethod(TonapiClientBase): - - def get_account_info(self, account_id: str) -> Multisig: - """ - Get multisig account info. - - :param account_id: account ID - :return: :class:`AddressForm` - """ - method = f"v2/multisig/{account_id}" - response = self._get(method=method) - - return Multisig(**response) diff --git a/pytonapi/tonapi/methods/nft.py b/pytonapi/tonapi/methods/nft.py deleted file mode 100644 index 1378408..0000000 --- a/pytonapi/tonapi/methods/nft.py +++ /dev/null @@ -1,134 +0,0 @@ -from typing import List, Optional - -from pytonapi.schema.events import AccountEvents -from pytonapi.schema.nft import NftCollections, NftCollection, NftItems, NftItem -from pytonapi.tonapi.client import TonapiClientBase - - -class NftMethod(TonapiClientBase): - - def get_collections(self, limit: int = 15, offset: int = 0) -> NftCollections: - """ - Get NFT collections. - - :param limit: Default value : 15 - :param offset: Default value : 0 - :return: :class:`NftCollections` - """ - method = "v2/nfts/collections" - params = {"limit": limit, "offset": offset} - response = self._get(method=method, params=params) - - return NftCollections(**response) - - def get_collection_by_collection_address(self, account_id: str) -> NftCollection: - """ - Get NFT collection by collection address. - - :param account_id: Account ID - :return: :class:`NftCollection` - """ - method = f"v2/nfts/collections/{account_id}" - response = self._get(method=method) - - return NftCollection(**response) - - def get_items_by_collection_address( - self, - account_id: str, - limit: int = 1000, - offset: int = 0, - ) -> NftItems: - """ - Get NFT items from collection by collection address. - - :param account_id: Account ID - :param limit: Default value: 1000 - :param offset: Default value: 0 - :return: :class:`NftItems` - """ - method = f"v2/nfts/collections/{account_id}/items" - params = {"limit": limit, "offset": offset} - response = self._get(method=method, params=params) - - return NftItems(**response) - - def get_all_items_by_collection_address(self, account_id: str) -> NftItems: - """ - Get all NFT items from collection by collection address. - - :param account_id: Account ID - :return: :class:`NftItems` - """ - nft_items: List[NftItem] = [] - offset, limit = 0, 1000 - - while True: - result = self.get_items_by_collection_address( - account_id=account_id, limit=limit, offset=offset, - ) - nft_items += result.nft_items - offset += limit - - if len(result.nft_items) == 0: - break - - return NftItems(nft_items=nft_items) - - def get_item_by_address(self, account_id: str) -> NftItem: - """ - Get NFT item by its address. - - :param account_id: Account ID - :return: :class:`NftItem` - """ - method = f"v2/nfts/{account_id}" - response = self._get(method=method) - - return NftItem(**response) - - def get_bulk_items(self, account_ids: List[str]) -> NftItems: - """ - Get NFT items by their addresses. - - :param account_ids: A list of account IDs - :return: :class:`NftItems` - """ - method = f"v2/nfts/_bulk" - params = {"account_ids": account_ids} - response = self._post(method=method, body=params) - - return NftItems(**response) - - def get_nft_history( - self, - account_id: str, - limit: int = 100, - before_lt: Optional[int] = None, - accept_language: str = "en", - start_date: Optional[int] = None, - end_date: Optional[int] = None, - ) -> AccountEvents: - """ - Get the transfer NFTs history for account. - - :param account_id: Account ID - :param limit: Default value: 100 - :param before_lt: Default value: None (omit this parameter to get last events) - :param accept_language: Default value: en - :param start_date: Default value: None - :param end_date: Default value: None - :return: :class:`AccountEvents` - """ - method = f"v2/nfts/{account_id}/history" - params = {"limit": limit} - if before_lt is not None: - params["before_lt"] = before_lt - if start_date: - params["start_date"] = start_date - if end_date: - params["end_date"] = end_date - headers = {"Accept-Language": accept_language} - response = self._get(method=method, params=params, headers=headers) - - return AccountEvents(**response) diff --git a/pytonapi/tonapi/methods/rates.py b/pytonapi/tonapi/methods/rates.py deleted file mode 100644 index c022ca3..0000000 --- a/pytonapi/tonapi/methods/rates.py +++ /dev/null @@ -1,58 +0,0 @@ -from typing import List, Optional - -from pytonapi.schema.rates import Rates, ChartRates, MarketsTonRates -from pytonapi.tonapi.client import TonapiClientBase - - -class RatesMethod(TonapiClientBase): - - def get_prices(self, tokens: List[str], currencies: List[str]) -> Rates: - """ - Get the token price to the currency. - - :param tokens: Accept TON and jetton master addresses, example: - ["TON", "EQBCFwW8uFUh-amdRmNY9NyeDEaeDYXd9ggJGsicpqVcHq7B"] - :param currencies: Accept TON and all possible fiat currencies, example: - ["TON","USD", "RUB"] - :return: :class:`Rates` - """ - params = { - "tokens": ",".join(map(str, tokens)), - "currencies": ",".join(map(str, currencies)), - } - method = f"v2/rates" - response = self._get(method=method, params=params) - - return Rates(**response) - - def get_chart(self, token: str, currency: Optional[str] = "usd", - start_date: Optional[str] = None, end_date: Optional[str] = None - ) -> ChartRates: - """ - Get the token price to the currency. - - :param token: accept jetton master address - :param currency: accept fiat currency, example: "USD", "RUB" and so on - :param start_date: start date - :param end_date: end date - :return: :class:`ChartRates` - """ - params = {"token": token, "currency": currency} - if start_date: params["start_date"] = start_date # noqa:E701 - if end_date: params["end_date"] = end_date # noqa:E701 - method = f"v2/rates/chart" - response = self._get(method=method, params=params) - - return ChartRates(**response) - - - def get_ton_price_from_markets(self) -> MarketsTonRates: - """ - Get the TON price from markets. - - :return: :class:`MarketsTonRates` - """ - method = f"v2/rates/markets" - response = self._get(method=method) - - return MarketsTonRates(**response) diff --git a/pytonapi/tonapi/methods/sse.py b/pytonapi/tonapi/methods/sse.py deleted file mode 100644 index 39aac0b..0000000 --- a/pytonapi/tonapi/methods/sse.py +++ /dev/null @@ -1,103 +0,0 @@ -import json -from typing import List, Callable, Any, Tuple, Optional - -from pytonapi.schema.events import TransactionEventData, TraceEventData, MempoolEventData, BlockEventData -from pytonapi.tonapi.client import TonapiClientBase - - -class SSEMethod(TonapiClientBase): - - def subscribe_to_transactions( - self, - handler: Callable[[TransactionEventData, Any], Any], - accounts: List[str], - operations: Optional[List[str]] = None, - args: Tuple = (), - ) -> None: - """ - Subscribes to transactions SSE events for the specified accounts. - - :param handler: A callable function to handle the SSEEvent - :param accounts: A comma-separated list of account IDs. - A special value of "accounts" is ALL. TonAPI will stream transactions for all accounts in this case. - :param operations: A comma-separated list of operations, which makes it possible - to get transactions based on the `first 4 bytes of a message body of an inbound message(opens in a new tab) - `_. - Each operation is a string containing either one of the supported names or a hex string - representing a message operation opcode which is an unsigned 32-bit integer. - A hex string must start with "0x" prefix and have exactly 8 hex digits. - An example of "operations" is &operations=JettonTransfer,0x0524c7ae,StonfiSwap. - The advantage of using hex strings is that it"s possible to get transactions for operations - that are not yet present on `the list `_. - :param args: Additional arguments to pass to the handler - """ - method = "v2/sse/accounts/transactions" - params = {"accounts": ",".join(accounts)} - if operations: - params["operations"] = ",".join(operations) - - for data in self._subscribe(method=method, params=params): - event = TransactionEventData(**json.loads(data)) - result = handler(event, *args) - if result is not None: - return result - - def subscribe_to_traces( - self, - handler: Callable[[TraceEventData, Any], Any], - accounts: List[str], - args: Tuple = (), - ) -> None: - """ - Subscribes to traces SSE events for the specified accounts. - - :handler: A callable function to handle the SSEEvent - :accounts: A list of account addresses to subscribe to - """ - method = "v2/sse/accounts/traces" - params = {"accounts": accounts} - for data in self._subscribe(method=method, params=params): - event = TraceEventData(**json.loads(data)) - result = handler(event, *args) - if result is not None: - return result - - def subscribe_to_mempool( - self, - handler: Callable[[MempoolEventData, Any], Any], - accounts: List[str], - args: Tuple = (), - ) -> None: - """ - Subscribes to mempool SSE events for the specified accounts. - - :handler: A callable function to handle the SSEEvent - :accounts: A list of account addresses to subscribe to - """ - method = "v2/sse/mempool" - params = {"accounts": ",".join(accounts)} - for data in self._subscribe(method=method, params=params): - event = MempoolEventData(**json.loads(data)) - result = handler(event, *args) - if result is not None: - return result - - def subscribe_to_blocks( - self, - handler: Callable[[BlockEventData, Any], Any], - workchain: Optional[int] = None, - args: Tuple = (), - ) -> Any: - """ - Subscribes to blocks SSE events for the specified workchains. - - :handler: A callable function to handle the SSEEvent - :workchain: The ID of the workchain to subscribe to. If None, subscribes to all workchains. - """ - method = "v2/sse/blocks" - params = {} if workchain is None else {"workchain": workchain} - for data in self._subscribe(method=method, params=params): - event = BlockEventData(**json.loads(data)) - result = handler(event, *args) - if result is not None: - return result diff --git a/pytonapi/tonapi/methods/staking.py b/pytonapi/tonapi/methods/staking.py deleted file mode 100644 index 154a9bf..0000000 --- a/pytonapi/tonapi/methods/staking.py +++ /dev/null @@ -1,65 +0,0 @@ -from typing import Optional - -from pytonapi.tonapi.client import TonapiClientBase -from pytonapi.schema.staking import (StakingPoolInfo, AccountStaking, - StakingPoolHistory, StakingPools) - - -class StakingMethod(TonapiClientBase): - - def get_participating_pools(self, account_id: str) -> AccountStaking: - """ - All pools where account participates. - - :param account_id: account ID - :return: :class:`AccountStaking` - """ - method = f"v2/staking/nominator/{account_id}/pools" - response = self._get(method=method) - - return AccountStaking(**response) - - def get_pool_info(self, account_id: str, accept_language: str = "en") -> StakingPoolInfo: - """ - Stacking pool info. - - :param account_id: account ID - :param accept_language: Default value : en - :return: :class:`StakingPoolInfo` - """ - method = f"v2/staking/pool/{account_id}" - headers = {"Accept-Language": accept_language} - response = self._get(method=method, headers=headers) - - return StakingPoolInfo(**response) - - def get_pool_history(self, account_id: str) -> StakingPoolHistory: - """ - Stacking pool history. - - :param account_id: account ID - :return: :class:`StakingPoolHistory` - """ - method = f"v2/staking/pool/{account_id}/history" - response = self._get(method=method) - - return StakingPoolHistory(**response) - - def get_all_network_pools(self, available_for: str, include_unverified: Optional[bool] = False, - accept_language: str = "en") -> StakingPools: - """ - All pools available in network. - - :param available_for: account ID - :param include_unverified: return also pools not from - white list - just compatible by interfaces (maybe dangerous!) - :param accept_language: Default value : en - :return: :class:`StakingPools` - """ - method = f"v2/staking/pools" - params = {"available_for": available_for, - "include_unverified": "true" if include_unverified else "false"} - headers = {"Accept-Language": accept_language} - response = self._get(method=method, params=params, headers=headers) - - return StakingPools(**response) diff --git a/pytonapi/tonapi/methods/storage.py b/pytonapi/tonapi/methods/storage.py deleted file mode 100644 index 2b2dbe4..0000000 --- a/pytonapi/tonapi/methods/storage.py +++ /dev/null @@ -1,16 +0,0 @@ -from pytonapi.tonapi.client import TonapiClientBase -from pytonapi.schema.storage import StorageProviders - - -class StorageMethod(TonapiClientBase): - - def get_providers(self) -> StorageProviders: - """ - Get TON storage providers deployed to the blockchain. - - :return: :class:`StorageProviders` - """ - method = f"v2/storage/providers" - response = self._get(method=method) - - return StorageProviders(**response) diff --git a/pytonapi/tonapi/methods/tonconnect.py b/pytonapi/tonapi/methods/tonconnect.py deleted file mode 100644 index 36d8f04..0000000 --- a/pytonapi/tonapi/methods/tonconnect.py +++ /dev/null @@ -1,28 +0,0 @@ -from pytonapi.tonapi.client import TonapiClientBase -from pytonapi.schema.tonconnect import TonconnectPayload, AccountInfoByStateInit - - -class TonconnectMethod(TonapiClientBase): - - def get_payload(self) -> TonconnectPayload: - """ - Get a payload for further token receipt. - - :return: :class:`TonconnectPayload` - """ - method = "v2/tonconnect/payload" - response = self._get(method=method) - - return TonconnectPayload(**response) - - def get_info_by_state_init(self, state_init: str) -> AccountInfoByStateInit: - """ - Get account info by state init. - - :return: :class:`AccountInfoByStateInit` - """ - method = "v2/tonconnect/stateinit" - body = {"state_init": state_init} - response = self._post(method=method, body=body) - - return AccountInfoByStateInit(**response) diff --git a/pytonapi/tonapi/methods/traces.py b/pytonapi/tonapi/methods/traces.py deleted file mode 100644 index 8411973..0000000 --- a/pytonapi/tonapi/methods/traces.py +++ /dev/null @@ -1,42 +0,0 @@ -import base64 -import binascii -from typing import Any, Dict, Optional - -from pytonapi.schema.traces import Trace -from pytonapi.tonapi.client import TonapiClientBase - - -class TracesMethod(TonapiClientBase): - - def get_trace(self, trace_id: str) -> Trace: - """ - Get the trace by trace ID or hash of any transaction in trace. - - :param trace_id: trace ID or transaction hash in hex (without 0x) or base64url format - :return: :class:`Trace` - """ - if len(trace_id) == 44: - decoded = base64.urlsafe_b64decode(trace_id + "=" * (-len(trace_id) % 4)) - trace_id = binascii.hexlify(decoded).decode("utf-8") - method = f"v2/traces/{trace_id}" - response = self._get(method=method) - - return Trace(**response) - - def emulate(self, body: Dict[str, Any], ignore_signature_check: Optional[bool] = None) -> Trace: - """ - Emulate sending message to blockchain. - - :param body: bag-of-cells serialized to base64 - example value: - { - "boc": "te6ccgECBQEAARUAAkWIAWTtae+KgtbrX26Bep8JSq8lFLfGOoyGR/xwdjfvpvEaHg" - } - :param ignore_signature_check: Default value : None - :return: :class: `Trace` - """ - method = "v2/traces/emulate" - params = {"ignore_signature_check": ignore_signature_check} if ignore_signature_check else {} - response = self._post(method=method, params=params, body=body) - - return Trace(**response) diff --git a/pytonapi/tonapi/methods/utilites.py b/pytonapi/tonapi/methods/utilites.py deleted file mode 100644 index 3b31231..0000000 --- a/pytonapi/tonapi/methods/utilites.py +++ /dev/null @@ -1,28 +0,0 @@ -from pytonapi.schema.utilites import AddressForm, ServiceStatus -from pytonapi.tonapi.client import TonapiClientBase - - -class UtilitiesMethod(TonapiClientBase): - - def parse_address(self, account_id: str) -> AddressForm: - """ - parse address and display in all formats. - - :param account_id: account ID - :return: :class:`AddressForm` - """ - method = f"v2/address/{account_id}/parse" - response = self._get(method=method) - - return AddressForm(**response) - - def status(self) -> ServiceStatus: - """ - Reduce indexing latency. - - :return: :class:`ServiceStatus` - """ - method = "v2/status" - response = self._get(method=method) - - return ServiceStatus(**response) diff --git a/pytonapi/tonapi/methods/wallet.py b/pytonapi/tonapi/methods/wallet.py deleted file mode 100644 index b004def..0000000 --- a/pytonapi/tonapi/methods/wallet.py +++ /dev/null @@ -1,106 +0,0 @@ -from typing import Dict, Any, Union - -from pytonapi.schema.accounts import Accounts -from pytonapi.schema.events import MessageConsequences -from pytonapi.tonapi.client import TonapiClientBase - - -class WalletMethod(TonapiClientBase): - - def get_backup_info( - self, - x_tonconnect_auth: str, - ) -> Union[str, None]: - """ - Get backup info. - - :param x_tonconnect_auth: X-TonConnect-Auth - :return: :class:`str` dump - """ - method = "v2/wallet/backup" - headers = {"X-TonConnect-Auth": x_tonconnect_auth} - response = self._get(method=method, headers=headers) - - return response.get("dump", None) - - def account_verification( - self, - body: Dict[str, Any], - ) -> Union[str, None]: - """ - Account verification and token issuance. - - :param body: Data that is expected from TON Connect - example value: - { - "address": "0:97146a46acc2654y27947f14c4a4b14273e954f78bc017790b41208b0043200b", - "proof": { - "timestamp": 1678275313, - "domain": { - "length_bytes": 0, - "value": "string" - }, - "signature": "string", - "payload": "84jHVNLQmZsAAAAAZB0Zryi2wqVJI-KaKNXOvCijEi46YyYzkaSHyJrMPBMOkVZa", - "state_init": "string" - } - } - :return: :class:`str` token - """ - method = "v2/wallet/auth/proof" - response = self._post(method=method, body=body) - - return response.get("token", None) - - def get_by_public_key( - self, - public_key: str, - ) -> Accounts: - """ - Get wallet by public key. - - :param public_key: Public key - :return: :class:`Accounts` - """ - method = f"v2/pubkeys/{public_key}/wallets" - response = self._get(method=method) - - return Accounts(**response) - - def get_account_seqno( - self, - account_id: str, - ) -> Union[int, None]: - """ - Get account seqno. - - :param account_id: Account ID - :return: :class:`int` seqno - """ - method = f"v2/wallet/{account_id}/seqno" - response = self._get(method=method) - - return response.get("seqno", None) - - def emulate(self, body: Dict[str, Any], accept_language: str = "en") -> MessageConsequences: - """ - Emulate sending message to blockchain. - - :param body: Data that is expected. example value: - { - "boc": "string", - "params": [ - { - "address": "0:97146a46acc2654y27947f14c4a4b14273e954f78bc017790b41208b0043200b", - "balance": 10000000000 - } - ] - } - :param accept_language: Default value: en - :return: :class:`Dict[str, Any]` - """ - method = "v2/wallet/emulate" - headers = {"Accept-Language": accept_language} - response = self._post(method=method, body=body, headers=headers) - - return MessageConsequences(**response) diff --git a/pytonapi/tonapi/methods/webhooks.py b/pytonapi/tonapi/methods/webhooks.py deleted file mode 100644 index 6ccf132..0000000 --- a/pytonapi/tonapi/methods/webhooks.py +++ /dev/null @@ -1,81 +0,0 @@ -from typing import List - -from pytonapi.schema.webhooks import WebhookCreate, WebhookList, AccountSubscriptions -from pytonapi.tonapi.client import TonapiClientBase - - -class WebhooksMethod(TonapiClientBase): - - def __init__(self, *args, **kwargs) -> None: - super().__init__(*args, **kwargs) - self.base_url = "https://rt.tonapi.io/" - - def create_webhook(self, endpoint: str) -> WebhookCreate: - """ - Create a new webhook and return its ID. - - :param endpoint: The webhook endpoint URL to receive transaction events. - :return: An object containing the ID of the created webhook. - :rtype: WebhookCreate - """ - method = "webhooks" - body = {"endpoint": endpoint} - response = self._post(method=method, body=body) - return WebhookCreate(**response) - - def list_webhooks(self) -> WebhookList: - """ - Retrieve a list of all available webhooks. - - :return: A list containing all webhooks with their IDs and endpoints. - :rtype: WebhookList - """ - method = "webhooks" - response = self._get(method=method) - return WebhookList(**response) - - def delete_webhook(self, webhook_id: int) -> None: - """ - Delete a webhook and all its subscriptions. - - :param webhook_id: The ID of the webhook to delete. - """ - method = f"webhooks/{webhook_id}" - self._delete(method=method) - - def subscribe_to_account(self, webhook_id: int, accounts: List[str]) -> None: - """ - Subscribe a webhook to specific account transactions. - - :param webhook_id: The ID of the webhook to subscribe. - :param accounts: A list of account IDs to subscribe to. - """ - method = f"webhooks/{webhook_id}/account-tx/subscribe" - body = {"accounts": [{"account_id": account} for account in accounts]} - self._post(method=method, body=body) - - def unsubscribe_from_account(self, webhook_id: int, accounts: List[str]) -> None: - """ - Unsubscribe a webhook from specific account transactions. - - :param webhook_id: The ID of the webhook to unsubscribe. - :param accounts: A list of account IDs to unsubscribe from. - """ - method = f"webhooks/{webhook_id}/account-tx/unsubscribe" - body = {"accounts": accounts} - self._post(method=method, body=body) - - def get_subscriptions(self, webhook_id: int, offset: int = 0, limit: int = 10) -> AccountSubscriptions: - """ - Retrieve the list of subscriptions for a given webhook. - - :param webhook_id: The ID of the webhook. - :param offset: The offset for pagination. Default is 0. - :param limit: The maximum number of subscriptions to return. Default is 10. - :return: A list of account transaction subscriptions with details. - :rtype: AccountSubscriptions - """ - method = f"webhooks/{webhook_id}/account-tx/subscriptions" - params = {"offset": offset, "limit": limit} - response = self._get(method=method, params=params) - return AccountSubscriptions(**response)