From 2cdc102a743531b9b8f3e7698e352296d0c485be Mon Sep 17 00:00:00 2001 From: bohd4nx Date: Thu, 5 Mar 2026 06:36:27 +0200 Subject: [PATCH 1/4] refactor: reorganize imports across multiple files for consistency and clarity --- app/core/__init__.py | 10 +++++++++- app/methods/premium.py | 11 ++++++++--- app/methods/stars.py | 11 ++++++++--- app/methods/ton.py | 4 +--- app/utils/client.py | 2 +- app/utils/hash.py | 2 +- app/utils/wallet.py | 4 +--- 7 files changed, 29 insertions(+), 15 deletions(-) diff --git a/app/core/__init__.py b/app/core/__init__.py index 537895b..4388f7a 100644 --- a/app/core/__init__.py +++ b/app/core/__init__.py @@ -1,5 +1,12 @@ from app.core.config import config -from app.core.constants import ADS_PAGE, BASE_HEADERS, DEVICE, PREMIUM_PAGE, STARS_PAGE +from app.core.constants import ( + ADS_PAGE, + BASE_HEADERS, + DEVICE, + PREMIUM_PAGE, + STARS_PAGE, + WALLET_CLASSES, +) from app.core.cookies import load_cookies from app.core.exceptions import ( ConfigError, @@ -19,6 +26,7 @@ __all__ = [ "DEVICE", "PREMIUM_PAGE", "STARS_PAGE", + "WALLET_CLASSES", "ConfigError", "CookiesError", "FragmentError", diff --git a/app/methods/premium.py b/app/methods/premium.py index a6004f9..9d68d9c 100644 --- a/app/methods/premium.py +++ b/app/methods/premium.py @@ -4,9 +4,14 @@ import time import httpx -from app.core import load_cookies -from app.core.constants import BASE_HEADERS, DEVICE, PREMIUM_PAGE -from app.core.exceptions import FragmentError, UserNotFoundError +from app.core import ( + BASE_HEADERS, + DEVICE, + PREMIUM_PAGE, + FragmentError, + UserNotFoundError, + load_cookies, +) from app.utils import ( execute_transaction_request, get_account_info, diff --git a/app/methods/stars.py b/app/methods/stars.py index 2dbf05e..97d1f60 100644 --- a/app/methods/stars.py +++ b/app/methods/stars.py @@ -4,9 +4,14 @@ import time import httpx -from app.core import load_cookies -from app.core.constants import BASE_HEADERS, DEVICE, STARS_PAGE -from app.core.exceptions import FragmentError, UserNotFoundError +from app.core import ( + BASE_HEADERS, + DEVICE, + STARS_PAGE, + FragmentError, + UserNotFoundError, + load_cookies, +) from app.utils import ( execute_transaction_request, get_account_info, diff --git a/app/methods/ton.py b/app/methods/ton.py index 60b3932..79ed10a 100644 --- a/app/methods/ton.py +++ b/app/methods/ton.py @@ -4,9 +4,7 @@ import time import httpx -from app.core import load_cookies -from app.core.constants import ADS_PAGE, BASE_HEADERS, DEVICE -from app.core.exceptions import FragmentError, UserNotFoundError +from app.core import ADS_PAGE, BASE_HEADERS, DEVICE, FragmentError, UserNotFoundError, load_cookies from app.utils import ( execute_transaction_request, get_account_info, diff --git a/app/utils/client.py b/app/utils/client.py index 694d40f..8627390 100644 --- a/app/utils/client.py +++ b/app/utils/client.py @@ -3,7 +3,7 @@ from typing import Any import httpx -from app.core.exceptions import RequestError, WalletError +from app.core import RequestError, WalletError from app.utils.wallet import link_wallet logger = logging.getLogger(__name__) diff --git a/app/utils/hash.py b/app/utils/hash.py index a09455d..790e683 100644 --- a/app/utils/hash.py +++ b/app/utils/hash.py @@ -4,7 +4,7 @@ from typing import Any import httpx -from app.core.exceptions import HashFetchError +from app.core import HashFetchError logger = logging.getLogger(__name__) diff --git a/app/utils/wallet.py b/app/utils/wallet.py index 01932cd..a19af69 100644 --- a/app/utils/wallet.py +++ b/app/utils/wallet.py @@ -7,9 +7,7 @@ import httpx from tonutils.clients import TonapiClient from tonutils.types import NetworkGlobalID -from app.core import config -from app.core.constants import DEVICE, WALLET_CLASSES -from app.core.exceptions import TransactionError, WalletError +from app.core import DEVICE, WALLET_CLASSES, TransactionError, WalletError, config from app.utils.decoder import clean_decode logger = logging.getLogger(__name__) From 8cf57b58b71b24bb4ad80130537822236348de75 Mon Sep 17 00:00:00 2001 From: bohd4nx Date: Sun, 8 Mar 2026 03:58:07 +0200 Subject: [PATCH 2/4] refactor: enhance test workflow and integration tests with cookies handling and configuration checks --- .github/workflows/tests.yml | 11 +++++++++++ tests/003_test_integration.py | 4 ++-- tests/conftest.py | 11 ++++++++++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e68274d..b556946 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -21,7 +21,18 @@ jobs: - name: Install dependencies run: pip install -r requirements.txt pytest pytest-asyncio + - name: Write cookies.json + if: ${{ env.COOKIES_JSON != '' }} + run: echo "$COOKIES_JSON" > cookies.json + env: + COOKIES_JSON: ${{ secrets.COOKIES_JSON }} + - name: Run tests + if: github.ref != 'refs/heads/master' + run: pytest --ignore=tests/003_test_integration.py + + - name: Run tests (master — full suite) + if: github.ref == 'refs/heads/master' run: pytest env: SEED: ${{ secrets.SEED }} diff --git a/tests/003_test_integration.py b/tests/003_test_integration.py index 8736906..0fa44a3 100644 --- a/tests/003_test_integration.py +++ b/tests/003_test_integration.py @@ -1,13 +1,13 @@ """End-to-end integration test: buy 50 Stars for @bohd4nx. Requires cookies.json and a valid .env (API_KEY + SEED). -Auto-skipped when cookies are unavailable (e.g. local runs without secrets). +Auto-skipped when cookies or config are unavailable (e.g. CI without secrets). """ from app.methods.stars import buy_stars -async def test_buy_stars_e2e(cookies): +async def test_buy_stars_e2e(cookies, tests_config): result = await buy_stars("@bohd4nx", 50) assert result["success"] is True, result.get("error") assert result["data"]["transaction_id"] diff --git a/tests/conftest.py b/tests/conftest.py index 5fd734c..8c02ca9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,13 +1,22 @@ import pytest +from app.core.config import config from app.core.cookies import load_cookies from app.core.exceptions import CookiesError @pytest.fixture def cookies(): - """Load Fragment cookies, skip the test if they are unavailable.""" + """Load Fragment cookies; skip the test if they are unavailable.""" try: return load_cookies() except CookiesError as exc: pytest.skip(f"Cookies unavailable — {exc}") + + +@pytest.fixture +def tests_config(): + """Require a fully configured environment (SEED + API_KEY); skip otherwise.""" + if config is None: + pytest.skip("Config unavailable — set SEED and API_KEY in .env or environment") + return config From 38a4619e42b48b7e1aa473786a7255f4c2cd2696 Mon Sep 17 00:00:00 2001 From: bohd4nx Date: Sun, 8 Mar 2026 04:26:45 +0200 Subject: [PATCH 3/4] fix: pass cookies to AsyncClient instead of per-request Fixes httpx DeprecationWarning: Setting per-request cookies is deprecated. Cookies are now set on the AsyncClient instance and removed from all individual post() calls and internal function signatures. --- app/methods/premium.py | 15 ++++----------- app/methods/stars.py | 12 ++++-------- app/methods/ton.py | 13 ++++--------- app/utils/client.py | 7 +++---- app/utils/wallet.py | 2 -- 5 files changed, 15 insertions(+), 34 deletions(-) diff --git a/app/methods/premium.py b/app/methods/premium.py index 9d68d9c..bea566b 100644 --- a/app/methods/premium.py +++ b/app/methods/premium.py @@ -33,14 +33,12 @@ HEADERS: dict[str, str] = { async def search_premium_recipient( client: httpx.AsyncClient, fragment_hash: str, - cookies: dict, username: str, months: int, ) -> str: resp = await client.post( f"https://fragment.com/api?hash={fragment_hash}", headers=HEADERS, - cookies=cookies, data={"query": username, "months": months, "method": "searchPremiumGiftRecipient"}, ) result = parse_json_response(resp, "searchPremiumGiftRecipient") @@ -56,14 +54,12 @@ async def search_premium_recipient( async def init_gift_premium( client: httpx.AsyncClient, fragment_hash: str, - cookies: dict, recipient: str, months: int, ) -> str: await client.post( f"https://fragment.com/api?hash={fragment_hash}", headers=HEADERS, - cookies=cookies, data={ "mode": "new", "lv": "false", @@ -74,7 +70,6 @@ async def init_gift_premium( resp = await client.post( f"https://fragment.com/api?hash={fragment_hash}", headers=HEADERS, - cookies=cookies, data={"recipient": recipient, "months": months, "method": "initGiftPremiumRequest"}, ) result = parse_json_response(resp, "initGiftPremiumRequest") @@ -101,14 +96,12 @@ async def buy_premium(username: str, months: int) -> dict: # logger.info("Retrieving TON wallet info") account = await get_account_info() - async with httpx.AsyncClient() as client: + async with httpx.AsyncClient(cookies=cookies) as client: logger.info("Searching recipient: %s", username) - recipient = await search_premium_recipient( - client, fragment_hash, cookies, username, months - ) + recipient = await search_premium_recipient(client, fragment_hash, username, months) logger.info("Initializing Premium gift request: %s months to %s", months, username) - req_id = await init_gift_premium(client, fragment_hash, cookies, recipient, months) + req_id = await init_gift_premium(client, fragment_hash, recipient, months) # logger.info("Requesting transaction payload (req_id=%s)", req_id) tx_data = { @@ -120,7 +113,7 @@ async def buy_premium(username: str, months: int) -> dict: "method": "getGiftPremiumLink", } transaction = await execute_transaction_request( - client, HEADERS, cookies, account, tx_data, fragment_hash + client, HEADERS, account, tx_data, fragment_hash ) logger.info("Broadcasting transaction to TON blockchain") diff --git a/app/methods/stars.py b/app/methods/stars.py index 97d1f60..fa3d507 100644 --- a/app/methods/stars.py +++ b/app/methods/stars.py @@ -33,13 +33,11 @@ HEADERS: dict[str, str] = { async def search_stars_recipient( client: httpx.AsyncClient, fragment_hash: str, - cookies: dict, username: str, ) -> str: resp = await client.post( f"https://fragment.com/api?hash={fragment_hash}", headers=HEADERS, - cookies=cookies, data={"query": username, "quantity": "", "method": "searchStarsRecipient"}, ) result = parse_json_response(resp, "searchStarsRecipient") @@ -55,14 +53,12 @@ async def search_stars_recipient( async def init_buy_stars( client: httpx.AsyncClient, fragment_hash: str, - cookies: dict, recipient: str, amount: int, ) -> str: resp = await client.post( f"https://fragment.com/api?hash={fragment_hash}", headers=HEADERS, - cookies=cookies, data={"recipient": recipient, "quantity": amount, "method": "initBuyStarsRequest"}, ) result = parse_json_response(resp, "initBuyStarsRequest") @@ -89,12 +85,12 @@ async def buy_stars(username: str, amount: int) -> dict: # logger.info("Retrieving TON wallet info") account = await get_account_info() - async with httpx.AsyncClient() as client: + async with httpx.AsyncClient(cookies=cookies) as client: logger.info("Searching recipient: %s", username) - recipient = await search_stars_recipient(client, fragment_hash, cookies, username) + recipient = await search_stars_recipient(client, fragment_hash, username) logger.info("Initializing Stars purchase request: %s stars to %s", amount, username) - req_id = await init_buy_stars(client, fragment_hash, cookies, recipient, amount) + req_id = await init_buy_stars(client, fragment_hash, recipient, amount) # logger.info("Requesting transaction payload (req_id=%s)", req_id) tx_data = { @@ -106,7 +102,7 @@ async def buy_stars(username: str, amount: int) -> dict: "method": "getBuyStarsLink", } transaction = await execute_transaction_request( - client, HEADERS, cookies, account, tx_data, fragment_hash + client, HEADERS, account, tx_data, fragment_hash ) logger.info("Broadcasting transaction to TON blockchain") diff --git a/app/methods/ton.py b/app/methods/ton.py index 79ed10a..bba62d0 100644 --- a/app/methods/ton.py +++ b/app/methods/ton.py @@ -26,19 +26,16 @@ HEADERS: dict[str, str] = { async def search_ads_recipient( client: httpx.AsyncClient, fragment_hash: str, - cookies: dict, username: str, ) -> str: await client.post( f"https://fragment.com/api?hash={fragment_hash}", headers=HEADERS, - cookies=cookies, data={"mode": "new", "method": "updateAdsTopupState"}, ) resp = await client.post( f"https://fragment.com/api?hash={fragment_hash}", headers=HEADERS, - cookies=cookies, data={"query": username, "method": "searchAdsTopupRecipient"}, ) result = parse_json_response(resp, "searchAdsTopupRecipient") @@ -54,14 +51,12 @@ async def search_ads_recipient( async def init_ads_topup( client: httpx.AsyncClient, fragment_hash: str, - cookies: dict, recipient: str, amount: int, ) -> str: resp = await client.post( f"https://fragment.com/api?hash={fragment_hash}", headers=HEADERS, - cookies=cookies, data={"recipient": recipient, "amount": amount, "method": "initAdsTopupRequest"}, ) result = parse_json_response(resp, "initAdsTopupRequest") @@ -88,12 +83,12 @@ async def topup_ton(username: str, amount: int) -> dict: # logger.info("Retrieving TON wallet info") account = await get_account_info() - async with httpx.AsyncClient() as client: + async with httpx.AsyncClient(cookies=cookies) as client: logger.info("Searching recipient: %s", username) - recipient = await search_ads_recipient(client, fragment_hash, cookies, username) + recipient = await search_ads_recipient(client, fragment_hash, username) logger.info("Initializing topup request: %s TON to %s", amount, username) - req_id = await init_ads_topup(client, fragment_hash, cookies, recipient, amount) + req_id = await init_ads_topup(client, fragment_hash, recipient, amount) # logger.info("Requesting transaction payload (req_id=%s)", req_id) tx_data = { @@ -105,7 +100,7 @@ async def topup_ton(username: str, amount: int) -> dict: "method": "getAdsTopupLink", } transaction = await execute_transaction_request( - client, HEADERS, cookies, account, tx_data, fragment_hash + client, HEADERS, account, tx_data, fragment_hash ) logger.info("Broadcasting transaction to TON blockchain") diff --git a/app/utils/client.py b/app/utils/client.py index 8627390..434da6c 100644 --- a/app/utils/client.py +++ b/app/utils/client.py @@ -21,23 +21,22 @@ def parse_json_response(response: httpx.Response, context: str) -> dict[str, Any async def execute_transaction_request( client: httpx.AsyncClient, headers: dict, - cookies: dict, account: dict[str, Any], tx_data: dict[str, Any], fragment_hash: str, ) -> dict[str, Any]: url = f"https://fragment.com/api?hash={fragment_hash}" - resp = await client.post(url, headers=headers, cookies=cookies, data=tx_data) + resp = await client.post(url, headers=headers, data=tx_data) transaction = parse_json_response(resp, tx_data.get("method", "transaction")) if transaction.get("need_verify"): - if not await link_wallet(client, headers, cookies, account, fragment_hash): + if not await link_wallet(client, headers, account, fragment_hash): raise WalletError( "Failed to link your TON wallet to Fragment. " "Make sure the wallet matching your cookies is used." ) - resp = await client.post(url, headers=headers, cookies=cookies, data=tx_data) + resp = await client.post(url, headers=headers, data=tx_data) transaction = parse_json_response(resp, tx_data.get("method", "transaction")) return transaction diff --git a/app/utils/wallet.py b/app/utils/wallet.py index a19af69..5c71ab3 100644 --- a/app/utils/wallet.py +++ b/app/utils/wallet.py @@ -83,14 +83,12 @@ async def get_account_info() -> dict[str, Any]: async def link_wallet( client: httpx.AsyncClient, headers: dict, - cookies: dict, account: dict[str, Any], fragment_hash: str, ) -> bool: resp = await client.post( f"https://fragment.com/api?hash={fragment_hash}", headers=headers, - cookies=cookies, data={ "account": json.dumps(account), "device": DEVICE, From c3de8a8ca3caddb7c9800dd2d432e87ccfc7ba17 Mon Sep 17 00:00:00 2001 From: bohd4nx Date: Sun, 8 Mar 2026 04:37:07 +0200 Subject: [PATCH 4/4] refactor: enhance transaction logging for premium, stars, and TON topup methods --- app/methods/premium.py | 3 +++ app/methods/stars.py | 1 + app/methods/ton.py | 1 + app/utils/decoder.py | 4 +--- app/utils/wallet.py | 4 ++-- main.py | 14 +++----------- 6 files changed, 11 insertions(+), 16 deletions(-) diff --git a/app/methods/premium.py b/app/methods/premium.py index bea566b..05f8621 100644 --- a/app/methods/premium.py +++ b/app/methods/premium.py @@ -118,6 +118,9 @@ async def buy_premium(username: str, months: int) -> dict: logger.info("Broadcasting transaction to TON blockchain") tx_hash = await process_transaction(transaction) + logger.info( + "Premium purchase successful: %s months -> %s | tx: %s", months, username, tx_hash + ) return { "success": True, "data": { diff --git a/app/methods/stars.py b/app/methods/stars.py index fa3d507..f7d6ec9 100644 --- a/app/methods/stars.py +++ b/app/methods/stars.py @@ -107,6 +107,7 @@ async def buy_stars(username: str, amount: int) -> dict: logger.info("Broadcasting transaction to TON blockchain") tx_hash = await process_transaction(transaction) + logger.info("Stars purchase successful: %s stars -> %s | tx: %s", amount, username, tx_hash) return { "success": True, "data": { diff --git a/app/methods/ton.py b/app/methods/ton.py index bba62d0..7cd985d 100644 --- a/app/methods/ton.py +++ b/app/methods/ton.py @@ -105,6 +105,7 @@ async def topup_ton(username: str, amount: int) -> dict: logger.info("Broadcasting transaction to TON blockchain") tx_hash = await process_transaction(transaction) + logger.info("TON topup successful: %s TON -> %s | tx: %s", amount, username, tx_hash) return { "success": True, "data": { diff --git a/app/utils/decoder.py b/app/utils/decoder.py index 70097b2..2d52926 100644 --- a/app/utils/decoder.py +++ b/app/utils/decoder.py @@ -19,8 +19,6 @@ logger = logging.getLogger(__name__) def clean_decode(payload: str) -> str: - logger.debug("Original payload: %s", payload) - # Pad and decode base64 → BOC bytes s = payload.strip() if not s: @@ -34,5 +32,5 @@ def clean_decode(payload: str) -> str: sl.load_uint(32) # op code — always 0 for text comment result = sl.load_snake_string().strip() - logger.debug("Decoded payload: %s", result.replace("\n", " ")) + logger.debug("Payload: %s -> %s", payload, result.replace("\n", " ")) return result diff --git a/app/utils/wallet.py b/app/utils/wallet.py index 5c71ab3..021575d 100644 --- a/app/utils/wallet.py +++ b/app/utils/wallet.py @@ -56,8 +56,8 @@ async def process_transaction(transaction_data: dict) -> str: amount=int(message["amount"]), # nanotons, not TON body=payload, ) - - return result + tx_hash = result.normalized_hash + return tx_hash except (WalletError, TransactionError): raise except Exception as exc: diff --git a/main.py b/main.py index d03d4a7..884b7f7 100644 --- a/main.py +++ b/main.py @@ -14,9 +14,7 @@ async def topup_ton_example(): result = await topup_ton("@bohd4nx", 100) if result["success"]: - data = result["data"] - logger.info(f"TON topup successful: {data['amount']} TON sent to {data['username']}") - logger.info(f"Transaction ID: {data['transaction_id']}") + pass # Transaction successful, details are logged in the method else: logger.error(f"TON topup failed: {result['error']}") @@ -28,11 +26,7 @@ async def buy_premium_example(): result = await buy_premium("@bohd4nx", 12) if result["success"]: - data = result["data"] - logger.info( - f"Premium purchase successful: {data['months']} months sent to {data['username']}" - ) - logger.info(f"Transaction ID: {data['transaction_id']}") + pass # Transaction successful, details are logged in the method else: logger.error(f"Premium purchase failed: {result['error']}") @@ -44,9 +38,7 @@ async def buy_stars_example(): result = await buy_stars("@bohd4nx", 1000000) if result["success"]: - data = result["data"] - logger.info(f"Stars purchase successful: {data['amount']} stars sent to {data['username']}") - logger.info(f"Transaction ID: {data['transaction_id']}") + pass # Transaction successful, details are logged in the method else: logger.error(f"Stars purchase failed: {result['error']}")