diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d23d6d9..4d79616 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -16,7 +16,7 @@ jobs:
- uses: actions/setup-python@v6
with:
- python-version: "3.10"
+ python-version: "3.11"
cache: pip
- run: pip install ".[dev]"
@@ -30,7 +30,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- python-version: [ "3.10", "3.11", "3.12", "3.13", "3.14"]
+ python-version: ["3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@v6
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
deleted file mode 100644
index 1db8218..0000000
--- a/CONTRIBUTING.md
+++ /dev/null
@@ -1,58 +0,0 @@
-# Contributing to pyfragment
-
-## Development setup
-
-```bash
-git clone https://github.com/bohd4nx/pyfragment.git
-cd pyfragment
-pip install -e ".[dev]"
-```
-
-## Running checks
-
-```bash
-# Lint and format
-ruff check . --fix && ruff format .
-
-# Type check
-mypy . --explicit-package-bases
-
-# Tests
-pytest
-```
-
-All three must pass before opening a PR.
-
-## Project structure
-
-```
-pyfragment/
- client.py — FragmentClient (public entry point)
- enums.py — ApiProvider, PaymentMethod, WalletVersion
- exceptions.py — exception hierarchy
- core/ — constants, validation helpers
- domains/ — one package per feature domain
- ads/ — recharge_ads, topup_gram
- anonymous_numbers/— get_login_code, toggle_login_codes, terminate_sessions
- giveaways/ — giveaway_stars, giveaway_premium
- marketplace/ — search_usernames, search_numbers, search_gifts
- purchases/ — purchase_stars, purchase_premium
- services/ — shared infrastructure services
- cookies/ — browser cookie extraction (models + service)
- tonapi/ — wallet info, transaction signing (tonapi/toncenter)
-tests/ — unit tests (pytest)
-examples/ — runnable usage examples (excluded from CI)
-```
-
-## Conventions
-
-- All public async methods live on `FragmentClient` and delegate to a domain service.
-- Domain functions receive a `FragmentClient` instance, never raw httpx clients.
-- Patch targets in tests use the module where the name is **defined**, e.g. `pyfragment.services.tonapi.transaction._make_ton_client`.
-- Versioning follows [CalVer](https://calver.org/): `YYYY.MINOR.MICRO`. Bump in `pyproject.toml`; tag as `vYYYY.MINOR.MICRO`.
-
-## Pull requests
-
-- Keep PRs focused — one feature or fix per PR.
-- Update `CHANGELOG.md` under `[Unreleased]`.
-- Add or update tests for any changed behaviour.
diff --git a/README.md b/README.md
index d4dbf8a..35de889 100644
--- a/README.md
+++ b/README.md
@@ -9,18 +9,14 @@
[](https://github.com/bohd4nx/pyfragment/actions)
[](LICENSE)
-Async Python client for the **[Fragment](https://fragment.com)** marketplace API.
-Buy Stars & Premium, run giveaways, top up GRAM (ex TON) and Ads balances,
-manage anonymous numbers, and search Fragment listings.
+Async Python client for the **[Fragment.com](https://fragment.com)** marketplace API.
-**[Documentation](https://bohd4nx.gitbook.io/pyfragment/)** · **[Examples](https://github.com/bohd4nx/pyfragment/tree/master/examples)** · **[Changelog](CHANGELOG.md)** · **[Donate GRAM](https://app.tonkeeper.com/transfer/UQCppfw5DxWgdVHf3zkmZS8k1mt9oAUYxQLwq2fz3nhO8No5)**
+**[Documentation](https://bohd4nx.gitbook.io/pyfragment/)** · **[Examples](https://github.com/bohd4nx/pyfragment/tree/master/examples)**
> **Disclaimer:** This project is not affiliated with [Fragment](https://fragment.com) or [Telegram](https://telegram.org).
----
-
## Installation
```bash
@@ -46,120 +42,24 @@ from pyfragment.enums import PaymentMethod
async def main() -> None:
async with FragmentClient(
seed="word1 word2 ... word24",
- api_key="YOUR_API_KEY", # tonconsole.com (tonapi, default) or t.me/toncenter
- cookies={
- "stel_ssid": "...",
- "stel_dt": "...",
- "stel_token": "...",
- "stel_ton_token": "...",
- },
- wallet_version="V5R1", # or "V4R2", "HighloadV2", "HighloadV3R1"
- api_provider="tonapi", # or "toncenter"
+ api_key="YOUR_API_KEY",
+ cookies={"stel_ssid": "...", "stel_dt": "...", "stel_token": "...", "stel_ton_token": "..."},
) as client:
wallet = await client.get_wallet()
print(f"GRAM: {wallet.gram_balance} | USDT: {wallet.usdt_balance}")
- recipient = "https://t.me/username" # also: @username, username
-
- stars = await client.purchase_stars(recipient, amount=500, payment_method=PaymentMethod.USDT_GRAM)
+ stars = await client.purchase_stars("@username", amount=500, payment_method=PaymentMethod.USDT_GRAM)
print(f"Sent {stars.amount} Stars to {stars.username} | tx: {stars.transaction_id}")
- premium = await client.purchase_premium(recipient, months=6, payment_method=PaymentMethod.GRAM)
+ premium = await client.purchase_premium("@username", months=6, payment_method=PaymentMethod.GRAM)
print(f"Sent Premium {premium.amount}m to {premium.username} | tx: {premium.transaction_id}")
asyncio.run(main())
```
----
-
-## Configuration
-
-| Parameter | Type | Default | Description |
-| ---------------- | ------------- | ---------- | ----------------------------------------------------------------------------------------------------------------- |
-| `seed` | `str` | — | 12- or 24-word GRAM (ex TON) wallet mnemonic |
-| `api_key` | `str` | — | API key for the chosen provider (see `api_provider`) |
-| `cookies` | `dict \| str` | — | Fragment session cookies |
-| `wallet_version` | `str` | `"V5R1"` | `"V4R2"` or `"V5R1"` — also accepts `WalletVersion` literal |
-| `api_provider` | `str` | `"tonapi"` | `"tonapi"` ([tonconsole.com](https://tonconsole.com)) or `"toncenter"` ([t.me/toncenter](https://t.me/toncenter)) |
-| `timeout` | `float` | `30.0` | HTTP request timeout in seconds |
-
----
-
-## Credentials
-
-### Fragment cookies
-
-Log in to [fragment.com](https://fragment.com) and connect your GRAM (ex TON) wallet.
-
-**Automatically** (recommended) — reads directly from your browser, no extension needed:
-
-```bash
-pip install "pyfragment[browser]"
-```
-
-```python
-from pyfragment import get_cookies_from_browser
-
-result = get_cookies_from_browser("chrome") # firefox, edge, brave, ...
-# result.cookies — dict[str, str] ready to pass to FragmentClient
-# result.expires — ISO 8601 expiry of stel_ssid, or None for session cookies
-```
-
-**Manually** — use [Cookie Editor](https://chromewebstore.google.com/detail/cookie-editor/hlkenndednhfkekhgcdicdfddnkalmdm) and export: `stel_ssid`, `stel_dt`, `stel_token`, `stel_ton_token`. Pass as a `dict` or JSON string.
-
-Refresh cookies when you get authentication errors.
-
-### Tonapi key
-
-Generate at [tonconsole.com](https://tonconsole.com).
-
-### Seed phrase
-
-12- or 24-word mnemonic from your GRAM (ex TON) wallet (**Tonkeeper → Settings → Backup**). Never share it.
-
----
-
-## Logging
-
-`pyfragment` uses the standard `logging` module under the `pyfragment` namespace and is silent by default:
-
-```python
-import logging
-
-logging.basicConfig(level=logging.INFO)
-logging.getLogger("pyfragment").setLevel(logging.DEBUG) # for detailed request logs
-```
-
----
-
-## Error Handling
-
-All exceptions inherit from `FragmentError`:
-
-```python
-from pyfragment import (
- ConfigurationError, # invalid arguments (amount, months, payment_method…)
- UserNotFoundError, # recipient not found on Fragment
- WalletError, # insufficient GRAM (ex TON) or USDT balance
- TransactionError, # broadcast failed, duplicate seqno, invalid payload
- FragmentAPIError, # Fragment API returned an error response
- FragmentPageError, # page fetch or hash extraction failed
- AnonymousNumberError, # number not owned, wrong state, login code issues
- CookieError, # missing or malformed session cookies
- ParseError, # failed to decode Fragment payload
- VerificationError, # on-chain verification step failed
- OperationError, # generic operation-level failure
- UnexpectedError, # unexpected API response structure
-)
-```
-
----
-
-Made with ❤️ by [@bohd4nx](https://t.me/bohd4nx) · [Contributing](CONTRIBUTING.md) · [Security](SECURITY.md)
-
-**Star ⭐ if you found it useful**
+[Changelog](CHANGELOG.md) · [Contributing](CONTRIBUTING.md) · [Security](SECURITY.md)
diff --git a/SECURITY.md b/SECURITY.md
deleted file mode 100644
index 1800853..0000000
--- a/SECURITY.md
+++ /dev/null
@@ -1,19 +0,0 @@
-# Security Policy
-
-## Reporting a vulnerability
-
-Please **do not** open a public GitHub issue for security vulnerabilities.
-
-Report them privately via GitHub's [Security Advisory](https://github.com/bohd4nx/pyfragment/security/advisories/new) feature, or contact the maintainer directly at [@bohd4nx](https://t.me/bohd4nx) on Telegram.
-
-Include:
-
-- A description of the vulnerability and its potential impact.
-- Steps to reproduce or a proof-of-concept.
-- Affected versions.
-
-You will receive a response within 72 hours. Once the fix is released, the advisory will be published.
-
-## Scope
-
-This library handles sensitive credentials (GRAM (ex TON) seed phrases, Fragment session cookies, Tonapi keys). Please treat any finding that could expose or misuse these credentials as high severity.
diff --git a/pyfragment/core/transport.py b/pyfragment/core/transport.py
index 9ebeb32..58cfac7 100644
--- a/pyfragment/core/transport.py
+++ b/pyfragment/core/transport.py
@@ -8,7 +8,7 @@ from typing import Any, cast
import httpx
from pyfragment.core.constants import DEFAULT_TIMEOUT, FRAGMENT_BASE_URL
-from pyfragment.exceptions import FragmentPageError, ParseError, VerificationError
+from pyfragment.exceptions import FragmentPageError, ParseError
async def get_fragment_hash(
@@ -73,17 +73,3 @@ async def fragment_request(
)
return parse_json_response(resp, data.get("method", "request"))
raise FragmentPageError(FragmentPageError.BAD_STATUS.format(status=429, url=f"{FRAGMENT_BASE_URL}/api"))
-
-
-async def execute_transaction_request(
- session: httpx.AsyncClient,
- headers: dict[str, str],
- tx_data: dict[str, Any],
- fragment_hash: str,
-) -> dict[str, Any]:
- transaction = await fragment_request(session, fragment_hash, headers, tx_data)
-
- if transaction.get("need_verify"):
- raise VerificationError(VerificationError.KYC_REQUIRED)
-
- return transaction
diff --git a/pyfragment/enums.py b/pyfragment/enums.py
index 098f17b..e8ea1c3 100644
--- a/pyfragment/enums.py
+++ b/pyfragment/enums.py
@@ -1,18 +1,10 @@
from __future__ import annotations
-import sys
-from enum import Enum
+from enum import StrEnum
from typing import Any
from tonutils.contracts.wallet import WalletHighloadV2, WalletHighloadV3R1, WalletV4R2, WalletV5R1
-if sys.version_info >= (3, 11):
- from enum import StrEnum
-else:
-
- class StrEnum(str, Enum): # noqa: F811
- pass
-
class PaymentMethod(StrEnum):
GRAM = "ton"
diff --git a/pyfragment/exceptions.py b/pyfragment/exceptions.py
index 9153d24..0dd296e 100644
--- a/pyfragment/exceptions.py
+++ b/pyfragment/exceptions.py
@@ -36,10 +36,6 @@ class ConfigurationError(ClientError):
f"Invalid Stars amount: must be an integer between {STARS_PURCHASE_MIN:,} and {STARS_PURCHASE_MAX:,}."
)
INVALID_GRAM_AMOUNT = f"Invalid GRAM (ex TON) amount: must be an integer between {GRAM_TOPUP_MIN:,} and {GRAM_TOPUP_MAX:,}."
- INVALID_USERNAME = (
- "Invalid username '{username}'. "
- "Must be 5-32 characters and contain only letters (A-Z, a-z), digits (0-9), or underscores (_)."
- )
INVALID_WINNERS_STARS = (
f"Invalid winners count: must be an integer between {STARS_WINNERS_MIN:,} and {STARS_WINNERS_MAX:,}."
)
diff --git a/pyfragment/services/cookies/service.py b/pyfragment/services/cookies/service.py
index c88ed40..9ed7a8a 100644
--- a/pyfragment/services/cookies/service.py
+++ b/pyfragment/services/cookies/service.py
@@ -1,7 +1,7 @@
from __future__ import annotations
import importlib
-from datetime import datetime, timezone
+from datetime import UTC, datetime
from typing import Any
from pyfragment.core.constants import FRAGMENT_BASE_URL, FRAGMENT_DOMAIN, REQUIRED_COOKIE_KEYS
@@ -42,11 +42,11 @@ def get_cookies_from_browser(browser: str = "chrome") -> CookieResult:
if cookie.get("name") == "stel_ssid":
raw = cookie.get("expires")
if isinstance(raw, (int, float)):
- expires_iso = datetime.fromtimestamp(raw, tz=timezone.utc).isoformat()
+ expires_iso = datetime.fromtimestamp(raw, tz=UTC).isoformat()
elif isinstance(raw, str) and raw:
for fmt in ("%Y-%m-%dT%H:%M:%S.%fZ", "%Y-%m-%dT%H:%M:%SZ"):
try:
- expires_iso = datetime.strptime(raw, fmt).replace(tzinfo=timezone.utc).isoformat()
+ expires_iso = datetime.strptime(raw, fmt).replace(tzinfo=UTC).isoformat()
break
except ValueError:
continue
@@ -54,7 +54,7 @@ def get_cookies_from_browser(browser: str = "chrome") -> CookieResult:
if expires_iso:
expires_dt = datetime.fromisoformat(expires_iso)
- if expires_dt < datetime.now(timezone.utc):
+ if expires_dt < datetime.now(UTC):
raise CookieError(CookieError.EXPIRED.format(expires=expires_iso))
return CookieResult(cookies={k: cookie_map[k] for k in REQUIRED_COOKIE_KEYS}, expires=expires_iso)
diff --git a/pyproject.toml b/pyproject.toml
index dbc5a69..4af9914 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -5,51 +5,33 @@ build-backend = "hatchling.build"
[project]
name = "pyfragment"
version = "2026.3.2"
-description = "Async Python client for the Fragment API. Buy Stars and Premium, top up GRAM (ex TON) and Ads balances, run giveaways, manage anonymous numbers, and search Fragment listings."
+description = "Async Python client for the Fragment.com marketplace API."
readme = "README.md"
license = { text = "MIT" }
-requires-python = ">=3.10"
+requires-python = ">=3.11"
authors = [{ name = "bohd4nx" }]
keywords = [
"fragment",
- "fragment-api",
"telegram",
- "telegram-api",
"telegram-stars",
"telegram-premium",
- "telegram-giveaway",
- "telegram-ads",
"ton",
- "ton-blockchain",
"tonapi",
"anonymous-numbers",
- "username-auctions",
- "gift-marketplace",
- "crypto-payments",
- "nft-marketplace",
- "web3",
- "python-client",
- "typed",
"asyncio",
]
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
- "Natural Language :: English",
"Operating System :: OS Independent",
- "Programming Language :: Python",
"Programming Language :: Python :: 3 :: Only",
- "Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Framework :: AsyncIO",
- "Topic :: Software Development :: Libraries",
"Topic :: Software Development :: Libraries :: Python Modules",
- "Topic :: Internet",
- "Topic :: Internet :: WWW/HTTP",
"Typing :: Typed",
]
dependencies = ["httpx>=0.25", "tonutils>=2.0.1"]
@@ -59,7 +41,6 @@ dev = ["pytest", "pytest-asyncio", "pytest-mock", "mypy", "ruff"]
browser = ["rookiepy>=0.5.6"]
[project.urls]
-Homepage = "https://github.com/bohd4nx/pyfragment"
Repository = "https://github.com/bohd4nx/pyfragment"
Documentation = "https://bohd4nx.gitbook.io/pyfragment/"
Issues = "https://github.com/bohd4nx/pyfragment/issues"
@@ -76,20 +57,17 @@ addopts = "-v --tb=short"
[tool.ruff]
line-length = 128
-target-version = "py310"
+target-version = "py311"
[tool.ruff.lint]
-# E — pycodestyle errors, F — pyflakes, W — warnings, I — isort, UP — pyupgrade
select = ["E", "F", "W", "I", "UP"]
-# E501 — line too long (covered by line-length above)
-# UP017 — use datetime.UTC (only available in Python 3.11+, we support 3.10)
-ignore = ["E501", "UP017"]
+ignore = ["E501"]
[tool.ruff.lint.per-file-ignores]
"tests/*" = ["E402"]
[tool.mypy]
-python_version = "3.10"
+python_version = "3.11"
strict = true
exclude = ["^examples/", "^logs/", "^tests/"]
diff --git a/tests/shared.py b/tests/shared.py
index 161cf9e..edb1fd8 100644
--- a/tests/shared.py
+++ b/tests/shared.py
@@ -36,10 +36,6 @@ FAKE_BALANCE_NANOGRAM: int = 1_500_000_000 # 1.5 GRAM (ex TON)
# recharge_ads
FAKE_ADS_ACCOUNT: str = "@mychannel"
-# Revenue withdrawals
-FAKE_WITHDRAWAL_WALLET: str = "EQDxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
-FAKE_REVENUE_TX: str = "revenue_tx_abc123"
-
# Anonymous number
FAKE_HTML_WITH_CODE: str = """