mirror of
https://github.com/vibe-existing/pyfragment.git
synced 2026-07-25 06:54:31 +00:00
feat: implement CI/CD workflows for testing, linting, and publishing to PyPI
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
name: Bug report
|
||||
description: Report an issue or unexpected behavior in FragmentAPI.
|
||||
labels:
|
||||
- bug
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
label: Checklist
|
||||
options:
|
||||
- label: I am sure the error is coming from FragmentAPI code
|
||||
required: true
|
||||
- label: I have searched the issue tracker for similar bug reports, including closed ones
|
||||
required: true
|
||||
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
## Context
|
||||
Please provide as much detail as possible to help us reproduce and fix the issue.
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: Operating system
|
||||
placeholder: e.g. Ubuntu 22.04 / macOS 14 / Windows 11
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: Python version
|
||||
description: Run `python --version` inside your virtualenv
|
||||
placeholder: e.g. 3.12.3
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
attributes:
|
||||
label: FragmentAPI version
|
||||
description: Run `pip show fragmentapi` inside your virtualenv
|
||||
placeholder: e.g. 2026.1.0
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
description: Describe what you expected to happen.
|
||||
placeholder: e.g. Stars should be gifted and StarsResult returned.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Current behavior
|
||||
description: Describe what is actually happening.
|
||||
placeholder: e.g. RequestError is raised with status 400.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: Minimal steps that reproduce the issue.
|
||||
placeholder: |
|
||||
1. Create FragmentClient with valid credentials
|
||||
2. Call gift_stars("@username", amount=100)
|
||||
3. See error
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Code example
|
||||
description: Provide a [minimal reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) if applicable.
|
||||
placeholder: |
|
||||
import asyncio
|
||||
from fragmentapi import FragmentClient
|
||||
|
||||
async def main():
|
||||
client = FragmentClient(...)
|
||||
result = await client.gift_stars("@username", amount=100)
|
||||
|
||||
asyncio.run(main())
|
||||
render: python
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Traceback / logs
|
||||
description: Paste the full traceback or relevant logs.
|
||||
placeholder: |
|
||||
Traceback (most recent call last):
|
||||
File "main.py", line 7, in main
|
||||
...
|
||||
fragmentapi.types.RequestError: ...
|
||||
render: sh
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional information
|
||||
description: Anything else that might help us diagnose the problem.
|
||||
placeholder: e.g. Only happens with V5R1 wallet version.
|
||||
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: true
|
||||
contact_links:
|
||||
- name: Ask a question or start a discussion
|
||||
url: https://github.com/bohd4nx/FragmentAPI/discussions
|
||||
about: General questions, ideas, and community help go here — not in the issue tracker.
|
||||
@@ -0,0 +1,51 @@
|
||||
name: Feature request
|
||||
description: Suggest an improvement or new feature for FragmentAPI.
|
||||
labels:
|
||||
- enhancement
|
||||
body:
|
||||
- type: dropdown
|
||||
attributes:
|
||||
label: FragmentAPI version
|
||||
description: Which version are you running?
|
||||
options:
|
||||
- latest
|
||||
- older
|
||||
- n/a
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Problem
|
||||
description: Is your request related to a specific problem? Describe it.
|
||||
placeholder: e.g. There is no way to check my current TON balance before sending.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Proposed solution
|
||||
description: Describe what you would like to see added or changed.
|
||||
placeholder: e.g. Add a get_balance() method to FragmentClient.
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Alternatives considered
|
||||
description: Any workarounds or alternative approaches you have thought of.
|
||||
placeholder: e.g. I manually call the Fragment API, but it's not ergonomic.
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Code example
|
||||
description: A short example demonstrating the desired API, if applicable.
|
||||
placeholder: |
|
||||
balance = await client.get_balance()
|
||||
print(balance.ton)
|
||||
render: python
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Additional information
|
||||
description: Any other context, screenshots, or references.
|
||||
@@ -0,0 +1,34 @@
|
||||
# Description
|
||||
|
||||
Please include a summary of the change and which issue is fixed.
|
||||
Include relevant motivation and context.
|
||||
|
||||
Fixes # (issue)
|
||||
|
||||
## Type of change
|
||||
|
||||
- [ ] Documentation (typos, examples, or any docs update)
|
||||
- [ ] Bug fix (non-breaking change which fixes an issue)
|
||||
- [ ] New feature (non-breaking change which adds functionality)
|
||||
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
|
||||
- [ ] This change requires a documentation update
|
||||
|
||||
## How has this been tested?
|
||||
|
||||
Describe the tests you ran to verify the change and list any relevant details.
|
||||
|
||||
- [ ] Existing tests pass (`pytest`)
|
||||
- [ ] New tests added for this change
|
||||
|
||||
**Test configuration:**
|
||||
* OS:
|
||||
* Python version:
|
||||
* FragmentAPI version:
|
||||
|
||||
## Checklist
|
||||
|
||||
- [ ] My code follows the style guidelines of this project
|
||||
- [ ] I have performed a self-review of my own code
|
||||
- [ ] I have updated documentation where necessary
|
||||
- [ ] I have added tests that prove my fix or feature works
|
||||
- [ ] All new and existing tests pass locally
|
||||
@@ -0,0 +1,31 @@
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint & format
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6.0.2
|
||||
|
||||
- uses: actions/setup-python@v6.2.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v7.5.0
|
||||
|
||||
- name: Install dev dependencies
|
||||
run: uv pip install --system ".[dev]"
|
||||
|
||||
- name: Run ruff
|
||||
run: ruff check .
|
||||
|
||||
- name: Run black
|
||||
run: black --check . --target-version py312
|
||||
@@ -0,0 +1,49 @@
|
||||
name: Publish to PyPI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6.0.2
|
||||
|
||||
- uses: actions/setup-python@v6.2.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v7.5.0
|
||||
|
||||
- name: Build distribution
|
||||
run: uv build
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: dist
|
||||
path: dist/*
|
||||
|
||||
publish:
|
||||
name: Publish
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: pypi
|
||||
url: https://pypi.org/project/fragmentapi/
|
||||
permissions:
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@v8.0.1
|
||||
with:
|
||||
name: dist
|
||||
path: dist
|
||||
|
||||
- name: Publish to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@v1.13.0
|
||||
@@ -8,18 +8,21 @@ on:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Run tests
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: actions/checkout@v6.0.2
|
||||
|
||||
- uses: actions/setup-python@v6
|
||||
- uses: actions/setup-python@v6.2.0
|
||||
with:
|
||||
python-version: "3.12"
|
||||
cache: "pip"
|
||||
|
||||
- name: Install uv
|
||||
uses: astral-sh/setup-uv@v7.5.0
|
||||
|
||||
- name: Install dependencies
|
||||
run: pip install -r requirements.txt pytest pytest-asyncio
|
||||
run: uv pip install --system ".[dev]"
|
||||
|
||||
- name: Write cookies.json
|
||||
if: ${{ env.COOKIES_JSON != '' }}
|
||||
|
||||
+13
@@ -24,3 +24,16 @@ logs/
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
cookies.json
|
||||
|
||||
# Testing & tooling artifacts
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
.mypy_cache/
|
||||
.ruff_cache/
|
||||
.coverage
|
||||
htmlcov/
|
||||
|
||||
# Build & distribution
|
||||
dist/
|
||||
build/
|
||||
*.egg-info/
|
||||
|
||||
Binary file not shown.
@@ -17,9 +17,9 @@ from fragmentapi.types import (
|
||||
)
|
||||
from fragmentapi.utils import (
|
||||
execute_transaction_request,
|
||||
fragment_post,
|
||||
get_account_info,
|
||||
get_fragment_hash,
|
||||
parse_json_response,
|
||||
process_transaction,
|
||||
)
|
||||
|
||||
@@ -40,16 +40,16 @@ async def _search_recipient(
|
||||
username: str,
|
||||
months: int,
|
||||
) -> str:
|
||||
resp = await session.post(
|
||||
f"https://fragment.com/api?hash={fragment_hash}",
|
||||
headers=HEADERS,
|
||||
data={
|
||||
result = await fragment_post(
|
||||
session,
|
||||
fragment_hash,
|
||||
HEADERS,
|
||||
{
|
||||
"query": username,
|
||||
"months": months,
|
||||
"method": "searchPremiumGiftRecipient",
|
||||
},
|
||||
)
|
||||
result = parse_json_response(resp, "searchPremiumGiftRecipient")
|
||||
recipient = result.get("found", {}).get("recipient")
|
||||
if not recipient:
|
||||
raise UserNotFoundError(UserNotFoundError.NOT_FOUND.format(username=username))
|
||||
@@ -62,26 +62,27 @@ async def _init_request(
|
||||
recipient: str,
|
||||
months: int,
|
||||
) -> str:
|
||||
await session.post(
|
||||
f"https://fragment.com/api?hash={fragment_hash}",
|
||||
headers=HEADERS,
|
||||
data={
|
||||
await fragment_post(
|
||||
session,
|
||||
fragment_hash,
|
||||
HEADERS,
|
||||
{
|
||||
"mode": "new",
|
||||
"lv": "false",
|
||||
"dh": str(int(time.time())),
|
||||
"method": "updatePremiumState",
|
||||
},
|
||||
)
|
||||
resp = await session.post(
|
||||
f"https://fragment.com/api?hash={fragment_hash}",
|
||||
headers=HEADERS,
|
||||
data={
|
||||
result = await fragment_post(
|
||||
session,
|
||||
fragment_hash,
|
||||
HEADERS,
|
||||
{
|
||||
"recipient": recipient,
|
||||
"months": months,
|
||||
"method": "initGiftPremiumRequest",
|
||||
},
|
||||
)
|
||||
result = parse_json_response(resp, "initGiftPremiumRequest")
|
||||
req_id = result.get("req_id")
|
||||
if not req_id:
|
||||
raise FragmentAPIError(FragmentAPIError.NO_REQUEST_ID.format(context="Premium purchase"))
|
||||
|
||||
@@ -16,9 +16,9 @@ from fragmentapi.types import (
|
||||
)
|
||||
from fragmentapi.utils import (
|
||||
execute_transaction_request,
|
||||
fragment_post,
|
||||
get_account_info,
|
||||
get_fragment_hash,
|
||||
parse_json_response,
|
||||
process_transaction,
|
||||
)
|
||||
|
||||
@@ -38,12 +38,16 @@ async def _search_recipient(
|
||||
fragment_hash: str,
|
||||
username: str,
|
||||
) -> str:
|
||||
resp = await session.post(
|
||||
f"https://fragment.com/api?hash={fragment_hash}",
|
||||
headers=HEADERS,
|
||||
data={"query": username, "quantity": "", "method": "searchStarsRecipient"},
|
||||
result = await fragment_post(
|
||||
session,
|
||||
fragment_hash,
|
||||
HEADERS,
|
||||
{
|
||||
"query": username,
|
||||
"quantity": "",
|
||||
"method": "searchStarsRecipient",
|
||||
},
|
||||
)
|
||||
result = parse_json_response(resp, "searchStarsRecipient")
|
||||
recipient = result.get("found", {}).get("recipient")
|
||||
if not recipient:
|
||||
raise UserNotFoundError(UserNotFoundError.NOT_FOUND.format(username=username))
|
||||
@@ -56,16 +60,16 @@ async def _init_request(
|
||||
recipient: str,
|
||||
amount: int,
|
||||
) -> str:
|
||||
resp = await session.post(
|
||||
f"https://fragment.com/api?hash={fragment_hash}",
|
||||
headers=HEADERS,
|
||||
data={
|
||||
result = await fragment_post(
|
||||
session,
|
||||
fragment_hash,
|
||||
HEADERS,
|
||||
{
|
||||
"recipient": recipient,
|
||||
"quantity": amount,
|
||||
"method": "initBuyStarsRequest",
|
||||
},
|
||||
)
|
||||
result = parse_json_response(resp, "initBuyStarsRequest")
|
||||
req_id = result.get("req_id")
|
||||
if not req_id:
|
||||
raise FragmentAPIError(FragmentAPIError.NO_REQUEST_ID.format(context="Stars purchase"))
|
||||
|
||||
+15
-16
@@ -16,9 +16,9 @@ from fragmentapi.types import (
|
||||
)
|
||||
from fragmentapi.utils import (
|
||||
execute_transaction_request,
|
||||
fragment_post,
|
||||
get_account_info,
|
||||
get_fragment_hash,
|
||||
parse_json_response,
|
||||
process_transaction,
|
||||
)
|
||||
|
||||
@@ -38,17 +38,16 @@ async def _search_recipient(
|
||||
fragment_hash: str,
|
||||
username: str,
|
||||
) -> str:
|
||||
await session.post(
|
||||
f"https://fragment.com/api?hash={fragment_hash}",
|
||||
headers=HEADERS,
|
||||
data={"mode": "new", "method": "updateAdsTopupState"},
|
||||
await fragment_post(session, fragment_hash, HEADERS, {"mode": "new", "method": "updateAdsTopupState"})
|
||||
result = await fragment_post(
|
||||
session,
|
||||
fragment_hash,
|
||||
HEADERS,
|
||||
{
|
||||
"query": username,
|
||||
"method": "searchAdsTopupRecipient",
|
||||
},
|
||||
)
|
||||
resp = await session.post(
|
||||
f"https://fragment.com/api?hash={fragment_hash}",
|
||||
headers=HEADERS,
|
||||
data={"query": username, "method": "searchAdsTopupRecipient"},
|
||||
)
|
||||
result = parse_json_response(resp, "searchAdsTopupRecipient")
|
||||
recipient = result.get("found", {}).get("recipient")
|
||||
if not recipient:
|
||||
raise UserNotFoundError(UserNotFoundError.NOT_FOUND.format(username=username))
|
||||
@@ -61,16 +60,16 @@ async def _init_request(
|
||||
recipient: str,
|
||||
amount: int,
|
||||
) -> str:
|
||||
resp = await session.post(
|
||||
f"https://fragment.com/api?hash={fragment_hash}",
|
||||
headers=HEADERS,
|
||||
data={
|
||||
result = await fragment_post(
|
||||
session,
|
||||
fragment_hash,
|
||||
HEADERS,
|
||||
{
|
||||
"recipient": recipient,
|
||||
"amount": amount,
|
||||
"method": "initAdsTopupRequest",
|
||||
},
|
||||
)
|
||||
result = parse_json_response(resp, "initAdsTopupRequest")
|
||||
req_id = result.get("req_id")
|
||||
if not req_id:
|
||||
raise FragmentAPIError(FragmentAPIError.NO_REQUEST_ID.format(context="TON topup"))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from fragmentapi.types.constants import (
|
||||
BASE_HEADERS,
|
||||
DEVICE,
|
||||
MIN_TON_BALANCE,
|
||||
PREMIUM_PAGE,
|
||||
REQUIRED_COOKIE_KEYS,
|
||||
STARS_PAGE,
|
||||
@@ -30,6 +31,7 @@ __all__ = [
|
||||
# constants
|
||||
"BASE_HEADERS",
|
||||
"DEVICE",
|
||||
"MIN_TON_BALANCE",
|
||||
"PREMIUM_PAGE",
|
||||
"REQUIRED_COOKIE_KEYS",
|
||||
"STARS_PAGE",
|
||||
|
||||
@@ -10,6 +10,9 @@ SUPPORTED_WALLET_VERSIONS: frozenset[str] = frozenset(get_args(WalletVersion))
|
||||
# Wallet class map — used to resolve the correct contract from WALLET_VERSION
|
||||
WALLET_CLASSES: dict[str, type] = {"V4R2": WalletV4R2, "V5R1": WalletV5R1}
|
||||
|
||||
# Minimum wallet balance required to cover TON network gas fees.
|
||||
MIN_TON_BALANCE: float = 0.056
|
||||
|
||||
# Required Fragment session cookie keys
|
||||
REQUIRED_COOKIE_KEYS: tuple[str, ...] = ("stel_ssid", "stel_dt", "stel_token", "stel_ton_token")
|
||||
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
from fragmentapi.utils.client import (
|
||||
from fragmentapi.utils.decoder import clean_decode
|
||||
from fragmentapi.utils.http import (
|
||||
execute_transaction_request,
|
||||
fragment_post,
|
||||
get_fragment_hash,
|
||||
parse_json_response,
|
||||
)
|
||||
from fragmentapi.utils.decoder import clean_decode
|
||||
from fragmentapi.utils.wallet import get_account_info, process_transaction
|
||||
|
||||
__all__ = [
|
||||
"clean_decode",
|
||||
"execute_transaction_request",
|
||||
"fragment_post",
|
||||
"get_account_info",
|
||||
"get_fragment_hash",
|
||||
"parse_json_response",
|
||||
|
||||
@@ -6,6 +6,21 @@ from fragmentapi.types import RequestError
|
||||
|
||||
|
||||
def clean_decode(payload: str) -> str:
|
||||
"""Decode a base64-encoded BOC payload to a plain-text comment string.
|
||||
|
||||
Fragment transaction payloads are BOC-serialised TVM cells. This function
|
||||
base64-decodes the payload, parses the cell, skips the 32-bit op-code
|
||||
prefix, and reads the snake-encoded UTF-8 comment.
|
||||
|
||||
Args:
|
||||
payload: Base64url-encoded BOC string (padding is added automatically).
|
||||
|
||||
Returns:
|
||||
Decoded comment string, or ``""`` for an empty payload.
|
||||
|
||||
Raises:
|
||||
RequestError: If the payload cannot be decoded or parsed.
|
||||
"""
|
||||
s = payload.strip()
|
||||
if not s:
|
||||
return ""
|
||||
|
||||
@@ -76,6 +76,35 @@ def parse_json_response(response: httpx.Response, context: str) -> dict[str, Any
|
||||
raise RequestError(RequestError.UNPARSEABLE.format(context=context, exc=exc)) from exc
|
||||
|
||||
|
||||
async def fragment_post(
|
||||
session: httpx.AsyncClient,
|
||||
fragment_hash: str,
|
||||
headers: dict[str, str],
|
||||
data: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
"""POST a single request to the Fragment API.
|
||||
|
||||
Builds the ``/api?hash=`` URL, sends the request, and returns the
|
||||
parsed JSON body. Use this for every API method call — search,
|
||||
init, state updates, etc.
|
||||
|
||||
Args:
|
||||
session: Active httpx session with Fragment cookies.
|
||||
fragment_hash: Short-lived hash from the Fragment page HTML.
|
||||
headers: Page-specific HTTP headers.
|
||||
data: Form data payload; must include a ``"method"`` key.
|
||||
|
||||
Returns:
|
||||
Parsed API response as a dict.
|
||||
"""
|
||||
resp = await session.post(
|
||||
f"https://fragment.com/api?hash={fragment_hash}",
|
||||
headers=headers,
|
||||
data=data,
|
||||
)
|
||||
return parse_json_response(resp, data.get("method", "request"))
|
||||
|
||||
|
||||
async def execute_transaction_request(
|
||||
session: httpx.AsyncClient,
|
||||
headers: dict,
|
||||
@@ -97,9 +126,7 @@ async def execute_transaction_request(
|
||||
VerificationError: If Fragment requires KYC verification.
|
||||
RequestError: If the response cannot be parsed.
|
||||
"""
|
||||
url = f"https://fragment.com/api?hash={fragment_hash}"
|
||||
resp = await session.post(url, headers=headers, data=tx_data)
|
||||
transaction = parse_json_response(resp, tx_data.get("method", "transaction"))
|
||||
transaction = await fragment_post(session, fragment_hash, headers, tx_data)
|
||||
|
||||
if transaction.get("need_verify"):
|
||||
raise VerificationError(VerificationError.KYC_REQUIRED)
|
||||
@@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Any
|
||||
from tonutils.clients import TonapiClient
|
||||
from tonutils.types import NetworkGlobalID
|
||||
|
||||
from fragmentapi.types import WALLET_CLASSES, TransactionError, WalletError
|
||||
from fragmentapi.types import MIN_TON_BALANCE, WALLET_CLASSES, TransactionError, WalletError
|
||||
from fragmentapi.utils.decoder import clean_decode
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -16,6 +16,22 @@ def _init_ton_client(client: "FragmentClient") -> TonapiClient:
|
||||
|
||||
|
||||
async def process_transaction(client: "FragmentClient", transaction_data: dict) -> str:
|
||||
"""Sign and broadcast a Fragment transaction to the TON network.
|
||||
|
||||
Validates the payload structure, checks the wallet balance, decodes the
|
||||
on-chain comment, and calls ``wallet.transfer``.
|
||||
|
||||
Args:
|
||||
client: Authenticated :class:`FragmentClient` instance.
|
||||
transaction_data: Raw transaction dict from ``execute_transaction_request``.
|
||||
|
||||
Returns:
|
||||
Normalised transaction hash string.
|
||||
|
||||
Raises:
|
||||
TransactionError: If the payload is malformed or the broadcast fails.
|
||||
WalletError: If the wallet balance is too low or cannot be fetched.
|
||||
"""
|
||||
if "transaction" not in transaction_data or "messages" not in transaction_data["transaction"]:
|
||||
raise TransactionError(TransactionError.INVALID_PAYLOAD)
|
||||
|
||||
@@ -30,7 +46,7 @@ async def process_transaction(client: "FragmentClient", transaction_data: dict)
|
||||
try:
|
||||
await wallet.refresh()
|
||||
balance_ton = wallet.balance / 1_000_000_000
|
||||
if balance_ton < 0.056:
|
||||
if balance_ton < MIN_TON_BALANCE:
|
||||
raise WalletError(WalletError.LOW_BALANCE.format(balance=balance_ton))
|
||||
except WalletError:
|
||||
raise
|
||||
@@ -54,6 +70,21 @@ async def process_transaction(client: "FragmentClient", transaction_data: dict)
|
||||
|
||||
|
||||
async def get_account_info(client: "FragmentClient") -> dict[str, Any]:
|
||||
"""Fetch wallet address, public key, and state-init for the Fragment API.
|
||||
|
||||
Fragment requires account info to build each transaction payload. The
|
||||
returned dict is JSON-serialised and passed as the ``account`` field in
|
||||
``getBuy*Link`` / ``get*Link`` requests.
|
||||
|
||||
Args:
|
||||
client: Authenticated :class:`FragmentClient` instance.
|
||||
|
||||
Returns:
|
||||
Dict with ``address``, ``publicKey``, ``chain``, ``walletStateInit``.
|
||||
|
||||
Raises:
|
||||
WalletError: If account info cannot be retrieved.
|
||||
"""
|
||||
async with _init_ton_client(client) as ton:
|
||||
try:
|
||||
wallet_cls = WALLET_CLASSES[client.wallet_version]
|
||||
|
||||
+12
-2
@@ -30,6 +30,14 @@ dependencies = [
|
||||
"tonutils[pytoniq]==2.0.0",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"pytest>=9.0",
|
||||
"pytest-asyncio>=1.0",
|
||||
"ruff",
|
||||
"black",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/bohd4nx/FragmentAPI"
|
||||
Repository = "https://github.com/bohd4nx/FragmentAPI"
|
||||
@@ -49,11 +57,13 @@ line-length = 128
|
||||
target-version = ["py312"]
|
||||
|
||||
[tool.ruff]
|
||||
target-version = "py312"
|
||||
line-length = 128
|
||||
target-version = "py312"
|
||||
|
||||
[tool.ruff.lint]
|
||||
# E — pycodestyle errors, F — pyflakes, W — warnings, I — isort
|
||||
select = ["E", "F", "W", "I"]
|
||||
# E501 — line too long (covered by line-length above)
|
||||
ignore = ["E501"]
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
"tests/*" = ["E402"]
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
-e .[dev]
|
||||
+1
-2
@@ -1,2 +1 @@
|
||||
httpx==0.28.1
|
||||
tonutils[pytoniq]==2.0.0
|
||||
-e .
|
||||
|
||||
Reference in New Issue
Block a user