mirror of
https://github.com/bohd4nx/FragmentAPI.git
synced 2026-07-25 06:14:29 +00:00
128 lines
5.5 KiB
Python
128 lines
5.5 KiB
Python
from __future__ import annotations
|
|
|
|
import base64
|
|
import logging
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
from ton_core import NetworkGlobalID
|
|
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, WALLET_CLASSES
|
|
from pyfragment.exceptions import WalletError
|
|
from pyfragment.models.wallet import WalletInfo
|
|
|
|
if TYPE_CHECKING:
|
|
from pyfragment.client import FragmentClient
|
|
|
|
|
|
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."""
|
|
try:
|
|
jetton_wallet_address = await get_wallet_address_get_method(
|
|
client=ton,
|
|
address=USDT_TON_MASTER_ADDRESS,
|
|
owner_address=wallet_address,
|
|
)
|
|
wallet_data = await get_wallet_data_get_method(client=ton, address=jetton_wallet_address)
|
|
raw_balance = int(wallet_data[0]) if wallet_data else 0
|
|
return float(raw_balance) / 1_000_000.0
|
|
except ProviderResponseError as exc:
|
|
if exc.code == 404:
|
|
logger.debug("No USDT jetton wallet found for '%s'; treating balance as 0", wallet_address)
|
|
return 0.0
|
|
logger.error("Failed to load USDT balance for wallet '%s': %s", wallet_address, exc, exc_info=True)
|
|
raise WalletError(WalletError.USDT_BALANCE_CHECK_FAILED.format(exc=exc)) from exc
|
|
except Exception as exc:
|
|
logger.exception("Failed to load USDT balance for wallet '%s' due to an unexpected error", wallet_address)
|
|
raise WalletError(WalletError.USDT_BALANCE_CHECK_FAILED.format(exc=exc)) from exc
|
|
|
|
|
|
async def check_ton_payment_balance(
|
|
balance_ton: float,
|
|
amount_ton: float,
|
|
required_payment_amount: float | None,
|
|
) -> None:
|
|
"""Validate that the TON wallet can cover a TON-denominated payment."""
|
|
tx_price_ton = amount_ton
|
|
if required_payment_amount is not None and required_payment_amount > 0:
|
|
tx_price_ton = max(tx_price_ton, required_payment_amount)
|
|
|
|
required_ton = max(tx_price_ton, MIN_TON_BALANCE)
|
|
if balance_ton < required_ton:
|
|
logger.error(
|
|
"Failed TON balance check: balance=%s TON, required=%s TON",
|
|
round(balance_ton, 6),
|
|
round(required_ton, 6),
|
|
)
|
|
raise WalletError(WalletError.LOW_TON_BALANCE.format(balance=balance_ton, required=required_ton))
|
|
|
|
|
|
async def check_usdt_payment_balance(
|
|
balance_ton: 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:
|
|
logger.error(
|
|
"Failed TON gas reserve check for USDT payment: balance=%s TON, required=%s TON",
|
|
round(balance_ton, 6),
|
|
MIN_TON_BALANCE,
|
|
)
|
|
raise WalletError(WalletError.LOW_TON_BALANCE.format(balance=balance_ton, required=MIN_TON_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
|
|
if usdt_balance < required_usdt:
|
|
logger.error(
|
|
"Failed USDT balance check for wallet '%s': balance=%s USDT, required=%s USDT",
|
|
wallet_address,
|
|
round(usdt_balance, 6),
|
|
round(required_usdt, 6),
|
|
)
|
|
raise WalletError(WalletError.LOW_USDT_BALANCE.format(balance=usdt_balance, required=required_usdt))
|
|
|
|
|
|
async def get_account_info(client: FragmentClient) -> dict[str, Any]:
|
|
"""Build the wallet payload Fragment needs to prepare a transaction."""
|
|
async with TonapiClient(network=NetworkGlobalID.MAINNET, api_key=client.api_key) as ton:
|
|
try:
|
|
wallet_cls = WALLET_CLASSES[client.wallet_version]
|
|
wallet, pub_key, _, _ = wallet_cls.from_mnemonic(client=ton, mnemonic=client.seed)
|
|
boc = wallet.state_init.serialize().to_boc()
|
|
return {
|
|
"address": wallet.address.to_str(False, False),
|
|
"publicKey": pub_key.as_hex,
|
|
"chain": "-239",
|
|
"walletStateInit": base64.b64encode(boc).decode(),
|
|
}
|
|
except Exception as exc:
|
|
logger.exception("Failed to build Fragment account info from the configured wallet")
|
|
raise WalletError(WalletError.ACCOUNT_INFO_FAILED.format(exc=exc)) from exc
|
|
|
|
|
|
async def get_wallet_info(client: FragmentClient) -> WalletInfo:
|
|
"""Fetch the wallet address, chain state, and TON/USDT balances."""
|
|
async with TonapiClient(network=NetworkGlobalID.MAINNET, api_key=client.api_key) as ton:
|
|
try:
|
|
wallet_cls = WALLET_CLASSES[client.wallet_version]
|
|
wallet, _, _, _ = wallet_cls.from_mnemonic(client=ton, mnemonic=client.seed)
|
|
await wallet.refresh()
|
|
wallet_address = wallet.address.to_str(False, False)
|
|
usdt_balance = await get_usdt_balance(ton, wallet_address)
|
|
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),
|
|
usdt_balance=round(usdt_balance, 4),
|
|
)
|
|
except Exception as exc:
|
|
logger.exception("Failed to fetch wallet info from Tonapi")
|
|
raise WalletError(WalletError.WALLET_INFO_FAILED.format(exc=exc)) from exc
|