mirror of
https://github.com/bohd4nx/FragmentAPI.git
synced 2026-07-25 06:14:29 +00:00
refactor: update API key source in .env.example and enhance README with badges and feature descriptions
feat: improve logging levels for HTTP clients and add detailed logging in premium and stars purchase methods chore: remove unused transaction utility and adjust wallet processing logic test: add end-to-end integration test for buying Stars
This commit is contained in:
+1
-1
@@ -4,7 +4,7 @@
|
|||||||
# TON wallet seed phrase - 12 or 24 words separated by spaces
|
# TON wallet seed phrase - 12 or 24 words separated by spaces
|
||||||
SEED = "your_ton_wallet_seed_phrase_here"
|
SEED = "your_ton_wallet_seed_phrase_here"
|
||||||
|
|
||||||
# TON API key - get from https://t.me/tonapibot
|
# TON API key - get from https://tonconsole.com
|
||||||
API_KEY = "your_ton_api_key_here"
|
API_KEY = "your_ton_api_key_here"
|
||||||
|
|
||||||
# TON wallet contract version: V4R2 or V5R1 (default: V5R1)
|
# TON wallet contract version: V4R2 or V5R1 (default: V5R1)
|
||||||
|
|||||||
@@ -7,8 +7,13 @@
|
|||||||
<b>Automate TON topups, Telegram Premium purchases, and Stars transactions via Fragment.com</b>
|
<b>Automate TON topups, Telegram Premium purchases, and Stars transactions via Fragment.com</b>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[Report Bug](https://github.com/bohd4nx/fragmentapi/issues) · [Request Feature](https://github.com/bohd4nx/fragmentapi/issues) · [
|
[](https://python.org)
|
||||||
**Donate TON**](https://app.tonkeeper.com/transfer/UQCppfw5DxWgdVHf3zkmZS8k1mt9oAUYxQLwq2fz3nhO8No5)
|
[](https://github.com/nessshon/tonutils)
|
||||||
|
[](https://github.com/bohd4nx/FragmentAPI/stargazers)
|
||||||
|
[](https://github.com/bohd4nx/FragmentAPI/issues)
|
||||||
|
[](https://github.com/bohd4nx/FragmentAPI/actions)
|
||||||
|
|
||||||
|
[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>
|
</div>
|
||||||
|
|
||||||
@@ -16,9 +21,10 @@
|
|||||||
|
|
||||||
## ✨ Features
|
## ✨ Features
|
||||||
|
|
||||||
- 💰 **TON Advertisement Topups** - Send TON for advertising campaigns and purchasing gifts (1-1,000,000,000 TON)
|
- 💰 **TON Advertisement Topups** — Send TON directly to Fragment ad accounts (1–1,000,000,000 TON)
|
||||||
- 👑 **Telegram Premium Gifts** - Purchase Premium subscriptions (3, 6, or 12 months)
|
- 👑 **Telegram Premium Gifts** — Purchase Premium subscriptions for any user (3, 6, or 12 months)
|
||||||
- ⭐ **Telegram Stars Purchases** - Buy Stars for users (50-1,000,000 Stars)
|
- ⭐ **Telegram Stars Purchases** — Buy Stars and send them to any Telegram user (50–1,000,000 Stars)
|
||||||
|
- 🔐 **Multi-wallet support** — Configurable wallet contract version (V4R2 / V5R1)
|
||||||
|
|
||||||
## 🚀 Quick Start
|
## 🚀 Quick Start
|
||||||
|
|
||||||
@@ -32,88 +38,82 @@ pip install -r requirements.txt
|
|||||||
|
|
||||||
### 2. Configuration
|
### 2. Configuration
|
||||||
|
|
||||||
Copy example configuration and edit:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
|
cp cookies.example.json cookies.json
|
||||||
```
|
```
|
||||||
|
|
||||||
Edit `.env` file:
|
Edit `.env`:
|
||||||
|
|
||||||
```env
|
```env
|
||||||
SEED=word1 word2 word3 ... word24
|
# 24-word TON wallet seed phrase
|
||||||
|
SEED = word1 word2 word3 ... word24
|
||||||
|
|
||||||
API_KEY=your_ton_api_key_here
|
# API key from @tonapibot on Telegram
|
||||||
|
API_KEY = your_tonapi_key_here
|
||||||
|
|
||||||
|
# Wallet contract version: V4R2 or V5R1 (default: V5R1)
|
||||||
|
WALLET_VERSION = V5R1
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3. Getting Required Data
|
### 3. Getting Required Data
|
||||||
|
|
||||||
#### 🍪 Fragment.com Cookies
|
#### 🍪 Fragment.com Cookies
|
||||||
|
|
||||||
**Prerequisites**: Login to your Telegram account and connect the TON wallet you want to use for payments.
|
**Prerequisites**: Log in to Telegram on Fragment and connect the TON wallet you'll use for payments.
|
||||||
|
|
||||||
1. **Install Cookie Editor Extension**:
|
1. Install [Cookie Editor](https://chromewebstore.google.com/detail/cookie-editor/hlkenndednhfkekhgcdicdfddnkalmdm) extension
|
||||||
|
2. Open [fragment.com](https://fragment.com) and make sure you're logged in
|
||||||
- Download
|
3. Click the Cookie Editor icon → **Export** → **Header String**
|
||||||
from [Chrome Web Store](https://chromewebstore.google.com/detail/cookie-editor/hlkenndednhfkekhgcdicdfddnkalmdm)
|
4. Split the result into the four fields in `cookies.json`:
|
||||||
- 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 and split it into JSON fields in `cookies.json`
|
|
||||||
|
|
||||||
**Expected format** (`cookies.json` in project root):
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"stel_ssid": "<SSID>",
|
"stel_ssid": "...",
|
||||||
"stel_dt": "<STEL_DT>",
|
"stel_dt": "...",
|
||||||
"stel_token": "<TOKEN>",
|
"stel_token": "...",
|
||||||
"stel_ton_token": "<TON_TOKEN>"
|
"stel_ton_token": "..."
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 🔐 TON Wallet Seed Phrase
|
#### 🔐 TON Wallet Seed Phrase
|
||||||
|
|
||||||
**If you don't have a TON wallet yet**:
|
If you don't have a TON wallet, create one in [Tonkeeper](https://tonkeeper.com) (iOS / Android).
|
||||||
|
Go to **Settings → Backup**, copy the 24 words and paste them into `SEED` in `.env`.
|
||||||
|
|
||||||
1. **Download Tonkeeper**:
|
> ⚠️ Never share your seed phrase with anyone. Store it offline.
|
||||||
|
|
||||||
- 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
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
**Format**: `word1 word2 word3 ... word24`
|
|
||||||
|
|
||||||
#### 🔗 Fragment Hash
|
|
||||||
|
|
||||||
Hash is fetched automatically from Fragment pages at runtime. You no longer need to add `HASH` to `.env`.
|
|
||||||
|
|
||||||
#### 🔑 TON API Key
|
#### 🔑 TON API Key
|
||||||
|
|
||||||
1. **Get API Key**:
|
1. Go to [tonconsole.com](https://tonconsole.com)
|
||||||
- Visit [TON Console](https://tonconsole.com)
|
2. Create an account and log in
|
||||||
- Create account and login
|
3. Generate a new API key
|
||||||
- Generate new API key
|
4. Paste it into `API_KEY` in `.env`
|
||||||
- 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.
|
#### 🔐 Wallet Version
|
||||||
|
|
||||||
|
| Version | Use when |
|
||||||
|
| ------- | -------------------------------------------------------------- |
|
||||||
|
| `V5R1` | Default — Tonkeeper / MyTonWallet (wallets created after 2024) |
|
||||||
|
| `V4R2` | Older Tonkeeper wallets |
|
||||||
|
|
||||||
|
Not sure? Run this to check which address matches your wallet:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3 -c "
|
||||||
|
import asyncio
|
||||||
|
from tonutils.clients import TonapiClient
|
||||||
|
from tonutils.contracts.wallet import WalletV4R2, WalletV5R1
|
||||||
|
from tonutils.types import NetworkGlobalID
|
||||||
|
from app.core import config
|
||||||
|
|
||||||
|
client = TonapiClient(network=NetworkGlobalID.MAINNET, api_key=config.API_KEY)
|
||||||
|
w4, _, _, _ = WalletV4R2.from_mnemonic(client=client, mnemonic=config.SEED)
|
||||||
|
w5, _, _, _ = WalletV5R1.from_mnemonic(client=client, mnemonic=config.SEED)
|
||||||
|
print('V4R2:', w4.address.to_str(True, True))
|
||||||
|
print('V5R1:', w5.address.to_str(True, True))
|
||||||
|
"
|
||||||
|
```
|
||||||
|
|
||||||
### 4. Usage
|
### 4. Usage
|
||||||
|
|
||||||
@@ -126,35 +126,57 @@ python main.py
|
|||||||
#### Programmatic Usage
|
#### Programmatic Usage
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from app.methods import FragmentTon, FragmentPremium, FragmentStars
|
import asyncio
|
||||||
|
from app.methods import topup_ton, buy_premium, buy_stars
|
||||||
|
|
||||||
# TON topup for ads
|
async def main():
|
||||||
ton_client = FragmentTon()
|
# Send 10 TON to @username
|
||||||
result = await ton_client.topup_ton("@username", 5)
|
result = await topup_ton("@username", 10)
|
||||||
|
print(result)
|
||||||
|
|
||||||
# Premium purchase
|
# Gift 6 months of Telegram Premium
|
||||||
premium_client = FragmentPremium()
|
result = await buy_premium("@username", 6)
|
||||||
result = await premium_client.buy_premium("@username", 6)
|
print(result)
|
||||||
|
|
||||||
# Stars purchase
|
# Buy 500 Stars for @username
|
||||||
stars_client = FragmentStars()
|
result = await buy_stars("@username", 500)
|
||||||
result = await stars_client.buy_stars("@username", 50)
|
print(result)
|
||||||
|
|
||||||
|
asyncio.run(main())
|
||||||
|
```
|
||||||
|
|
||||||
|
**Return format** (on success):
|
||||||
|
|
||||||
|
```python
|
||||||
|
{
|
||||||
|
"success": True,
|
||||||
|
"data": {
|
||||||
|
"transaction_id": "<TL-B ExternalMessage ...>",
|
||||||
|
"username": "@username",
|
||||||
|
"amount": 10, # or "months" for Premium
|
||||||
|
"timestamp": 1741234567
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Return format** (on failure):
|
||||||
|
|
||||||
|
```python
|
||||||
|
{
|
||||||
|
"success": False,
|
||||||
|
"error": "Telegram user '@unknown' was not found on Fragment."
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Supported Operations
|
### Supported Operations
|
||||||
|
|
||||||
| Operation | Method | Parameters | Limits |
|
| Operation | Function | Parameters | Limits |
|
||||||
|--------------------|---------------------------------|------------------------|---------------------|
|
| ------------------ | ------------------------------- | ---------------------- | ------------------- |
|
||||||
| **TON Topup** | `topup_ton(username, amount)` | Username, TON amount | 1-1,000,000,000 TON |
|
| **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 |
|
| **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 |
|
| **Stars Purchase** | `buy_stars(username, amount)` | Username, Stars amount | 50–1,000,000 Stars |
|
||||||
|
|
||||||
### Username Formats
|
Usernames can be passed with or without `@`.
|
||||||
|
|
||||||
All methods accept various username formats:
|
|
||||||
|
|
||||||
- `@username` (with @)
|
|
||||||
- `username` (without @)
|
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@ def setup_logging() -> None:
|
|||||||
file_handler.setFormatter(formatter)
|
file_handler.setFormatter(formatter)
|
||||||
logging.basicConfig(level=logging.DEBUG, handlers=[console_handler, file_handler], force=True)
|
logging.basicConfig(level=logging.DEBUG, handlers=[console_handler, file_handler], force=True)
|
||||||
|
|
||||||
logging.getLogger("httpx").setLevel(logging.INFO)
|
logging.getLogger("httpx").setLevel(logging.WARNING)
|
||||||
logging.getLogger("httpcore").setLevel(logging.WARNING)
|
logging.getLogger("httpcore").setLevel(logging.WARNING)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -87,16 +87,25 @@ async def buy_premium(username: str, months: int) -> dict:
|
|||||||
return {"success": False, "error": "Invalid duration. Choose 3, 6, or 12 months."}
|
return {"success": False, "error": "Invalid duration. Choose 3, 6, or 12 months."}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
logger.info("Loading session cookies")
|
||||||
cookies = load_cookies()
|
cookies = load_cookies()
|
||||||
|
|
||||||
|
logger.info("Fetching Fragment session hash")
|
||||||
fragment_hash = await get_fragment_hash(cookies, HEADERS, PREMIUM_PAGE)
|
fragment_hash = await get_fragment_hash(cookies, HEADERS, PREMIUM_PAGE)
|
||||||
|
|
||||||
|
# logger.info("Retrieving TON wallet info")
|
||||||
account = await get_account_info()
|
account = await get_account_info()
|
||||||
|
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
|
logger.info("Searching recipient: %s", username)
|
||||||
recipient = await search_premium_recipient(
|
recipient = await search_premium_recipient(
|
||||||
client, fragment_hash, cookies, username, months
|
client, fragment_hash, cookies, 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, cookies, recipient, months)
|
||||||
|
|
||||||
|
# logger.info("Requesting transaction payload (req_id=%s)", req_id)
|
||||||
tx_data = {
|
tx_data = {
|
||||||
"account": json.dumps(account),
|
"account": json.dumps(account),
|
||||||
"device": DEVICE,
|
"device": DEVICE,
|
||||||
@@ -109,6 +118,7 @@ async def buy_premium(username: str, months: int) -> dict:
|
|||||||
client, HEADERS, cookies, account, tx_data, fragment_hash
|
client, HEADERS, cookies, account, tx_data, fragment_hash
|
||||||
)
|
)
|
||||||
|
|
||||||
|
logger.info("Broadcasting transaction to TON blockchain")
|
||||||
tx_hash = await process_transaction(transaction)
|
tx_hash = await process_transaction(transaction)
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
|
|||||||
@@ -75,14 +75,23 @@ async def buy_stars(username: str, amount: int) -> dict:
|
|||||||
return {"success": False, "error": "Amount must be an integer >= 50 stars."}
|
return {"success": False, "error": "Amount must be an integer >= 50 stars."}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
logger.info("Loading session cookies")
|
||||||
cookies = load_cookies()
|
cookies = load_cookies()
|
||||||
|
|
||||||
|
logger.info("Fetching Fragment session hash")
|
||||||
fragment_hash = await get_fragment_hash(cookies, HEADERS, STARS_PAGE)
|
fragment_hash = await get_fragment_hash(cookies, HEADERS, STARS_PAGE)
|
||||||
|
|
||||||
|
# logger.info("Retrieving TON wallet info")
|
||||||
account = await get_account_info()
|
account = await get_account_info()
|
||||||
|
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() 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, cookies, 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, cookies, recipient, amount)
|
||||||
|
|
||||||
|
# logger.info("Requesting transaction payload (req_id=%s)", req_id)
|
||||||
tx_data = {
|
tx_data = {
|
||||||
"account": json.dumps(account),
|
"account": json.dumps(account),
|
||||||
"device": DEVICE,
|
"device": DEVICE,
|
||||||
@@ -95,6 +104,7 @@ async def buy_stars(username: str, amount: int) -> dict:
|
|||||||
client, HEADERS, cookies, account, tx_data, fragment_hash
|
client, HEADERS, cookies, account, tx_data, fragment_hash
|
||||||
)
|
)
|
||||||
|
|
||||||
|
logger.info("Broadcasting transaction to TON blockchain")
|
||||||
tx_hash = await process_transaction(transaction)
|
tx_hash = await process_transaction(transaction)
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
|
|||||||
@@ -81,14 +81,23 @@ async def topup_ton(username: str, amount: int) -> dict:
|
|||||||
return {"success": False, "error": "Amount must be an integer >= 1 TON."}
|
return {"success": False, "error": "Amount must be an integer >= 1 TON."}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
logger.info("Loading session cookies")
|
||||||
cookies = load_cookies()
|
cookies = load_cookies()
|
||||||
|
|
||||||
|
logger.info("Fetching Fragment session hash")
|
||||||
fragment_hash = await get_fragment_hash(cookies, HEADERS, ADS_PAGE)
|
fragment_hash = await get_fragment_hash(cookies, HEADERS, ADS_PAGE)
|
||||||
|
|
||||||
|
# logger.info("Retrieving TON wallet info")
|
||||||
account = await get_account_info()
|
account = await get_account_info()
|
||||||
|
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() 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, cookies, 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, cookies, recipient, amount)
|
||||||
|
|
||||||
|
# logger.info("Requesting transaction payload (req_id=%s)", req_id)
|
||||||
tx_data = {
|
tx_data = {
|
||||||
"account": json.dumps(account),
|
"account": json.dumps(account),
|
||||||
"device": DEVICE,
|
"device": DEVICE,
|
||||||
@@ -101,6 +110,7 @@ async def topup_ton(username: str, amount: int) -> dict:
|
|||||||
client, HEADERS, cookies, account, tx_data, fragment_hash
|
client, HEADERS, cookies, account, tx_data, fragment_hash
|
||||||
)
|
)
|
||||||
|
|
||||||
|
logger.info("Broadcasting transaction to TON blockchain")
|
||||||
tx_hash = await process_transaction(transaction)
|
tx_hash = await process_transaction(transaction)
|
||||||
return {
|
return {
|
||||||
"success": True,
|
"success": True,
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
import asyncio
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from tonutils.clients import TonapiClient
|
|
||||||
from tonutils.types import NetworkGlobalID
|
|
||||||
|
|
||||||
from app.core import config
|
|
||||||
from app.core.constants import WALLET_CLASSES
|
|
||||||
from app.core.exceptions import TransactionError, WalletError
|
|
||||||
from app.utils.decoder import clean_decode
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
async def process_transaction(transaction_data: dict) -> str:
|
|
||||||
logger.debug("transaction_data: %s", transaction_data)
|
|
||||||
|
|
||||||
if "transaction" not in transaction_data or "messages" not in transaction_data["transaction"]:
|
|
||||||
raise TransactionError(
|
|
||||||
"Fragment returned an invalid transaction payload. "
|
|
||||||
"The API response is missing expected 'transaction.messages' data."
|
|
||||||
)
|
|
||||||
|
|
||||||
client = TonapiClient(network=NetworkGlobalID.MAINNET, api_key=config.API_KEY)
|
|
||||||
async with client:
|
|
||||||
wallet_cls = WALLET_CLASSES[config.WALLET_VERSION]
|
|
||||||
wallet, _, _, _ = wallet_cls.from_mnemonic(client=client, mnemonic=config.SEED)
|
|
||||||
|
|
||||||
# Check balance before broadcasting
|
|
||||||
try:
|
|
||||||
await wallet.refresh()
|
|
||||||
balance_ton = wallet.balance / 1_000_000_000
|
|
||||||
if balance_ton < 0.056:
|
|
||||||
raise WalletError(
|
|
||||||
f"TON wallet balance is too low: {balance_ton:.2f} TON. "
|
|
||||||
"Minimum required is 0.056 TON."
|
|
||||||
)
|
|
||||||
except WalletError:
|
|
||||||
raise
|
|
||||||
except Exception as exc:
|
|
||||||
raise WalletError(f"Wallet balance check failed: {exc}") from exc
|
|
||||||
|
|
||||||
try:
|
|
||||||
message = transaction_data["transaction"]["messages"][0]
|
|
||||||
payload = clean_decode(message["payload"])
|
|
||||||
|
|
||||||
await wallet.refresh()
|
|
||||||
result = await wallet.transfer(
|
|
||||||
destination=message["address"],
|
|
||||||
amount=int(message["amount"]), # nanotons, not TON
|
|
||||||
body=payload,
|
|
||||||
)
|
|
||||||
|
|
||||||
return result
|
|
||||||
except (WalletError, TransactionError):
|
|
||||||
raise
|
|
||||||
except Exception as exc:
|
|
||||||
raise TransactionError(f"Transaction broadcast failed: {exc}") from exc
|
|
||||||
|
|
||||||
+15
-22
@@ -1,4 +1,3 @@
|
|||||||
import asyncio
|
|
||||||
import base64
|
import base64
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
@@ -29,43 +28,37 @@ async def process_transaction(transaction_data: dict) -> str:
|
|||||||
"The API response is missing expected 'transaction.messages' data."
|
"The API response is missing expected 'transaction.messages' data."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# TODO: Investigate 406 'inbound external message rejected before smart-contract execution'.
|
||||||
|
# This happens when the previous transaction's seqno hasn't been confirmed on-chain yet,
|
||||||
|
# causing the wallet contract to reject the new message.
|
||||||
async with initialize_ton_client() as client:
|
async with initialize_ton_client() as client:
|
||||||
wallet_cls = WALLET_CLASSES[config.WALLET_VERSION]
|
wallet_cls = WALLET_CLASSES[config.WALLET_VERSION]
|
||||||
wallet, _, _, _ = wallet_cls.from_mnemonic(client=client, mnemonic=config.SEED)
|
wallet, _, _, _ = wallet_cls.from_mnemonic(client=client, mnemonic=config.SEED)
|
||||||
|
|
||||||
# Check balance before broadcasting
|
# Check balance before broadcasting
|
||||||
# try:
|
try:
|
||||||
# await wallet.refresh()
|
await wallet.refresh()
|
||||||
# balance_ton = wallet.balance / 1_000_000_000
|
balance_ton = wallet.balance / 1_000_000_000
|
||||||
# if balance_ton < 0.056:
|
if balance_ton < 0.056:
|
||||||
# raise WalletError(
|
raise WalletError(
|
||||||
# f"TON wallet balance is too low: {balance_ton:.2f} TON. "
|
f"TON wallet balance is too low: {balance_ton:.2f} TON. "
|
||||||
# "Minimum required is 0.056 TON."
|
"Minimum required is 0.056 TON."
|
||||||
# )
|
)
|
||||||
# except WalletError:
|
except WalletError:
|
||||||
# raise
|
raise
|
||||||
# except Exception as exc:
|
except Exception as exc:
|
||||||
# raise WalletError(f"Wallet balance check failed: {exc}") from exc
|
raise WalletError(f"Wallet balance check failed: {exc}") from exc
|
||||||
|
|
||||||
try:
|
try:
|
||||||
message = transaction_data["transaction"]["messages"][0]
|
message = transaction_data["transaction"]["messages"][0]
|
||||||
payload = clean_decode(message["payload"])
|
payload = clean_decode(message["payload"])
|
||||||
|
|
||||||
seqno_before = wallet.seqno
|
|
||||||
|
|
||||||
result = await wallet.transfer(
|
result = await wallet.transfer(
|
||||||
destination=message["address"],
|
destination=message["address"],
|
||||||
amount=int(message["amount"]), # nanotons, not TON
|
amount=int(message["amount"]), # nanotons, not TON
|
||||||
body=payload,
|
body=payload,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Wait for on-chain confirmation so the next call sees updated seqno
|
|
||||||
for _ in range(30):
|
|
||||||
await asyncio.sleep(3)
|
|
||||||
await wallet.refresh()
|
|
||||||
if wallet.seqno != seqno_before:
|
|
||||||
break
|
|
||||||
|
|
||||||
return result
|
return result
|
||||||
except (WalletError, TransactionError):
|
except (WalletError, TransactionError):
|
||||||
raise
|
raise
|
||||||
|
|||||||
@@ -0,0 +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).
|
||||||
|
"""
|
||||||
|
|
||||||
|
from app.methods.stars import buy_stars
|
||||||
|
|
||||||
|
|
||||||
|
async def test_buy_stars_e2e(cookies):
|
||||||
|
result = await buy_stars("@bohd4nx", 50)
|
||||||
|
assert result["success"] is True, result.get("error")
|
||||||
|
assert result["data"]["transaction_id"]
|
||||||
Reference in New Issue
Block a user