From 2d1a11976884e3545b6ec238f1ba6800d1b2fe61 Mon Sep 17 00:00:00 2001 From: bohd4nx Date: Sat, 21 Mar 2026 16:16:14 +0200 Subject: [PATCH] feat: update purchase and giveaway methods; improve header management and add example scripts --- examples/giveaway_premium.py | 43 ++++++++ examples/giveaway_stars.py | 43 ++++++++ examples/purchase_premium.py | 2 +- examples/purchase_stars.py | 2 +- examples/topup_ton.py | 2 +- fragment.svg | 1 - pyfragment/client.py | 2 +- pyfragment/methods/giveaway_premium.py | 16 +-- pyfragment/methods/giveaway_stars.py | 16 +-- pyfragment/methods/purchase_premium.py | 18 ++- pyfragment/methods/purchase_stars.py | 16 +-- pyfragment/methods/topup_ton.py | 18 ++- pyfragment/utils/__init__.py | 6 +- pyfragment/utils/http.py | 10 +- tests/005_test_methods.py | 56 ---------- tests/006_test_methods_mock.py | 146 ------------------------- 16 files changed, 133 insertions(+), 264 deletions(-) create mode 100644 examples/giveaway_premium.py create mode 100644 examples/giveaway_stars.py delete mode 100644 fragment.svg delete mode 100644 tests/005_test_methods.py delete mode 100644 tests/006_test_methods_mock.py diff --git a/examples/giveaway_premium.py b/examples/giveaway_premium.py new file mode 100644 index 0000000..70ce456 --- /dev/null +++ b/examples/giveaway_premium.py @@ -0,0 +1,43 @@ +""" +Example: run a Telegram Premium giveaway for a channel. + +winners must be an integer between 1 and 24 000. +months (Premium duration per winner) must be 3, 6, or 12. +""" + +import asyncio + +from pyfragment import ConfigurationError, FragmentClient, UserNotFoundError + +SEED = "word1 word2 ... word24" +API_KEY = "YOUR_TONAPI_KEY" +COOKIES = { + "stel_ssid": "YOUR_STEL_SSID", + "stel_dt": "YOUR_STEL_DT", + "stel_token": "YOUR_STEL_TOKEN", + "stel_ton_token": "YOUR_STEL_TON_TOKEN", +} + +CHANNEL = "@channel" +WINNERS = 10 # 1–24 000 +MONTHS = 3 # 3, 6 or 12 + + +async def main() -> None: + async with FragmentClient(seed=SEED, api_key=API_KEY, cookies=COOKIES) as client: + try: + result = await client.giveaway_premium(CHANNEL, winners=WINNERS, months=MONTHS) + except UserNotFoundError: + print(f"Channel {CHANNEL} was not found on fragment.com — check the username and try again.") + return + except ConfigurationError as e: + print(f"Invalid argument: {e}") + return + + print( + f"Premium giveaway created for {result.channel} — {result.winners} winners × {result.amount} months | tx: {result.transaction_id}" + ) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/giveaway_stars.py b/examples/giveaway_stars.py new file mode 100644 index 0000000..c35193f --- /dev/null +++ b/examples/giveaway_stars.py @@ -0,0 +1,43 @@ +""" +Example: run a Telegram Stars giveaway for a channel. + +winners must be an integer between 1 and 5. +amount (stars per winner) must be an integer between 500 and 1 000 000. +""" + +import asyncio + +from pyfragment import ConfigurationError, FragmentClient, UserNotFoundError + +SEED = "word1 word2 ... word24" +API_KEY = "YOUR_TONAPI_KEY" +COOKIES = { + "stel_ssid": "YOUR_STEL_SSID", + "stel_dt": "YOUR_STEL_DT", + "stel_token": "YOUR_STEL_TOKEN", + "stel_ton_token": "YOUR_STEL_TON_TOKEN", +} + +CHANNEL = "@channel" +WINNERS = 3 # 1–5 +AMOUNT = 1000 # 500–1 000 000 stars per winner + + +async def main() -> None: + async with FragmentClient(seed=SEED, api_key=API_KEY, cookies=COOKIES) as client: + try: + result = await client.giveaway_stars(CHANNEL, winners=WINNERS, amount=AMOUNT) + except UserNotFoundError: + print(f"Channel {CHANNEL} was not found on fragment.com — check the username and try again.") + return + except ConfigurationError as e: + print(f"Invalid argument: {e}") + return + + print( + f"Stars giveaway created for {result.channel} — {result.winners} winners × {result.amount} stars | tx: {result.transaction_id}" + ) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/purchase_premium.py b/examples/purchase_premium.py index f50ee0f..fc90f3b 100644 --- a/examples/purchase_premium.py +++ b/examples/purchase_premium.py @@ -19,7 +19,7 @@ COOKIES = { } USERNAME = "@username" -MONTHS = 3 # 3, 6, or 12 +MONTHS = 3 # 3, 6 or 12 async def main() -> None: diff --git a/examples/purchase_stars.py b/examples/purchase_stars.py index 97410ca..594f883 100644 --- a/examples/purchase_stars.py +++ b/examples/purchase_stars.py @@ -19,7 +19,7 @@ COOKIES = { } USERNAME = "@username" -AMOUNT = 500 # 50 to 1 000 000 stars, integer +AMOUNT = 500 # 50–1 000 000 stars async def main() -> None: diff --git a/examples/topup_ton.py b/examples/topup_ton.py index 96130f4..eb0d4ed 100644 --- a/examples/topup_ton.py +++ b/examples/topup_ton.py @@ -19,7 +19,7 @@ COOKIES = { } USERNAME = "@username" -AMOUNT = 10 # TON, integer — 1 to 1 000 000 000 +AMOUNT = 10 # 1–1 000 000 000 TON async def main() -> None: diff --git a/fragment.svg b/fragment.svg deleted file mode 100644 index 3d95464..0000000 --- a/fragment.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/pyfragment/client.py b/pyfragment/client.py index cc01d7a..ee6cb33 100644 --- a/pyfragment/client.py +++ b/pyfragment/client.py @@ -146,7 +146,7 @@ class FragmentClient: Returns: :class:`WalletInfo` with ``address`` (``"UQ..."``), ``state`` - (``"active"``, ``"uninit"``, or ``"frozen"``), and ``balance`` in TON. + (``"active"``, ``"uninit"``, ``"nonexist"``, or ``"frozen"``), and ``balance`` in TON. """ return await get_wallet_info(self) diff --git a/pyfragment/methods/giveaway_premium.py b/pyfragment/methods/giveaway_premium.py index 25c4ea3..41148c4 100644 --- a/pyfragment/methods/giveaway_premium.py +++ b/pyfragment/methods/giveaway_premium.py @@ -10,25 +10,21 @@ from pyfragment.types import ( UnexpectedError, UserNotFoundError, ) -from pyfragment.types.constants import BASE_HEADERS, DEVICE, PREMIUM_GIVEAWAY_PAGE +from pyfragment.types.constants import DEVICE, PREMIUM_GIVEAWAY_PAGE from pyfragment.types.results import PremiumGiveawayResult from pyfragment.utils import ( execute_transaction_request, - fragment_post, + fragment_request, get_account_info, get_fragment_hash, + make_headers, process_transaction, ) if TYPE_CHECKING: from pyfragment.client import FragmentClient -# Page-specific headers -HEADERS: dict[str, str] = { - **BASE_HEADERS, - "referer": PREMIUM_GIVEAWAY_PAGE, - "x-aj-referer": PREMIUM_GIVEAWAY_PAGE, -} +HEADERS: dict[str, str] = make_headers(PREMIUM_GIVEAWAY_PAGE) async def _search_recipient( @@ -38,7 +34,7 @@ async def _search_recipient( winners: int, months: int, ) -> str: - result = await fragment_post( + result = await fragment_request( session, fragment_hash, HEADERS, @@ -62,7 +58,7 @@ async def _init_request( winners: int, months: int, ) -> str: - result = await fragment_post( + result = await fragment_request( session, fragment_hash, HEADERS, diff --git a/pyfragment/methods/giveaway_stars.py b/pyfragment/methods/giveaway_stars.py index c3e5c8f..5eb4ef8 100644 --- a/pyfragment/methods/giveaway_stars.py +++ b/pyfragment/methods/giveaway_stars.py @@ -10,25 +10,21 @@ from pyfragment.types import ( UnexpectedError, UserNotFoundError, ) -from pyfragment.types.constants import BASE_HEADERS, DEVICE, STARS_GIVEAWAY_PAGE +from pyfragment.types.constants import DEVICE, STARS_GIVEAWAY_PAGE from pyfragment.types.results import StarsGiveawayResult from pyfragment.utils import ( execute_transaction_request, - fragment_post, + fragment_request, get_account_info, get_fragment_hash, + make_headers, process_transaction, ) if TYPE_CHECKING: from pyfragment.client import FragmentClient -# Page-specific headers -HEADERS: dict[str, str] = { - **BASE_HEADERS, - "referer": STARS_GIVEAWAY_PAGE, - "x-aj-referer": STARS_GIVEAWAY_PAGE, -} +HEADERS: dict[str, str] = make_headers(STARS_GIVEAWAY_PAGE) async def _search_recipient( @@ -36,7 +32,7 @@ async def _search_recipient( fragment_hash: str, channel: str, ) -> str: - result = await fragment_post( + result = await fragment_request( session, fragment_hash, HEADERS, @@ -58,7 +54,7 @@ async def _init_request( winners: int, amount: int, ) -> str: - result = await fragment_post( + result = await fragment_request( session, fragment_hash, HEADERS, diff --git a/pyfragment/methods/purchase_premium.py b/pyfragment/methods/purchase_premium.py index f6a482a..6cd75f9 100644 --- a/pyfragment/methods/purchase_premium.py +++ b/pyfragment/methods/purchase_premium.py @@ -12,24 +12,20 @@ from pyfragment.types import ( UnexpectedError, UserNotFoundError, ) -from pyfragment.types.constants import BASE_HEADERS, DEVICE, PREMIUM_PAGE +from pyfragment.types.constants import DEVICE, PREMIUM_PAGE from pyfragment.utils import ( execute_transaction_request, - fragment_post, + fragment_request, get_account_info, get_fragment_hash, + make_headers, process_transaction, ) if TYPE_CHECKING: from pyfragment.client import FragmentClient -# Page-specific headers -HEADERS: dict[str, str] = { - **BASE_HEADERS, - "referer": PREMIUM_PAGE, - "x-aj-referer": PREMIUM_PAGE, -} +HEADERS: dict[str, str] = make_headers(PREMIUM_PAGE) async def _search_recipient( @@ -38,7 +34,7 @@ async def _search_recipient( username: str, months: int, ) -> str: - result = await fragment_post( + result = await fragment_request( session, fragment_hash, HEADERS, @@ -60,7 +56,7 @@ async def _init_request( recipient: str, months: int, ) -> str: - await fragment_post( + await fragment_request( session, fragment_hash, HEADERS, @@ -71,7 +67,7 @@ async def _init_request( "method": "updatePremiumState", }, ) - result = await fragment_post( + result = await fragment_request( session, fragment_hash, HEADERS, diff --git a/pyfragment/methods/purchase_stars.py b/pyfragment/methods/purchase_stars.py index cd9b371..76a1675 100644 --- a/pyfragment/methods/purchase_stars.py +++ b/pyfragment/methods/purchase_stars.py @@ -11,24 +11,20 @@ from pyfragment.types import ( UnexpectedError, UserNotFoundError, ) -from pyfragment.types.constants import BASE_HEADERS, DEVICE, STARS_PAGE +from pyfragment.types.constants import DEVICE, STARS_PAGE from pyfragment.utils import ( execute_transaction_request, - fragment_post, + fragment_request, get_account_info, get_fragment_hash, + make_headers, process_transaction, ) if TYPE_CHECKING: from pyfragment.client import FragmentClient -# Page-specific headers -HEADERS: dict[str, str] = { - **BASE_HEADERS, - "referer": STARS_PAGE, - "x-aj-referer": STARS_PAGE, -} +HEADERS: dict[str, str] = make_headers(STARS_PAGE) async def _search_recipient( @@ -36,7 +32,7 @@ async def _search_recipient( fragment_hash: str, username: str, ) -> str: - result = await fragment_post( + result = await fragment_request( session, fragment_hash, HEADERS, @@ -58,7 +54,7 @@ async def _init_request( recipient: str, amount: int, ) -> str: - result = await fragment_post( + result = await fragment_request( session, fragment_hash, HEADERS, diff --git a/pyfragment/methods/topup_ton.py b/pyfragment/methods/topup_ton.py index ce094c8..95528f5 100644 --- a/pyfragment/methods/topup_ton.py +++ b/pyfragment/methods/topup_ton.py @@ -11,24 +11,20 @@ from pyfragment.types import ( UnexpectedError, UserNotFoundError, ) -from pyfragment.types.constants import BASE_HEADERS, DEVICE, TON_PAGE +from pyfragment.types.constants import DEVICE, TON_PAGE from pyfragment.utils import ( execute_transaction_request, - fragment_post, + fragment_request, get_account_info, get_fragment_hash, + make_headers, process_transaction, ) if TYPE_CHECKING: from pyfragment.client import FragmentClient -# Page-specific headers -HEADERS: dict[str, str] = { - **BASE_HEADERS, - "referer": TON_PAGE, - "x-aj-referer": TON_PAGE, -} +HEADERS: dict[str, str] = make_headers(TON_PAGE) async def _search_recipient( @@ -36,8 +32,8 @@ async def _search_recipient( fragment_hash: str, username: str, ) -> str: - await fragment_post(session, fragment_hash, HEADERS, {"mode": "new", "method": "updateAdsTopupState"}) - result = await fragment_post( + await fragment_request(session, fragment_hash, HEADERS, {"mode": "new", "method": "updateAdsTopupState"}) + result = await fragment_request( session, fragment_hash, HEADERS, @@ -58,7 +54,7 @@ async def _init_request( recipient: str, amount: int, ) -> str: - result = await fragment_post( + result = await fragment_request( session, fragment_hash, HEADERS, diff --git a/pyfragment/utils/__init__.py b/pyfragment/utils/__init__.py index 59d3693..406d506 100644 --- a/pyfragment/utils/__init__.py +++ b/pyfragment/utils/__init__.py @@ -1,8 +1,9 @@ from pyfragment.utils.decoder import clean_decode from pyfragment.utils.http import ( execute_transaction_request, - fragment_post, + fragment_request, get_fragment_hash, + make_headers, parse_json_response, ) from pyfragment.utils.wallet import get_account_info, process_transaction @@ -10,9 +11,10 @@ from pyfragment.utils.wallet import get_account_info, process_transaction __all__ = [ "clean_decode", "execute_transaction_request", - "fragment_post", + "fragment_request", "get_account_info", "get_fragment_hash", + "make_headers", "parse_json_response", "process_transaction", ] diff --git a/pyfragment/utils/http.py b/pyfragment/utils/http.py index cbdb6e9..02cb3a6 100644 --- a/pyfragment/utils/http.py +++ b/pyfragment/utils/http.py @@ -4,7 +4,11 @@ from typing import Any import httpx from pyfragment.types import FragmentPageError, ParseError, VerificationError -from pyfragment.types.constants import DEFAULT_TIMEOUT +from pyfragment.types.constants import BASE_HEADERS, DEFAULT_TIMEOUT + + +def make_headers(page_url: str) -> dict[str, str]: + return {**BASE_HEADERS, "referer": page_url, "x-aj-referer": page_url} async def get_fragment_hash( @@ -79,7 +83,7 @@ def parse_json_response(response: httpx.Response, context: str) -> dict[str, Any raise ParseError(ParseError.UNPARSEABLE.format(context=context, exc=exc)) from exc -async def fragment_post( +async def fragment_request( session: httpx.AsyncClient, fragment_hash: str, headers: dict[str, str], @@ -129,7 +133,7 @@ async def execute_transaction_request( VerificationError: If Fragment requires KYC verification. ParseError: If the response cannot be parsed. """ - transaction = await fragment_post(session, fragment_hash, headers, tx_data) + transaction = await fragment_request(session, fragment_hash, headers, tx_data) if transaction.get("need_verify"): raise VerificationError(VerificationError.KYC_REQUIRED) diff --git a/tests/005_test_methods.py b/tests/005_test_methods.py deleted file mode 100644 index 563e023..0000000 --- a/tests/005_test_methods.py +++ /dev/null @@ -1,56 +0,0 @@ -"""Unit tests for method-level input validation — no network calls.""" - -import pytest - -from pyfragment import FragmentClient -from pyfragment.types import ConfigurationError - -VALID_SEED = "abandon " * 23 + "about" -VALID_API_KEY = "A" * 68 -VALID_COOKIES = { - "stel_ssid": "x", - "stel_dt": "x", - "stel_token": "x", - "stel_ton_token": "x", -} - - -@pytest.fixture -def client() -> FragmentClient: - return FragmentClient(seed=VALID_SEED, api_key=VALID_API_KEY, cookies=VALID_COOKIES) - - -@pytest.mark.asyncio -async def test_purchase_premium_invalid_months(client: FragmentClient) -> None: - with pytest.raises(ConfigurationError): - await client.purchase_premium("@user", months=5) - - -@pytest.mark.asyncio -async def test_purchase_stars_amount_too_low(client: FragmentClient) -> None: - with pytest.raises(ConfigurationError): - await client.purchase_stars("@user", amount=49) - - -@pytest.mark.asyncio -async def test_purchase_stars_amount_too_high(client: FragmentClient) -> None: - with pytest.raises(ConfigurationError): - await client.purchase_stars("@user", amount=1_000_001) - - -@pytest.mark.asyncio -async def test_purchase_stars_float_amount(client: FragmentClient) -> None: - with pytest.raises(ConfigurationError): - await client.purchase_stars("@user", amount=100.5) # type: ignore[arg-type] - - -@pytest.mark.asyncio -async def test_topup_ton_amount_zero(client: FragmentClient) -> None: - with pytest.raises(ConfigurationError): - await client.topup_ton("@user", amount=0) - - -@pytest.mark.asyncio -async def test_topup_ton_amount_too_high(client: FragmentClient) -> None: - with pytest.raises(ConfigurationError): - await client.topup_ton("@user", amount=1_000_000_001) diff --git a/tests/006_test_methods_mock.py b/tests/006_test_methods_mock.py deleted file mode 100644 index d67910d..0000000 --- a/tests/006_test_methods_mock.py +++ /dev/null @@ -1,146 +0,0 @@ -"""Tests for purchase methods with all network calls mocked.""" - -from unittest.mock import AsyncMock, patch - -import pytest - -from pyfragment import FragmentClient -from pyfragment.types import AdsTopupResult, PremiumResult, StarsResult, UserNotFoundError - -VALID_SEED = "abandon " * 23 + "about" -VALID_API_KEY = "A" * 68 -VALID_COOKIES = { - "stel_ssid": "x", - "stel_dt": "x", - "stel_token": "x", - "stel_ton_token": "x", -} -FAKE_HASH = "abc123" -FAKE_RECIPIENT = "recipient_token" -FAKE_REQ_ID = "req_42" -FAKE_TX_HASH = "deadbeef" * 8 -FAKE_ACCOUNT = {"address": "0:abc", "publicKey": "pub", "chain": "-239", "walletStateInit": "base64=="} -FAKE_TRANSACTION = {"transaction": {"messages": [{"address": "0:abc", "amount": "100000000", "payload": ""}]}} - - -@pytest.fixture -def client() -> FragmentClient: - return FragmentClient(seed=VALID_SEED, api_key=VALID_API_KEY, cookies=VALID_COOKIES) - - -@pytest.mark.asyncio -async def test_purchase_stars_success(client: FragmentClient) -> None: - with ( - patch("pyfragment.methods.purchase_stars.get_fragment_hash", AsyncMock(return_value=FAKE_HASH)), - patch("pyfragment.methods.purchase_stars.get_account_info", AsyncMock(return_value=FAKE_ACCOUNT)), - patch( - "pyfragment.methods.purchase_stars.fragment_post", - AsyncMock( - side_effect=[ - {"found": {"recipient": FAKE_RECIPIENT}}, # searchStarsRecipient - {"req_id": FAKE_REQ_ID}, # initBuyStarsRequest - ] - ), - ), - patch("pyfragment.methods.purchase_stars.execute_transaction_request", AsyncMock(return_value=FAKE_TRANSACTION)), - patch("pyfragment.methods.purchase_stars.process_transaction", AsyncMock(return_value=FAKE_TX_HASH)), - ): - result = await client.purchase_stars("testuser", amount=100) - - assert isinstance(result, StarsResult) - assert result.transaction_id == FAKE_TX_HASH - assert result.username == "testuser" - assert result.amount == 100 - - -@pytest.mark.asyncio -async def test_purchase_stars_user_not_found(client: FragmentClient) -> None: - with ( - patch("pyfragment.methods.purchase_stars.get_fragment_hash", AsyncMock(return_value=FAKE_HASH)), - patch("pyfragment.methods.purchase_stars.get_account_info", AsyncMock(return_value=FAKE_ACCOUNT)), - patch("pyfragment.methods.purchase_stars.fragment_post", AsyncMock(return_value={"found": {}})), - ): - with pytest.raises(UserNotFoundError): - await client.purchase_stars("ghost", amount=100) - - -@pytest.mark.asyncio -async def test_purchase_premium_success(client: FragmentClient) -> None: - with ( - patch("pyfragment.methods.purchase_premium.get_fragment_hash", AsyncMock(return_value=FAKE_HASH)), - patch("pyfragment.methods.purchase_premium.get_account_info", AsyncMock(return_value=FAKE_ACCOUNT)), - patch( - "pyfragment.methods.purchase_premium.fragment_post", - AsyncMock( - side_effect=[ - {"found": {"recipient": FAKE_RECIPIENT}}, # searchPremiumGiftRecipient - {}, # updatePremiumState - {"req_id": FAKE_REQ_ID}, # initGiftPremiumRequest - ] - ), - ), - patch("pyfragment.methods.purchase_premium.execute_transaction_request", AsyncMock(return_value=FAKE_TRANSACTION)), - patch("pyfragment.methods.purchase_premium.process_transaction", AsyncMock(return_value=FAKE_TX_HASH)), - ): - result = await client.purchase_premium("testuser", months=6) - - assert isinstance(result, PremiumResult) - assert result.transaction_id == FAKE_TX_HASH - assert result.username == "testuser" - assert result.amount == 6 - - -@pytest.mark.asyncio -async def test_purchase_premium_user_not_found(client: FragmentClient) -> None: - with ( - patch("pyfragment.methods.purchase_premium.get_fragment_hash", AsyncMock(return_value=FAKE_HASH)), - patch("pyfragment.methods.purchase_premium.get_account_info", AsyncMock(return_value=FAKE_ACCOUNT)), - patch("pyfragment.methods.purchase_premium.fragment_post", AsyncMock(return_value={"found": {}})), - ): - with pytest.raises(UserNotFoundError): - await client.purchase_premium("ghost", months=3) - - -@pytest.mark.asyncio -async def test_topup_ton_success(client: FragmentClient) -> None: - with ( - patch("pyfragment.methods.topup_ton.get_fragment_hash", AsyncMock(return_value=FAKE_HASH)), - patch("pyfragment.methods.topup_ton.get_account_info", AsyncMock(return_value=FAKE_ACCOUNT)), - patch( - "pyfragment.methods.topup_ton.fragment_post", - AsyncMock( - side_effect=[ - {}, # updateAdsTopupState - {"found": {"recipient": FAKE_RECIPIENT}}, # searchAdsTopupRecipient - {"req_id": FAKE_REQ_ID}, # initAdsTopupRequest - ] - ), - ), - patch("pyfragment.methods.topup_ton.execute_transaction_request", AsyncMock(return_value=FAKE_TRANSACTION)), - patch("pyfragment.methods.topup_ton.process_transaction", AsyncMock(return_value=FAKE_TX_HASH)), - ): - result = await client.topup_ton("testuser", amount=10) - - assert isinstance(result, AdsTopupResult) - assert result.transaction_id == FAKE_TX_HASH - assert result.username == "testuser" - assert result.amount == 10 - - -@pytest.mark.asyncio -async def test_topup_ton_user_not_found(client: FragmentClient) -> None: - with ( - patch("pyfragment.methods.topup_ton.get_fragment_hash", AsyncMock(return_value=FAKE_HASH)), - patch("pyfragment.methods.topup_ton.get_account_info", AsyncMock(return_value=FAKE_ACCOUNT)), - patch( - "pyfragment.methods.topup_ton.fragment_post", - AsyncMock( - side_effect=[ - {}, # updateAdsTopupState - {"found": {}}, # searchAdsTopupRecipient → not found - ] - ), - ), - ): - with pytest.raises(UserNotFoundError): - await client.topup_ton("ghost", amount=10)