feat: Refactor cookie handling and hash retrieval; update environment configuration and improve logging levels

This commit is contained in:
bohd4nx
2026-01-19 19:02:28 +02:00
parent d299a1f804
commit b1eae1dcb7
15 changed files with 290 additions and 137 deletions
+5 -6
View File
@@ -1,11 +1,10 @@
# Fragment.com cookies - copy from browser after login (Header String format)
# IMPORTANT: Use quotes to prevent semicolons from being treated as comments
COOKIES = "your_fragment_cookies_here"
HASH =
# How to get fragment hash: open devtools -> Network -> api?hash=<your_fragment_hash_here>
# HASH = "your_fragment_hash_here" --- IGNORE ---
# Hash is now fetched dynamically, so this line is no longer needed.
# TON wallet seed phrase - 12 or 24 words separated by spaces
SEED =
SEED = "your_ton_wallet_seed_phrase_here"
# TON API key - get from https://tonconsole.com
API_KEY =
API_KEY = "your_ton_api_key_here"
+35 -50
View File
@@ -7,7 +7,8 @@
<b>Automate TON topups, Telegram Premium purchases, and Stars transactions via Fragment.com</b>
</p>
[Report Bug](https://github.com/bohd4nx/fragmentapi/issues) · [Request Feature](https://github.com/bohd4nx/fragmentapi/issues) · [**Donate TON**](https://app.tonkeeper.com/transfer/UQBUAa7KCx1ifmoEy6lF7j-822Dm_cE1j9SR7UWteu3jzukV)
[Report Bug](https://github.com/bohd4nx/fragmentapi/issues) · [Request Feature](https://github.com/bohd4nx/fragmentapi/issues) · [
**Donate TON**](https://app.tonkeeper.com/transfer/UQCppfw5DxWgdVHf3zkmZS8k1mt9oAUYxQLwq2fz3nhO8No5)
</div>
@@ -40,12 +41,8 @@ cp .env.example .env
Edit `.env` file:
```env
COOKIES=your_fragment_cookies_here
SEED=word1 word2 word3 ... word24
HASH=your_fragment_hash_here
API_KEY=your_ton_api_key_here
```
@@ -57,22 +54,27 @@ API_KEY=your_ton_api_key_here
1. **Install Cookie Editor Extension**:
- Download from [Chrome Web Store](https://chromewebstore.google.com/detail/cookie-editor/hlkenndednhfkekhgcdicdfddnkalmdm)
- Add extension to your browser
- Download
from [Chrome Web Store](https://chromewebstore.google.com/detail/cookie-editor/hlkenndednhfkekhgcdicdfddnkalmdm)
- Add extension to your browser
2. **Extract Cookies**:
- Open [Fragment.com](https://fragment.com) and ensure you're logged in
- Refresh the page completely
- Click on the Cookie Editor extension icon
- Click **"Export"** button
- Select **"Header String"** format
- Copy the result → paste to `COOKIES` in your `.env` file
- **Important**: Wrap the value in quotes to prevent semicolons from being treated as comments
- Open [Fragment.com](https://fragment.com) and ensure you're logged in
- Refresh the page completely
- Click on the Cookie Editor extension icon
- Click **"Export"** button
- Select **"Header String"** format
- Copy the result and split it into JSON fields in `cookies.json`
**Expected format**:
**Expected format** (`cookies.json` in project root):
```env
COOKIES = "stel_token=<TOKEN>;stel_dt=<STEL_DT>;stel_ssid=<SSID>;stel_ton_token=<TON_TOKEN>"
```json
{
"stel_ssid": "<SSID>",
"stel_dt": "<STEL_DT>",
"stel_token": "<TOKEN>",
"stel_ton_token": "<TON_TOKEN>"
}
```
#### 🔐 TON Wallet Seed Phrase
@@ -81,52 +83,35 @@ COOKIES = "stel_token=<TOKEN>;stel_dt=<STEL_DT>;stel_ssid=<SSID>;stel_ton_token=
1. **Download Tonkeeper**:
- iOS: [App Store](https://apps.apple.com/app/tonkeeper/id1587742107)
- Android: [Google Play](https://play.google.com/store/apps/details?id=com.ton_keeper)
- iOS: [App Store](https://apps.apple.com/app/tonkeeper/id1587742107)
- Android: [Google Play](https://play.google.com/store/apps/details?id=com.ton_keeper)
2. **Create New Wallet**:
- Open Tonkeeper app
- Tap **"Create New Wallet"**
- **IMPORTANT**: Write down your 24-word seed phrase on paper
- Store it securely - never share with anyone!
- Complete wallet setup
- Open Tonkeeper app
- Tap **"Create New Wallet"**
- **IMPORTANT**: Write down your 24-word seed phrase on paper
- Store it securely - never share with anyone!
- Complete wallet setup
3. **Get Your Seed Phrase**:
- If you already have a wallet, go to Settings → Backup
- Enter your passcode
- Copy the 24 words → paste to `SEED` in your `.env` file
- If you already have a wallet, go to Settings → Backup
- Enter your passcode
- Copy the 24 words → paste to `SEED` in your `.env` file
**Format**: `word1 word2 word3 ... word24`
#### 🔗 Fragment Hash
**Prerequisites**: Complete the cookies setup above first.
1. **Connect Wallet to Fragment**:
- Go to [Fragment.com](https://fragment.com)
- Ensure your TON wallet is connected
- Log in with your Telegram account
2. **Extract Hash**:
- Open **Developer Tools** (F12)
- Click on **"Network"** tab
- Navigate to [Fragment Stars Purchase](https://fragment.com/stars/buy)
- Select recipient as **"Buy for myself"**
- Refresh the page (Ctrl+R / Cmd+R)
- Look for network requests containing `?hash=`
- Find the hash value and copy it → paste to `HASH` in your `.env` file
**Expected format**: `?hash=<your_hash_here>`
Hash is fetched automatically from Fragment pages at runtime. You no longer need to add `HASH` to `.env`.
#### 🔑 TON API Key
1. **Get API Key**:
- Visit [TON Console](https://tonconsole.com)
- Create account and login
- Generate new API key
- Copy the key → paste to `API_KEY` in your `.env` file
- Visit [TON Console](https://tonconsole.com)
- Create account and login
- Generate new API key
- Copy the key → paste to `API_KEY` in your `.env` file
**Alternative**: You can also use [TON API](https://tonapi.io) for getting API key.
@@ -159,7 +144,7 @@ result = await stars_client.buy_stars("@username", 50)
### Supported Operations
| Operation | Method | Parameters | Limits |
| ------------------ | ------------------------------- | ---------------------- | ------------------- |
|--------------------|---------------------------------|------------------------|---------------------|
| **TON Topup** | `topup_ton(username, amount)` | Username, TON amount | 1-1,000,000,000 TON |
| **Premium Gift** | `buy_premium(username, months)` | Username, duration | 3, 6, or 12 months |
| **Stars Purchase** | `buy_stars(username, amount)` | Username, Stars amount | 50-1,000,000 Stars |
+1 -1
View File
@@ -18,7 +18,7 @@ class Config:
load_dotenv(env_path)
required_keys = ["COOKIES", "SEED", "HASH", "API_KEY"]
required_keys = ["SEED", "API_KEY"]
missing_keys: list[str] = []
for key in required_keys:
+2
View File
@@ -28,6 +28,8 @@ def setup_logging() -> None:
logging.getLogger("aiogram.dispatcher").setLevel(logging.INFO)
logging.getLogger("aiogram.event").setLevel(logging.ERROR)
logging.getLogger("httpx").setLevel(logging.INFO)
logging.getLogger("httpcore").setLevel(logging.WARNING)
logger = logging.getLogger(__name__)
+40 -19
View File
@@ -7,7 +7,15 @@ from tonutils.client import TonapiClient
from tonutils.wallet import WalletV5R1
from app.core import config
from app.utils import TransactionProcessor, WalletLinker, ApiClient, clean_decode, parse_json_response
from app.utils import (
TransactionProcessor,
WalletLinker,
ApiClient,
clean_decode,
parse_json_response,
load_cookies,
get_fragment_hash,
)
logger = logging.getLogger(__name__)
@@ -15,20 +23,21 @@ logger = logging.getLogger(__name__)
class FragmentPremium:
def __init__(self):
self.headers = {
'accept': 'application/json, text/javascript, */*; q=0.01',
'accept-encoding': 'gzip, deflate, br, zstd',
'accept-language': 'en-US,en;q=0.9,uk;q=0.8,ru;q=0.7',
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
'cookie': config.COOKIES,
'origin': 'https://fragment.com',
'referer': 'https://fragment.com/premium/buy',
'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1',
'x-requested-with': 'XMLHttpRequest'
"accept": "application/json, text/javascript, */*; q=0.01",
"accept-encoding": "gzip, deflate, br, zstd",
"accept-language": "en-US,en;q=0.9,uk;q=0.8,ru;q=0.7",
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
"origin": "https://fragment.com",
"referer": "https://fragment.com/premium/gift",
"user-agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1",
"x-requested-with": "XMLHttpRequest",
}
self.cookies = load_cookies()
self.transaction_processor = TransactionProcessor(clean_decode)
self.wallet_linker = WalletLinker(self.headers, self.transaction_processor)
self.api_client = ApiClient(self.headers, self.wallet_linker)
self.wallet_linker = WalletLinker(self.headers, self.cookies, self.transaction_processor)
self.api_client = ApiClient(self.headers, self.cookies, self.wallet_linker)
@staticmethod
async def _get_account_info():
@@ -47,12 +56,20 @@ class FragmentPremium:
if months not in [3, 6, 12]:
return {"success": False, "error": "Invalid duration. Use 3, 6, or 12 months"}
fragment_hash = await get_fragment_hash(
self.cookies,
self.headers,
"https://fragment.com/premium/gift",
)
if not fragment_hash:
raise RuntimeError("Failed to fetch Fragment hash")
account = await self._get_account_info()
async with httpx.AsyncClient() as client:
search_data = {"query": username, "months": months, "method": "searchPremiumGiftRecipient"}
search_resp = await client.post(f"https://fragment.com/api?hash={config.HASH}",
headers=self.headers, data=search_data)
search_resp = await client.post(f"https://fragment.com/api?hash={fragment_hash}",
headers=self.headers, cookies=self.cookies, data=search_data)
search_result, error = parse_json_response(search_resp, logger, "search")
if search_result is None:
@@ -63,12 +80,12 @@ class FragmentPremium:
return {"success": False, "error": "User not found"}
update_data = {"mode": "new", "lv": "false", "dh": str(int(time.time())), "method": "updatePremiumState"}
await client.post(f"https://fragment.com/api?hash={config.HASH}",
headers=self.headers, data=update_data)
await client.post(f"https://fragment.com/api?hash={fragment_hash}",
headers=self.headers, cookies=self.cookies, data=update_data)
init_data = {"recipient": recipient, "months": months, "method": "initGiftPremiumRequest"}
init_resp = await client.post(f"https://fragment.com/api?hash={config.HASH}",
headers=self.headers, data=init_data)
init_resp = await client.post(f"https://fragment.com/api?hash={fragment_hash}",
headers=self.headers, cookies=self.cookies, data=init_data)
init_result, error = parse_json_response(init_resp, logger, "init")
if init_result is None:
@@ -91,7 +108,11 @@ class FragmentPremium:
'method': 'getGiftPremiumLink'
}
request_success, transaction_result = await self.api_client.execute_transaction_request(tx_data, account)
request_success, transaction_result = await self.api_client.execute_transaction_request(
tx_data,
account,
fragment_hash,
)
if not request_success:
return transaction_result
+38 -17
View File
@@ -7,7 +7,15 @@ from tonutils.client import TonapiClient
from tonutils.wallet import WalletV5R1
from app.core import config
from app.utils import TransactionProcessor, WalletLinker, ApiClient, clean_decode, parse_json_response
from app.utils import (
TransactionProcessor,
WalletLinker,
ApiClient,
clean_decode,
parse_json_response,
load_cookies,
get_fragment_hash,
)
logger = logging.getLogger(__name__)
@@ -15,20 +23,21 @@ logger = logging.getLogger(__name__)
class FragmentStars:
def __init__(self):
self.headers = {
'accept': 'application/json, text/javascript, */*; q=0.01',
'accept-encoding': 'gzip, deflate, br, zstd',
'accept-language': 'en-US,en;q=0.9,uk;q=0.8,ru;q=0.7',
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
'cookie': config.COOKIES,
'origin': 'https://fragment.com',
'referer': 'https://fragment.com/stars/buy',
'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1',
'x-requested-with': 'XMLHttpRequest'
"accept": "application/json, text/javascript, */*; q=0.01",
"accept-encoding": "gzip, deflate, br, zstd",
"accept-language": "en-US,en;q=0.9,uk;q=0.8,ru;q=0.7",
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
"origin": "https://fragment.com",
"referer": "https://fragment.com/stars/buy",
"user-agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1",
"x-requested-with": "XMLHttpRequest",
}
self.cookies = load_cookies()
self.transaction_processor = TransactionProcessor(clean_decode)
self.wallet_linker = WalletLinker(self.headers, self.transaction_processor)
self.api_client = ApiClient(self.headers, self.wallet_linker)
self.wallet_linker = WalletLinker(self.headers, self.cookies, self.transaction_processor)
self.api_client = ApiClient(self.headers, self.cookies, self.wallet_linker)
@staticmethod
async def _get_account_info():
@@ -47,12 +56,20 @@ class FragmentStars:
if amount < 50 or not isinstance(amount, int):
return {"success": False, "error": "Amount must be an integer >= 50 stars"}
fragment_hash = await get_fragment_hash(
self.cookies,
self.headers,
"https://fragment.com/stars/buy",
)
if not fragment_hash:
raise RuntimeError("Failed to fetch Fragment hash")
account = await self._get_account_info()
async with httpx.AsyncClient() as client:
search_data = {"query": username, "quantity": "", "method": "searchStarsRecipient"}
search_resp = await client.post(f"https://fragment.com/api?hash={config.HASH}",
headers=self.headers, data=search_data)
search_resp = await client.post(f"https://fragment.com/api?hash={fragment_hash}",
headers=self.headers, cookies=self.cookies, data=search_data)
search_result, error = parse_json_response(search_resp, logger, "search")
if search_result is None:
@@ -63,8 +80,8 @@ class FragmentStars:
return {"success": False, "error": "User not found"}
init_data = {"recipient": recipient, "quantity": amount, "method": "initBuyStarsRequest"}
init_resp = await client.post(f"https://fragment.com/api?hash={config.HASH}",
headers=self.headers, data=init_data)
init_resp = await client.post(f"https://fragment.com/api?hash={fragment_hash}",
headers=self.headers, cookies=self.cookies, data=init_data)
init_result, error = parse_json_response(init_resp, logger, "init")
if init_result is None:
@@ -83,7 +100,11 @@ class FragmentStars:
'method': 'getBuyStarsLink'
}
request_success, transaction_result = await self.api_client.execute_transaction_request(tx_data, account)
request_success, transaction_result = await self.api_client.execute_transaction_request(
tx_data,
account,
fragment_hash,
)
if not request_success:
return transaction_result
+40 -19
View File
@@ -7,7 +7,15 @@ from tonutils.client import TonapiClient
from tonutils.wallet import WalletV5R1
from app.core import config
from app.utils import TransactionProcessor, WalletLinker, ApiClient, clean_decode, parse_json_response
from app.utils import (
TransactionProcessor,
WalletLinker,
ApiClient,
clean_decode,
parse_json_response,
load_cookies,
get_fragment_hash,
)
logger = logging.getLogger(__name__)
@@ -15,20 +23,21 @@ logger = logging.getLogger(__name__)
class FragmentTon:
def __init__(self):
self.headers = {
'accept': 'application/json, text/javascript, */*; q=0.01',
'accept-encoding': 'gzip, deflate, br, zstd',
'accept-language': 'en-US,en;q=0.9,uk;q=0.8,ru;q=0.7',
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
'cookie': config.COOKIES,
'origin': 'https://fragment.com',
'referer': 'https://fragment.com/ads/topup',
'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1',
'x-requested-with': 'XMLHttpRequest'
"accept": "application/json, text/javascript, */*; q=0.01",
"accept-encoding": "gzip, deflate, br, zstd",
"accept-language": "en-US,en;q=0.9,uk;q=0.8,ru;q=0.7",
"content-type": "application/x-www-form-urlencoded; charset=UTF-8",
"origin": "https://fragment.com",
"referer": "https://fragment.com/ads/topup",
"user-agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1",
"x-requested-with": "XMLHttpRequest",
}
self.cookies = load_cookies()
self.transaction_processor = TransactionProcessor(clean_decode)
self.wallet_linker = WalletLinker(self.headers, self.transaction_processor)
self.api_client = ApiClient(self.headers, self.wallet_linker)
self.wallet_linker = WalletLinker(self.headers, self.cookies, self.transaction_processor)
self.api_client = ApiClient(self.headers, self.cookies, self.wallet_linker)
@staticmethod
async def _get_account_info():
@@ -47,16 +56,24 @@ class FragmentTon:
if amount < 1 or not isinstance(amount, int):
return {"success": False, "error": "Amount must be an integer >= 1 TON"}
fragment_hash = await get_fragment_hash(
self.cookies,
self.headers,
"https://fragment.com/ads/topup",
)
if not fragment_hash:
raise RuntimeError("Failed to fetch Fragment hash")
account = await self._get_account_info()
async with httpx.AsyncClient() as client:
update_data = {"mode": "new", "method": "updateAdsTopupState"}
await client.post(f"https://fragment.com/api?hash={config.HASH}",
headers=self.headers, data=update_data)
await client.post(f"https://fragment.com/api?hash={fragment_hash}",
headers=self.headers, cookies=self.cookies, data=update_data)
search_data = {"query": username, "method": "searchAdsTopupRecipient"}
search_resp = await client.post(f"https://fragment.com/api?hash={config.HASH}",
headers=self.headers, data=search_data)
search_resp = await client.post(f"https://fragment.com/api?hash={fragment_hash}",
headers=self.headers, cookies=self.cookies, data=search_data)
search_result, error = parse_json_response(search_resp, logger, "search")
if search_result is None:
@@ -67,8 +84,8 @@ class FragmentTon:
return {"success": False, "error": "User not found"}
init_data = {"recipient": recipient, "amount": amount, "method": "initAdsTopupRequest"}
init_resp = await client.post(f"https://fragment.com/api?hash={config.HASH}",
headers=self.headers, data=init_data)
init_resp = await client.post(f"https://fragment.com/api?hash={fragment_hash}",
headers=self.headers, cookies=self.cookies, data=init_data)
init_result, error = parse_json_response(init_resp, logger, "init")
if init_result is None:
@@ -90,7 +107,11 @@ class FragmentTon:
'method': 'getAdsTopupLink'
}
request_success, transaction_result = await self.api_client.execute_transaction_request(tx_data, account)
request_success, transaction_result = await self.api_client.execute_transaction_request(
tx_data,
account,
fragment_hash,
)
if not request_success:
return transaction_result
+11 -1
View File
@@ -1,6 +1,16 @@
from app.utils.client import ApiClient, parse_json_response
from app.utils.cookies import load_cookies
from app.utils.decoder import clean_decode
from app.utils.hash import get_fragment_hash
from app.utils.transaction import TransactionProcessor
from app.utils.wallet import WalletLinker
__all__ = ['TransactionProcessor', 'WalletLinker', 'ApiClient', 'clean_decode', 'parse_json_response']
__all__ = [
'TransactionProcessor',
'WalletLinker',
'ApiClient',
'clean_decode',
'parse_json_response',
'load_cookies',
'get_fragment_hash'
]
+18 -14
View File
@@ -1,15 +1,13 @@
from typing import Any
import logging
from typing import Any
import httpx
from app.core import config
def parse_json_response(
response: httpx.Response,
logger: logging.Logger,
context: str,
response: httpx.Response,
logger: logging.Logger,
context: str,
) -> tuple[dict[str, Any] | None, str | None]:
try:
return response.json(), None
@@ -20,26 +18,32 @@ def parse_json_response(
class ApiClient:
def __init__(self, headers: dict, wallet_linker):
def __init__(self, headers: dict, cookies: dict, wallet_linker):
self.headers = headers
self.cookies = cookies
self.wallet_linker = wallet_linker
async def execute_transaction_request(
self,
tx_data: dict[str, Any],
account: dict[str, Any],
fragment_hash: str,
) -> tuple[bool, dict[str, Any]]:
async with httpx.AsyncClient() as client:
tx_resp = await client.post(f"https://fragment.com/api?hash={config.HASH}",
headers=self.headers, data=tx_data)
transaction = tx_resp.json()
tx_resp = await client.post(f"https://fragment.com/api?hash={fragment_hash}",
headers=self.headers, cookies=self.cookies, data=tx_data)
transaction, error = parse_json_response(tx_resp, logging.getLogger(__name__), "transaction")
if transaction is None:
return False, {"success": False, "error": f"Invalid response from Fragment API: {error}"}
if transaction.get("need_verify"):
if not await self.wallet_linker.link_wallet(account):
if not await self.wallet_linker.link_wallet(account, fragment_hash):
return False, {"success": False, "error": "Failed to link wallet"}
tx_resp = await client.post(f"https://fragment.com/api?hash={config.HASH}",
headers=self.headers, data=tx_data)
transaction = tx_resp.json()
tx_resp = await client.post(f"https://fragment.com/api?hash={fragment_hash}",
headers=self.headers, cookies=self.cookies, data=tx_data)
transaction, error = parse_json_response(tx_resp, logging.getLogger(__name__), "transaction")
if transaction is None:
return False, {"success": False, "error": f"Invalid response from Fragment API: {error}"}
return True, transaction
+35
View File
@@ -0,0 +1,35 @@
import json
import logging
from pathlib import Path
from typing import Any
logger = logging.getLogger(__name__)
def load_cookies() -> dict[str, Any]:
cookies_path = Path(__file__).resolve().parents[2] / "cookies.json"
if not cookies_path.exists():
logger.error("cookies.json file not found!")
return {}
try:
with cookies_path.open("r", encoding="utf-8") as file:
cookies = json.load(file)
required_keys = ["stel_ssid", "stel_dt", "stel_token", "stel_ton_token"]
missing_or_empty = [
key for key in required_keys
if not str(cookies.get(key, "")).strip()
]
if missing_or_empty:
logger.warning(
"cookies.json has missing or empty values: %s",
", ".join(missing_or_empty)
)
return cookies
except Exception as exc:
logger.error(f"Failed to load cookies.json: {exc}")
return {}
+35
View File
@@ -0,0 +1,35 @@
import logging
import re
from typing import Any
import httpx
logger = logging.getLogger(__name__)
async def get_fragment_hash(
cookies: dict[str, Any],
headers: dict[str, str],
page_url: str,
) -> str | None:
request_headers = {
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"accept-language": headers.get("accept-language") or headers.get("Accept-Language", "en-US,en;q=0.9"),
"user-agent": headers.get("user-agent") or headers.get("User-Agent", ""),
"referer": "https://fragment.com/",
}
async with httpx.AsyncClient(cookies=cookies) as client:
response = await client.get(page_url, headers=request_headers)
if response.status_code != 200:
logger.error("Failed to fetch Fragment page for hash: %s", response.status_code)
return None
text = response.text
match = re.search(r"(?:https://fragment\.com)?/api\?hash=([a-f0-9]+)", text)
if match:
return match.group(1)
logger.error("Failed to extract Fragment hash from page")
return None
+23 -2
View File
@@ -1,10 +1,9 @@
import logging
from tonutils.client import TonapiClient
from tonutils.client import TonapiClient, ToncenterV3Client
from tonutils.wallet import WalletV5R1
from tonutils.wallet.messages import TransferMessage
from app.core import config
logger = logging.getLogger(__name__)
@@ -14,10 +13,32 @@ class TransactionProcessor:
def __init__(self, clean_decode_func):
self._clean_decode = clean_decode_func
@staticmethod
async def _check_wallet_balance() -> tuple[bool, str | None]:
client = ToncenterV3Client(is_testnet=False, rps=1, max_retries=1)
wallet, _, _, _ = WalletV5R1.from_mnemonic(client=client, mnemonic=config.SEED)
try:
balance = await wallet.balance()
except Exception as exc:
return False, f"Wallet balance check failed: {exc}"
try:
if float(balance) <= 0:
return False, "Wallet balance is zero"
except Exception:
pass
return True, None
async def process_transaction(self, transaction_data: dict) -> tuple[bool, str | None, str | None]:
if "transaction" not in transaction_data or "messages" not in transaction_data["transaction"]:
return False, "Invalid transaction", None
ready, reason = await self._check_wallet_balance()
if not ready:
return False, reason or "Wallet is not ready", None
client = TonapiClient(api_key=config.API_KEY, is_testnet=False)
wallet, _, _, _ = WalletV5R1.from_mnemonic(client=client, mnemonic=config.SEED)
+5 -6
View File
@@ -2,15 +2,14 @@ from typing import Any
import httpx
from app.core import config
class WalletLinker:
def __init__(self, headers: dict, transaction_processor):
def __init__(self, headers: dict, cookies: dict, transaction_processor):
self.headers = headers
self.cookies = cookies
self.transaction_processor = transaction_processor
async def link_wallet(self, account: dict[str, Any]) -> bool:
async def link_wallet(self, account: dict[str, Any], fragment_hash: str) -> bool:
async with httpx.AsyncClient() as client:
data = {
'account': account,
@@ -18,8 +17,8 @@ class WalletLinker:
'method': 'linkWallet'
}
response = await client.post(f"https://fragment.com/api?hash={config.HASH}",
headers=self.headers, data=data)
response = await client.post(f"https://fragment.com/api?hash={fragment_hash}",
headers=self.headers, cookies=self.cookies, data=data)
result = response.json()
if result.get("ok"):
+1 -1
View File
@@ -12,7 +12,7 @@ async def topup_ton_example():
ton_client = FragmentTon()
# @bohd4nx - target username, 5 - TON amount (integer 1-1000000000 (one billion))
result = await ton_client.topup_ton("@bohd4nx", 1000000000)
result = await ton_client.topup_ton("@bohd4nx", 100)
if result["success"]:
data = result["data"]
+1 -1
View File
@@ -1,4 +1,4 @@
python-dotenv==1.2.1
asyncio==4.0.0
httpx==0.28.1
tonutils==0.5.5
tonutils==0.5.6