Refactor for GRAM (ex TON) integration

- Updated imports and references from TON to GRAM across multiple modules.
- Renamed functions and variables to reflect the change from TON to GRAM.
- Adjusted validation checks for top-up and recharge amounts to use GRAM limits.
- Modified error messages and exceptions to indicate GRAM instead of TON.
- Updated tests to ensure they reflect the new GRAM terminology and functionality.
- Introduced new constants for GRAM-related configurations.
This commit is contained in:
bohd4nx
2026-06-16 00:47:49 +03:00
parent 3481769757
commit 8f865f4911
38 changed files with 227 additions and 181 deletions
+23 -23
View File
@@ -9,7 +9,7 @@ from tonutils.clients import TonapiClient
from tonutils.contracts.jetton import get_wallet_address_get_method, get_wallet_data_get_method
from tonutils.exceptions import ProviderResponseError
from pyfragment.core.constants import MIN_TON_BALANCE, MIN_USDT_BALANCE, USDT_TON_MASTER_ADDRESS
from pyfragment.core.constants import MIN_GRAM_BALANCE, MIN_USDT_BALANCE, USDT_GRAM_MASTER_ADDRESS
from pyfragment.exceptions import WalletError
from pyfragment.models.enums import WALLET_CLASSES
from pyfragment.models.wallet import WalletInfo
@@ -22,11 +22,11 @@ logger = logging.getLogger(__name__)
async def get_usdt_balance(ton: Any, wallet_address: str) -> float:
"""Return the USDT balance for a Fragment-linked TON wallet."""
"""Return the USDT balance for a Fragment-linked GRAM (ex TON) wallet."""
try:
jetton_wallet_address = await get_wallet_address_get_method(
client=ton,
address=USDT_TON_MASTER_ADDRESS,
address=USDT_GRAM_MASTER_ADDRESS,
owner_address=wallet_address,
)
wallet_data = await get_wallet_data_get_method(client=ton, address=jetton_wallet_address)
@@ -43,40 +43,40 @@ async def get_usdt_balance(ton: Any, wallet_address: str) -> float:
raise WalletError(WalletError.USDT_BALANCE_CHECK_FAILED.format(exc=exc)) from exc
async def check_ton_payment_balance(
balance_ton: float,
amount_ton: float,
async def check_gram_payment_balance(
balance_gram: float,
amount_gram: float,
required_payment_amount: float | None,
) -> None:
"""Validate that the TON wallet can cover a TON-denominated payment."""
tx_price_ton = amount_ton
"""Validate that the GRAM (ex TON) wallet can cover a GRAM (ex TON)-denominated payment."""
tx_price_gram = amount_gram
if required_payment_amount is not None and required_payment_amount > 0:
tx_price_ton = max(tx_price_ton, required_payment_amount)
tx_price_gram = max(tx_price_gram, required_payment_amount)
required_ton = max(tx_price_ton, MIN_TON_BALANCE)
if balance_ton < required_ton:
required_gram = max(tx_price_gram, MIN_GRAM_BALANCE)
if balance_gram < required_gram:
logger.error(
"Failed TON balance check: balance=%s TON, required=%s TON",
round(balance_ton, 6),
round(required_ton, 6),
"Failed GRAM (ex TON) balance check: balance=%s GRAM (ex TON), required=%s GRAM (ex TON)",
round(balance_gram, 6),
round(required_gram, 6),
)
raise WalletError(WalletError.LOW_TON_BALANCE.format(balance=balance_ton, required=required_ton))
raise WalletError(WalletError.LOW_GRAM_BALANCE.format(balance=balance_gram, required=required_gram))
async def check_usdt_payment_balance(
balance_ton: float,
balance_gram: float,
required_payment_amount: float | None,
ton: Any,
wallet_address: str,
) -> None:
"""Validate that the wallet can cover a USDT-denominated payment."""
if balance_ton < MIN_TON_BALANCE:
if balance_gram < MIN_GRAM_BALANCE:
logger.error(
"Failed TON gas reserve check for USDT payment: balance=%s TON, required=%s TON",
round(balance_ton, 6),
MIN_TON_BALANCE,
"Failed GRAM (ex TON) gas reserve check for USDT payment: balance=%s GRAM (ex TON), required=%s GRAM (ex TON)",
round(balance_gram, 6),
MIN_GRAM_BALANCE,
)
raise WalletError(WalletError.LOW_TON_BALANCE.format(balance=balance_ton, required=MIN_TON_BALANCE))
raise WalletError(WalletError.LOW_GRAM_BALANCE.format(balance=balance_gram, required=MIN_GRAM_BALANCE))
usdt_balance = await get_usdt_balance(ton, wallet_address)
required_usdt = required_payment_amount if required_payment_amount is not None else MIN_USDT_BALANCE
@@ -109,7 +109,7 @@ async def get_account_info(client: FragmentClient) -> dict[str, Any]:
async def get_wallet_info(client: FragmentClient) -> WalletInfo:
"""Fetch the wallet address, chain state, and TON/USDT balances."""
"""Fetch the wallet address, chain state, and GRAM (ex TON)/USDT balances."""
async with TonapiClient(network=NetworkGlobalID.MAINNET, api_key=client.api_key) as ton:
try:
wallet_cls = WALLET_CLASSES[client.wallet_version]
@@ -120,7 +120,7 @@ async def get_wallet_info(client: FragmentClient) -> WalletInfo:
return WalletInfo(
address=wallet.address.to_str(is_user_friendly=True, is_bounceable=False),
state=wallet.state.value,
ton_balance=round(wallet.balance / 1_000_000_000, 4),
gram_balance=round(wallet.balance / 1_000_000_000, 4),
usdt_balance=round(usdt_balance, 4),
)
except Exception as exc:
+14 -14
View File
@@ -11,7 +11,7 @@ from ton_core import Cell, NetworkGlobalID
from tonutils.clients import TonapiClient
from tonutils.exceptions import ProviderResponseError
from pyfragment.domains.tonapi.account import check_ton_payment_balance, check_usdt_payment_balance
from pyfragment.domains.tonapi.account import check_gram_payment_balance, check_usdt_payment_balance
from pyfragment.exceptions import ParseError, TransactionError, WalletError
from pyfragment.models.enums import WALLET_CLASSES, PaymentMethod
@@ -26,7 +26,7 @@ def clean_decode(payload: str) -> str | Cell:
"""Decode a base64 BOC comment from Fragment into text when possible.
Some Fragment payloads are plain text comments, while others are structured
TON messages such as jetton transfers. Non-text payloads are returned as a
GRAM (ex TON) messages such as jetton transfers. Non-text payloads are returned as a
`Cell` so the caller can keep the raw binary structure.
"""
s = payload.strip()
@@ -39,7 +39,7 @@ def clean_decode(payload: str) -> str | Cell:
sl = cell.begin_parse()
op = sl.load_uint(32)
if op != 0:
# Non-zero op code means this is a structured TON message, not a plain text comment.
# Non-zero op code means this is a structured GRAM (ex TON) message, not a plain text comment.
return cell
try:
return sl.load_snake_string().strip()
@@ -62,7 +62,7 @@ def _extract_message(transaction_data: dict[str, Any]) -> dict[str, Any]:
async def _check_payment_balances(
wallet: Any,
payment_method: PaymentMethod,
amount_ton: float,
amount_gram: float,
required_payment_amount: float | None,
transaction_data: dict[str, Any],
ton: Any,
@@ -70,18 +70,18 @@ async def _check_payment_balances(
"""Refresh wallet and verify sufficient balance before broadcasting."""
try:
await wallet.refresh()
balance_ton = wallet.balance / 1_000_000_000
balance_gram = wallet.balance / 1_000_000_000
if payment_method == "ton":
await check_ton_payment_balance(balance_ton, amount_ton, required_payment_amount)
await check_gram_payment_balance(balance_gram, amount_gram, required_payment_amount)
else:
# USDT is paid from the Fragment-linked wallet, not the signing wallet.
fragment_wallet_address = transaction_data["transaction"].get("from", "")
await check_usdt_payment_balance(balance_ton, required_payment_amount, ton, fragment_wallet_address)
await check_usdt_payment_balance(balance_gram, required_payment_amount, ton, fragment_wallet_address)
except WalletError:
raise
except Exception as exc:
logger.exception("Failed to validate balances before broadcasting transaction")
raise WalletError(WalletError.TON_BALANCE_CHECK_FAILED.format(exc=exc)) from exc
raise WalletError(WalletError.GRAM_BALANCE_CHECK_FAILED.format(exc=exc)) from exc
async def _broadcast_with_retry(wallet: Any, message: dict[str, Any], payload: str | Cell) -> str:
@@ -90,7 +90,7 @@ async def _broadcast_with_retry(wallet: Any, message: dict[str, Any], payload: s
try:
result = await wallet.transfer(
destination=message["address"],
amount=int(message["amount"]), # nanotons, not TON
amount=int(message["amount"]), # nanograms, not GRAM (ex TON)
body=payload,
)
return str(result.normalized_hash)
@@ -124,10 +124,10 @@ async def _broadcast_with_retry(wallet: Any, message: dict[str, Any], payload: s
async def process_transaction(
client: FragmentClient,
transaction_data: dict[str, Any],
payment_method: PaymentMethod = PaymentMethod.TON,
payment_method: PaymentMethod = PaymentMethod.GRAM,
required_payment_amount: float | None = None,
) -> str:
"""Sign and broadcast a Fragment transaction with the seeded TON wallet.
"""Sign and broadcast a Fragment transaction with the seeded GRAM (ex TON) wallet.
Args:
client: Authenticated `FragmentClient` instance.
@@ -139,13 +139,13 @@ async def process_transaction(
Normalized transaction hash string.
"""
message = _extract_message(transaction_data)
amount_ton = int(message["amount"]) / 1_000_000_000
amount_gram = int(message["amount"]) / 1_000_000_000
async with TonapiClient(network=NetworkGlobalID.MAINNET, api_key=client.api_key) as ton:
wallet_cls = WALLET_CLASSES[client.wallet_version]
wallet, _, _, _ = wallet_cls.from_mnemonic(client=ton, mnemonic=client.seed)
await _check_payment_balances(wallet, payment_method, amount_ton, required_payment_amount, transaction_data, ton)
await _check_payment_balances(wallet, payment_method, amount_gram, required_payment_amount, transaction_data, ton)
payload = clean_decode(str(message.get("payload", "")))
@@ -161,7 +161,7 @@ async def process_transaction(
raise TransactionError(TransactionError.BROADCAST_FAILED_SSL.format(exc=exc)) from exc
cause = cause.__cause__ or cause.__context__
logger.exception(
"Failed to broadcast transaction to '%s' for %s nanotons using payment method '%s'",
"Failed to broadcast transaction to '%s' for %s nanograms using payment method '%s'",
message["address"],
message["amount"],
payment_method,