from __future__ import annotations import base64 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 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: return 0.0 raise WalletError(WalletError.USDT_BALANCE_CHECK_FAILED.format(exc=exc)) from exc except Exception as exc: 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: 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: 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: 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: 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: raise WalletError(WalletError.WALLET_INFO_FAILED.format(exc=exc)) from exc