Загрузка...

TON + Python на примере автоматизации покупок на Fragment

Тема в разделе Статьи создана пользователем Апатия 5 июн 2025 в 19:58. 322 просмотра

Загрузка...
  1. Апатия
    Апатия Автор темы 5 июн 2025 в 19:58 444 971 15 июн 2021
    Доброго времени суток! :sueta:


    Сегодня хочу немного рассказать о том как работать с блокчейном TON при помоще Python, а так же разберем, как работает Fragment и купим звездочек при помощи скрипта.

    Код целиком и без пояснений внизу статьи
    А еще под конец статьи по какой то неведомой мне причине ломаются код блоки, возможно лимит, уж извиняйте



    Итого: у нас есть 2 проблемы:
    Первая, это нужно разобраться как нам работать с транзакциями.
    Вторая, как нам работать с фрагментом.

    Итак, начать очевидно стоит с создания кошелька и его инициализации при помощи вызова контракта. В целом этот шаг можно спокойно пропустить, и создать кошелек в том же тонкипере, однако учтите, что он должен быть обязательно инициализирован. Я же хочу это сделать с помощью кода.

    Создание кошелька

    Для создания кошелька мы будем использовать библиотеку
    ⁡tonsdk
    ⁡ , а вернее нам нужно всего 2 метода из нее, это
    ⁡Wallets
    и
    ⁡WalletVersionEnum

    Устанавливаем:
    ⁡pip install tonsdk

    Так же сразу начнем выстраивать небольшую структуру, чтобы не лепить все в кучу, условно для работы с кошельком у нас будет файл
    ⁡WalletUtils.py
    ⁡ В нем мы создадим одноименный класс, в котором у нас пока будут сразу две функции,
    ⁡create_wallet
    ⁡ будет отвечать за создание и сохранения нашего кошелька в файл,
    ⁡_return_wallet_data
    ⁡ же будет вспомогательная для структуризации данных этого кошелька.


    Python
    from json import dumps
    from pathlib import Path

    from tonsdk.contract.wallet import Wallets, WalletVersionEnum

    # Класс с для работы с кошельком
    class WalletUtils:
    @staticmethod
    def _return_wallet_data(mnemonics, pub_k, priv_k, wallet_address):
    """
    Формирует словарь с данными созданного кошелька.
    """
    return {
    "wallet_address": wallet_address.address.to_string(True, True, True),
    "mnemonics": mnemonics,
    "public_key": pub_k.hex(),
    "private_key": priv_k.hex(),
    "wallet_address_testnet": wallet_address.address.to_string(True, True, True, True)
    }

    def create_wallet(self, version="v4r2", save_to_file=False, save_dir="created_wallets/wallets_data.txt"):
    """
    Создаёт новый кошелёк TON заданной версии. По умолчанию используется версия v4r2.

    :param version: Версия кошелька (например, "v3r2", "v4r2").
    :param save_to_file: Сохранять ли данные в файл.
    :param save_dir: Путь до файла, в который будут сохранены данные.
    :return: Кортеж (данные_кошелька, объект_кошелька)
    """
    # Создание кошелька с заданной версией
    mnemonics, pub_k, priv_k, wallet_address = Wallets.create(
    version=getattr(WalletVersionEnum, version),
    workchain=0
    )

    # Формирование словаря с данными
    wallet_data = self._return_wallet_data(mnemonics, pub_k, priv_k, wallet_address)

    # Если указано — сохраняем в файл
    if save_to_file:
    Path(save_dir).parent.mkdir(parents=True, exist_ok=True)
    with open(save_dir, "a+", encoding="utf-8") as f:
    f.write(dumps(wallet_data, indent=4, ensure_ascii=False) + "\n\n")

    return wallet_data, wallet_address
    Вызваный нами метод
    ⁡Wallets.create
    ⁡ принимает версию и воркчейн кошелька который мы хотим создать, я выбираю версию v4r2 так как она банально современнее, но сильной разницы в том какую вы выбирите в целом нет, мы можем заглянуть в этот метод и увидеть какие версии поддерживаются.

    [IMG]
    Воркчейн, это логическая часть блокчейна, 0 - Основаня клиентская часть, мы будем использовать только ее, с этим можно даже не заморачиваться.

    Сам же метод целиком нам возвращает мнемотику, приватный и публичный ключи, а так же адрес нашего новоиспеченного кошелька, мы это записываем себе.

    А зачем множественное True у наших
    ⁡wallet_address
    ⁡ и
    ⁡wallet_address_testnet
    ⁡ ?

    Если кратко, то так надо. Это самый распространённый вариант — его и стоит использовать по умолчанию.
    Это параметры
    ⁡user_friendly=True url_safe=True bounceable=True
    ⁡ ,а четвертый в
    ⁡wallet_address_testnet
    ⁡ , это
    ⁡is_test_only=True
    ⁡ так мы получим адрес от тестнета, он нужен чтобы тестить наш код без траты реальных средств, покажу чуть позже как это работает.

    Грубо говоря эти 3 тру означают "Верни мне адрес в удобном виде, bounceable, и подходящий для URL."
    user_friendly отвечает за вывод адреса в читаемом виде, а не в хексе, bounceable означает, что адрес должен поддерживать bounceable сообщения и url_safe что мы хотим видеть адрес в безопасном base64 алфавите, без знаков "+" "-" и прочих
    Давайте запустим и посмотрим что вышло, создадим файл
    ⁡main.py
    ⁡ и импортируем туда наш класс, а после вызовем создание кошелька и сразу же его сохраним в файл


    Python
    from wallet.WalletUtils import WalletUtils # Импорт нашего класса

    if __name__ == '__main__':
    WalletUtils = WalletUtils()
    print(WalletUtils.create_wallet(save_to_file=True))
    [IMG]
    Отлично! Мы только что создали кошелек, хотя подождите, ведь блокчейн даже не знает о его существовании!
    Инициализация кошелька

    Для его записи в блокчейн и дальнейшей работы с ним, нам требуется его инициализировать, для этого мы для нашего класса
    ⁡WalletUtils
    ⁡ создадим еще 2 метода,
    ⁡wallet_from_mnemonics
    ⁡ который будет использоватся для получения данных о кошельке из сид фразы, он не обязателен, так как мы уже получили все данные о кошельке и записали их в файл, но очень удобен если у нас сохранена лишь сид фраза нашего кошелька.
    ⁡_init_wallet_async
    ⁡ будет у нас ассинхронной функцией для взаимодействия с контрактом инициализации, ассинхронность нужна для того, чтобы наш код не зависал во время ожидания ответа от сервера, ну и третяя это функция заглушка
    ⁡init_wallet
    ⁡ через которую мы будем запускать нашу ассинхронную функцию.

    Для взаимодействия с блокчейном, воспользуемся библиотекой
    ⁡TonTools
    ⁡ Устанавливаем
    ⁡pip install tontools


    Пишем код. Так же добавим логгинг для красоты происходящего

    Python

    import asyncio
    import logging
    from tonsdk.contract.wallet import Wallets, WalletVersionEnum
    from tonsdk.utils import bytes_to_b64str
    from TonTools import TonCenterClient # Клиент для взаимодействия с блокчейном TON

    class WalletUtils:

    def wallet_from_mnemonics(self, mnemonics: list, version="v4r2"):
    # Восстанавливаем кошелёк из мнемоники
    mnemonics, pub_k, priv_k, wallet_address = Wallets.from_mnemonics(
    mnemonics=mnemonics,
    version=getattr(WalletVersionEnum, version),
    workchain=0
    )

    # Формируем словарь с адресами и ключами
    wallet_data = self._return_wallet_data(mnemonics, pub_k, priv_k, wallet_address)

    # Возвращаем и данные, и сам объект кошелька
    return wallet_data, wallet_address

    def init_wallet(self, mnemonics, testnet=False):
    # Синхронно запускаем асинхронную инициализацию (удобно для внешнего кода)
    result = asyncio.run(self._init_wallet_async(mnemonics, testnet))
    return result

    async def _init_wallet_async(self, mnemonics, testnet=False):
    # Получаем данные и объект кошелька
    wallet_data, wallet_address = self.wallet_from_mnemonics(mnemonics=mnemonics)

    # Инициализируем TonCenter-клиент для отправки сообщения в сеть
    provider = TonCenterClient(testnet=testnet)

    # Создаём deploy-сообщение для регистрации кошелька в блокчейне
    query = wallet_address.create_init_external_message()

    # Переводим сообщение в base64 — формат, нужный API TonCenter
    deploy_message = bytes_to_b64str(query['message'].to_boc(False))

    # Отправляем сообщение.
    if await provider.send_boc(deploy_message) != 200:
    logging.error('Init error or wallet already init')
    else:
    logging.info("Wallet inited!")
    Python
    import asyncio
    import logging

    from tonsdk.contract.wallet import Wallets, WalletVersionEnum
    from json import dumps
    from pathlib import Path
    from TonTools import TonCenterClient

    from tonsdk.utils import bytes_to_b64str


    class WalletUtils:
    @staticmethod
    def _return_wallet_data(mnemonics, pub_k, priv_k, wallet_address):
    return {
    "wallet_address": wallet_address.address.to_string(True, True, True),
    "mnemonics": mnemonics,
    "public_key": pub_k.hex(),
    "private_key": priv_k.hex(),
    "wallet_address_testnet": wallet_address.address.to_string(True, True, True, True)
    }

    def create_wallet(self, version="v4r2", save_to_file=False, save_dir="created_wallets/wallets_data.txt"):
    mnemonics, pub_k, priv_k, wallet_address = Wallets.create(version=getattr(WalletVersionEnum, version),
    workchain=0)

    wallet_data = self._return_wallet_data(mnemonics, pub_k, priv_k, wallet_address)

    if save_to_file:
    Path(save_dir).parent.mkdir(parents=True, exist_ok=True)
    with open(save_dir, "a+", encoding="utf-8") as f:
    f.write(dumps(wallet_data, indent=4, ensure_ascii=False) + "\n\n")

    return wallet_data, wallet_address

    def wallet_from_mnemonics(self, mnemonics: list, version="v4r2"):
    mnemonics, pub_k, priv_k, wallet_address = Wallets.from_mnemonics(mnemonics=mnemonics,
    version=getattr(WalletVersionEnum, version),
    workchain=0)

    wallet_data = self._return_wallet_data(mnemonics, pub_k, priv_k, wallet_address)

    return wallet_data, wallet_address

    def init_wallet(self, mnemonics, testnet=False):
    result = asyncio.run(self._init_wallet_async(mnemonics, testnet))
    return result

    async def _init_wallet_async(self, mnemonics, testnet=False):
    wallet_data, wallet_address = self.wallet_from_mnemonics(mnemonics=mnemonics)
    provider = TonCenterClient(testnet=testnet)
    query = wallet_address.create_init_external_message()
    deploy_message = bytes_to_b64str(query['message'].to_boc(False))

    if await provider.send_boc(deploy_message) != 200:
    logging.error('Init error or wallet already init')
    else:
    logging.info("Wallet inited!")

    Пробуем!

    Python
    import json
    from wallet.WalletUtils import WalletUtils # Импорт нашего класса


    if __name__ == '__main__':
    WalletUtils = WalletUtils()
    # print(WalletUtils.create_wallet(save_to_file=False))

    # Загружаем нашу мнемотику
    with open("created_wallets/wallets_data.txt", "r") as f:
    mnemonics = json.load(f)['mnemonics']

    WalletUtils.init_wallet(mnemonics=mnemonics, testnet=True)
    [IMG]

    Кажется что то пошло не так. А все потому что нам для инициализации обязательно нужен баланс, стоимость процедуры составляет 0.005 TON1,29 рублей
    Так как я работаю с тестовой сетью я просто запрошу 2 тестовых TON через бота Telegram: TESTGIVER_TON_BOT
    Отправляю ему свой testnet адрес кошелька и спустя минуту получаю свои 2 ton, увидеть это можно на сканере testnet.tonviewer.com если вы делаете это в основной сети, то свой кошелек можно смотреть здесь tonviewer.com
    Как можно заметить на кошельке стоит пометка о том, что кошелек еще не инициализирован.

    [IMG]
    После того как получили баланс, запускаем код еще разок.
    [IMG]
    Успех! Смотрим что на сканере.
    [IMG]
    Как видим наш клшелек теперь активен.

    Отправка транзакции

    Отлично, теперь давайте попробуем отправить куда-нибудь транзакцию, например создадим еще кошелек через наш метод
    ⁡WalletUtils.create_wallet
    ⁡ инициализировать его не обязательно, нам просто нужно получить адрес для отправки.

    Теперь напишем код для отправки средств. Для отправки средств продолжим использовать TonTools. Так же не забываем о структуре, создадим файл
    ⁡Transactions.py
    ⁡ опять же с одноименным классом и двумя методами
    ⁡_send_ton_async
    ⁡ для отправки средств и классическая заглушка в виде
    ⁡send_ton
    Python
    import logging
    from TonTools import TonCenterClient, Wallet
    import asyncio
    from tonsdk.utils import from_nano


    class Transactions:
    @staticmethod
    async def _send_ton_async(mnemonics, destination_address, amount, payload, nano_amount, version, testnet, send_mode):
    # Инициализируем TonCenter-клиент (для testnet или mainnet)
    provider = TonCenterClient(testnet=testnet)

    # Импортируем кошелёк из мнемоники
    wallet = Wallet(mnemonics=mnemonics, version=version, provider=provider)

    # Если передаётся сумма в нанотонах — конвертируем в TON
    if nano_amount:
    amount = from_nano(amount, "ton")

    # Удаляем переносы строк из payload'а для читаемости лога
    clean_payload = payload.replace("\n", " ")

    # Выводим предупреждение с параметрами транзакции
    logging.warning(f'Sending {amount} TON to {destination_address} with payload: {clean_payload}')

    # Отправляем средства через метод transfer_ton
    if await wallet.transfer_ton(destination_address=destination_address, amount=amount,
    message=payload, send_mode=send_mode) != 200:
    logging.error("Transaction failed!") # Ошибка при отправке
    return 0
    else:
    logging.info("Sending successful!") # Успешная отправка
    return 1

    def send_ton(self, mnemonics, destination_address, amount, payload, nano_amount=True, version='v4r2', testnet=False, send_mode=0):
    # Запускаем асинхронную отправку синхронно через asyncio.run()
    result = asyncio.run(self._send_ton_async(
    mnemonics, destination_address, amount, payload, nano_amount, version, testnet, send_mode
    ))
    return result
    Из новых и неизвестных терминов здесь имеются
    ⁡payload, nano_amount, send_mode

    С payload все просто, грубо говоря это комментарий в транзакции или же memo tag, как любят говорить биржи, служит либо для идентификации платежей той же биржей либо для простой передачи информации вместе с транзакцией.

    nano_amount в контексте нашей функции это флаг, который если мы ставим на True мы даем функции понять, что мы передаем сумму транзакции в так называемых нанотонах, грубо говоря копейках. Так как метод
    ⁡transfer_ton
    из
    ⁡TonTools
    ⁡ ожидает, что мы передадим ему сумму в нормальном виде мы страхуемся и в случае чего переводим нанотоны в обычные тоны, для этого есть метод
    ⁡from_nano
    ⁡ из уже известной нам библиотеки
    ⁡tonsdk


    И последнее, но не по значению это send_mode, очень важная часть транзакции, отвечает за то, в каком виде будет отправлена транзакция, всего есть 2 основных значения, это 0 и 1 , по умолчанию у нас стоит 0, и это означает, что комиссия за транзакцию будет вычитаться из введенной нами суммы, покажу на примере, мы отправляем 1 TON и указываем send_mode 0, допустим комиссия при отправке составила 0.007 TON итого получатель получит
    ⁡1 - 0.007 = 0.993 TON
    ⁡ , но если же мы к нашему send_mode прибавим 1 и отправим ту же транзакцию, то комиссия будет покрыта отдельно с нашего кошелька, а получатель получит ровно ту сумму которую мы указали, то есть 1 TON. Почему это важно? Те кто хоть раз оплачивал при помощи криптовалюты знает, что сервисы требуют отправки точной суммы, но ведь мы не можем заранее знать какая комиссия, чтобы заложить ее в сумму транзакции, для этого мы используем сенд мод 1, чтобы получатель получил ровно ту сумму что мы отправили.

    [IMG]
    Давайте попробуем отправить транзакцию в 0.1 TON с каким-нибудь сообщением.
    Python
    import json
    from wallet.Transactions import Transactions
    # from wallet.WalletUtils import WalletUtils


    if __name__ == '__main__':
    # WalletUtils = WalletUtils()
    # print(WalletUtils.create_wallet(save_to_file=False))
    Transactions = Transactions()

    with open("created_wallets/wallets_data.txt", "r") as f:
    mnemonics = json.load(f)['mnemonics']

    # Используем send_mode 1 и указываем сумму НЕ в нанотонах, транзакция в тестнете
    Transactions.send_ton(mnemonics, "kQDMyvHYZQtwW-AdzWDPviTPa2QQd6O6rYXA6UP8eTPJ5Goh", 0.1, "Hello Lolz",
    nano_amount=False, send_mode=1, testnet=True)
    [IMG]
    [IMG]
    Ура! Получили ровно ту сумму которую указали, так как указали send_mode=1 все работает!

    Это был краткий экскурс в отправку TON, теперь давайте попробуем воспользоваться тем что написали и купить себе звездочек на Fragment.

    Разбираем как работает Fragment


    Итак, наша вторая проблема, это понять как работает Fragmnet, так как официального api у него нет.
    Начнем свое исследование с того, что авторизуемся на фрагмент и подключим наш кошелек через TonKeeper, в который я импортировал созданную нами сид фразу.
    Для начала давайте откроем страницу покупки звездочек и посмотрим что там происходит.
    [IMG]
    Тут нас просят ввести юзернейм и сумму, хорошо, давайте откроем вкладку Network в dev tools и попробуем ввести эти данные, например возьмем телеграм нашего многоуважаемого арбитра BDSM

    При вводе username видим запрос, давайте разбираться!
    Заглянем в ответ от сервера на наш запрос.

    [IMG]
    Из полезной информации хочется сразу выделить "recipient" это id пользователя который использует Fragment и "name" его можем использовать для вывода юзер френдли информации, запоминаем эти 2 параметра.
    Теперь заглянем в полезную нагрузку запроса, чтобы понять как нам его повторить.

    [IMG]
    Сразу здесь видим введенный нами Username, в данном случае это "ANSWERXYZ" и метод "searchStarsRecipient" а так же ужастный строчный параметр в виде hash. Можно подумать "Ужас, что это и откуда, как нам его передавать"
    Давайте разбираться, подобные хэши могут генерироваться js файлами при загрузке страницы, либо хранится прямиком на html страницы, давайте с нее и начнем, просто берем наш хэш и вставляем в поиск по странице

    [IMG]
    Нам повезло! То есть мы можем получить хэш просто отправив любой запрос на страницу. Давайте сразу попробуем воспроизвести в скрипте все что мы сейчас сделали.
    Создаем файл
    ⁡PaymentGet.py
    ⁡ с таким же классом, тут мы будем получать всю информацию для совершения покупки. Для начала напишем функции
    ⁡get_data_for_payment
    ⁡ это будет наша основная функция класса и вспомогательные
    ⁡_hash_get
    ⁡ где будем получать хэш и
    ⁡_update_url
    ⁡ с помощью которой будем обновлять нашу ссылку, добавляя в нее хэщ. Так же в обязательном порядке нам нужно вытащить куки, воспользуемся куки эдитором.

    [IMG]
    Как видим тут всего 4 параметра, давайте запишем их в файл
    ⁡cookies.json
    ⁡ в формате json.

    [IMG]
    Итак продолжим написание кода.
    Python
    import requests
    from re import search
    import logging
    import json


    class PaymentGet:
    def __init__(self):
    # Загружаем cookies из файла cookies.json
    with open('cookies.json', 'r') as file:
    loaded_cookies = json.load(file)

    self.cookies = loaded_cookies

    # Заголовки, имитирующие обычный браузерный запрос
    self.headers = {
    "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
    "Referer": "https://fragment.com/stars/buy", # Указывает, откуда был произведён запрос
    "X-Requested-With": "XMLHttpRequest", # Указывает, что это AJAX-запрос
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 OPR/117.0.0.0 (Edition Yx GX)"
    }

    def _hash_get(self):
    # Получаем HTML-страницу покупки звёздочек и вытаскиваем hash из URL внутри неё
    response = requests.get("https://fragment.com/stars/buy", cookies=self.cookies)
    if response.status_code == 200:
    return search(r'api\?hash=([a-zA-Z0-9]+)', response.text).group(1)

    def _update_url(self):
    # Обновлённый URL с нужным hash для дальнейших запросов к API
    return f"https://fragment.com/api?hash={self._hash_get()}"

    def get_data_for_payment(self, recipient, quantity):
    # Логируем попытку отправки звёздочек пользователю
    logging.warning(f"Sending {quantity} stars to @{recipient}...")

    # Получаем рабочий URL API
    url = self._update_url()

    # Отправляем POST-запрос для поиска получателя по юзернейму
    recipient_id_dirt = requests.post(
    url,
    headers=self.headers,
    cookies=self.cookies,
    data=f"query={recipient}&quantity=&method=searchStarsRecipient"
    )

    # Парсим JSON-ответ и извлекаем ID и имя получателя
    recipient_id = recipient_id_dirt.json().get("found", {}).get("recipient", "")
    name = recipient_id_dirt.json().get("found", {}).get("name", "")

    # Выводим результат
    print(f"Name: {name}\nRecipient id: {recipient_id}")
    Python
    from FragmentApi.PaymentGet import PaymentGet

    if __name__ == '__main__':
    PaymentGet = PaymentGet()
    PaymentGet.get_data_for_payment("ANSWERXYZ", 50)
    [IMG]
    Отлично! Мы сымитировали запрос сделаный нами через браузер чуть ранее.

    Теперь давайте вернемся в браузер и нажмем кнопку купить, чтобы понять что нам делать дальше.

    У нас открылось окно подтверждения покупки и отправился запрос, давайте разбираться
    [IMG]
    [IMG]
    Итак здсь мы видим id получателя который мы успешно записали ранее, количество звезд в данном случае 50 и метод, который нам нужно использовать для отправки этого запроса. Теперь взглянем, что нам это дает
    [IMG]
    Итак здесь нам интересен только
    ⁡req_id
    ⁡ так как это очевидно id нашего запроса на покупку, остальные параметры выглядят мусорными. Следующий этап, жмем подтверждение покупки.


    Опа! Новый запрос. Смотрим.
    [IMG]
    Глаза разбегаются. Смотрим, device в целом понятный параметр, он не сильно важен, но на всякий случай запишем, transaction, show_sender и метод с этим тоже все понятно, далее у нас id, это тот id транзакции который мы получали в прошлом шаге и самое страшное это account, в нем много параметров, address это адрес нашего кошелька в hex, выглядит не супер полезно, пропускам, далее идет chain, это айди сети TON он в целом всегда постоянный, давайте его укажем, далее walletStateInit выглядит как абсолютный мусор, пропускаем, ну и последнее это приватный ключ нашего кошелька, возможно важный параметр, давайте его передадим, получить его очень легко, просто вызовем наш уже знакомый метод
    ⁡WalletUtils.wallet_from_mnemonics
    ⁡ и возьмем его оттуда.


    С запросом разобрались, теперь давайте взглянем, что мы получаем взамен.

    [IMG]
    То что нужно! Здесь мы видим адрес на который нужно отправить TON для завершения покупки, так же сумму указанную в нанотонах и зашифрованный memo tag, расшифруем его с помощью модуля
    ⁡base64
    ⁡ и его метода
    ⁡b64decode


    Давайте дополним нашу функцию
    ⁡get_data_for_payment
    ⁡ а так же добавим два новых метода,
    ⁡_payload_get
    ⁡ для составления полезной нагрузки для последнего запроса и
    ⁡_message_decode
    ⁡ для расшифровки memo tag.


    Python
    import requests
    from re import search
    import logging
    from wallet.WalletUtils import WalletUtils
    from urllib.parse import urlencode
    import json
    import base64


    class PaymentGet:
    def __init__(self):
    # Инициализация вспомогательного класса для работы с кошельками
    self.WalletUtils = WalletUtils()

    # Загрузка cookies из файла для авторизации на Fragment
    with open('cookies.json', 'r') as file:
    loaded_cookies = json.load(file)

    self.cookies = loaded_cookies

    # Заголовки, имитирующие поведение браузера
    self.headers = {
    "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
    "Referer": "https://fragment.com/stars/buy",
    "X-Requested-With": "XMLHttpRequest",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 OPR/117.0.0.0 (Edition Yx GX)"
    }

    def _hash_get(self):
    # Получение хэша из страницы fragment.com, используется для доступа к API
    response = requests.get("https://fragment.com/stars/buy", cookies=self.cookies)
    if response.status_code == 200:
    return search(r'api\?hash=([a-zA-Z0-9]+)', response.text).group(1)

    def _update_url(self):
    # Возвращает URL с актуальным hash-параметром
    return f"https://fragment.com/api?hash={self._hash_get()}"

    def _payload_get(self, req_id, mnemonics):
    # Формирует тело запроса для получения данных транзакции
    payload = {
    "account": json.dumps({
    "chain": "-239", # -239 = testnet, если бы был 0 — mainnet
    "publicKey": self.WalletUtils.wallet_from_mnemonics(mnemonics)[0]["public_key"]
    }),
    "device": json.dumps({
    "platform": "web",
    "appName": "telegram-wallet", # Имитируем Telegram Wallet
    "appVersion": "1",
    "maxProtocolVersion": 2,
    "features": ["SendTransaction", {"name": "SendTransaction", "maxMessages": 4}]
    }),
    "transaction": 1,
    "id": req_id,
    "show_sender": 0,
    "method": "getBuyStarsLink"
    }
    return urlencode(payload)

    @staticmethod
    def _message_decode(encoded_payload):
    # Декодирует base64-пейлоад из ответа, чтобы получить читаемую строку
    padding_needed = len(encoded_payload) % 4
    if padding_needed != 0:
    encoded_payload += '=' * (4 - padding_needed)
    decoded_payload = base64.b64decode(encoded_payload)
    text_part = decoded_payload.split(b"\x00")[-1].decode("utf-8")
    return text_part

    def get_data_for_payment(self, recipient, quantity, mnemonics):
    # Главная функция: получает адрес, сумму и полезную нагрузку для оплаты звёздочек

    logging.warning(f"Sending {quantity} stars to @{recipient}...")

    url = self._update_url()

    # Получаем ID пользователя по юзернейму
    recipient_id_dirt = requests.post(url, headers=self.headers, cookies=self.cookies,
    data=f"query={recipient}&quantity=&method=searchStarsRecipient")
    recipient_id = recipient_id_dirt.json().get("found", {}).get("recipient", "")

    # Инициализируем покупку и получаем req_id
    req_id_dirt = requests.post(url, headers=self.headers, cookies=self.cookies,
    data=f"recipient={recipient_id}&quantity={quantity}&method=initBuyStarsRequest")
    req_id = req_id_dirt.json().get("req_id", "")

    # Формируем запрос на получение данных транзакции (TON)
    encoded_payload = self._payload_get(req_id, mnemonics)

    # Получаем из Fragment данные для отправки TON: кому, сколько, и какой payload
    buy_payload_dirt = requests.post(url, headers=self.headers, cookies=self.cookies, data=encoded_payload)
    buy_payload = buy_payload_dirt.json()["transaction"]["messages"][0]

    address, amount, encoded_message = buy_payload["address"], buy_payload["amount"], buy_payload["payload"]
    payload = self._message_decode(encoded_message)

    logging.info("Payment data received!")
    logging.warning("Waiting to send transaction...")

    return address, amount, payload
    По всей видимости мы готовы испытать эту машину! Но давайте объеденим получение информации о платеже и отправки транзакции. Создадим файл
    ⁡BuyStars.py

    [CODE=python]from FragmentApi.PaymentGet import PaymentGet
    from wallet.Transactions import Transactions


    def buy_stars(recipient, amount, mnemonics, version="v4r2", testnet=False, send_mode=1):

    # Инициализация классов для получения данных оплаты и отправки транзакции
    payment = PaymentGet()
    transactions = Transactions()

    # Получаем адрес получателя, сумму и payload для перевода TON в обмен на звёзды
    payment_address, payment_amount, payload = payment.get_data_for_payment(
    recipient=recipient,
    quantity=amount,
    mnemonics=mnemonics
    )

    # Отправляем TON с указанными параметрами (адрес, сумма, payload и др.)
    transactions.send_ton(
    mnemonics=mnemonics,
    destination_address=payment_address,
    amount=payment_amount,
    nano_amount=True, # указываем, что сумма в нанотонах
    payload=payload,
    version=version,
    testnet=testnet,
    send_mode=send_mode
    )[/CODE]
    Готово! Осталось передать в функцию
    ⁡buy_stars
    ⁡ юзернейм получателя, количество звездочек и сид фразу нашего кошелька.
    Но перед этим убедитесь, что заполнены cookies, кошелек создан и инициализирован, давайте отправим на наш кошелек немного TON и купим 50 звездочек для проверки.


    [CODE=python]import json
    from FragmentApi.BuyStars import buy_stars

    if __name__ == '__main__':
    with open("created_wallets/wallets_data.txt", "r") as f:
    mnemonics = json.load(f)['mnemonics']

    recipient = input("Recipient: ")
    amount = input("Amount: ")
    buy_stars(recipient, int(amount), mnemonics, send_mode=1, testnet=False)[/CODE]
    [IMG]
    [IMG]

    Победа! Все работает замечательно. Теперь вы настоящий крутой хакер.

    Project/
    ├── created_wallets/
    │ └── wallets_data.txt
    ├── FragmentApi/
    │ ├── BuyStars.py
    │ └── PaymentGet.py
    ├── wallet/
    │ ├── Transactions.py
    │ └── WalletUtils.py
    ├── cookies.json
    └──main.py
    [CODE=python]from FragmentApi.PaymentGet import PaymentGet
    from wallet.Transactions import Transactions


    def buy_stars(recipient, amount, mnemonics, version="v4r2", testnet=False, send_mode=1):
    payment = PaymentGet()
    transactions = Transactions()
    payment_address, payment_amount, payload = payment.get_data_for_payment(recipient=recipient, quantity=amount,
    mnemonics=mnemonics)

    transactions.send_ton(mnemonics=mnemonics, destination_address=payment_address, amount=payment_amount,
    nano_amount=True, payload=payload, version=version, testnet=testnet, send_mode=send_mode)[/CODE]
    [CODE=python]import requests
    from re import search
    import logging
    from wallet.WalletUtils import WalletUtils
    from urllib.parse import urlencode
    import json
    import base64


    class PaymentGet:
    def __init__(self):
    self.WalletUtils = WalletUtils()
    with open('cookies.json', 'r') as file:
    loaded_cookies = json.load(file)

    self.cookies = loaded_cookies
    self.headers = {
    "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
    "Referer": "[UNFURL]https://fragment.com/stars/buy"[/UNFURL],
    "X-Requested-With": "XMLHttpRequest",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 OPR/117.0.0.0 (Edition Yx GX)"
    }

    def _hash_get(self):
    response = requests.get("[UNFURL]https://fragment.com/stars/buy"[/UNFURL], cookies=self.cookies)
    if response.status_code == 200:
    return search(r'api\?hash=([a-zA-Z0-9]+)', response.text).group(1)

    def _update_url(self):
    return f"[UNFURL]https://fragment.com/api?hash={self._hash_get()}"[/UNFURL]

    def _payload_get(self, req_id, mnemonics):
    payload = {
    "account": json.dumps({
    "chain": "-239",
    "publicKey": self.WalletUtils.wallet_from_mnemonics(mnemonics)[0]["public_key"]
    }),
    "device": json.dumps({
    "platform": "web",
    "appName": "telegram-wallet",
    "appVersion": "1",
    "maxProtocolVersion": 2,
    "features": ["SendTransaction", {"name": "SendTransaction", "maxMessages": 4}]
    }),
    "transaction": 1,
    "id": req_id,
    "show_sender": 0,
    "method": "getBuyStarsLink"
    }
    return urlencode(payload)

    @staticmethod
    def _message_decode(encoded_payload):
    padding_needed = len(encoded_payload) % 4
    if padding_needed != 0:
    encoded_payload += '=' * (4 - padding_needed)
    decoded_payload = base64.b64decode(encoded_payload)
    text_part = decoded_payload.split(b"\x00")[-1].decode("utf-8")

    return text_part

    def get_data_for_payment(self, recipient, quantity, mnemonics):
    logging.warning(f"Sending {quantity} stars to @{recipient}...")

    url = self._update_url()

    recipient_id_dirt = [UNFURL='https://requests.post']requests.post[/UNFURL](url, headers=self.headers, cookies=self.cookies,
    data=f"query={recipient}&quantity=&method=searchStarsRecipient")
    recipient_id = recipient_id_dirt.json().get("found", {}).get("recipient", "")


    req_id_dirt = [UNFURL='https://requests.post']requests.post[/UNFURL](url, headers=self.headers, cookies=self.cookies,
    data=f"recipient={recipient_id}&quantity={quantity}&method=initBuyStarsRequest")
    req_id = req_id_dirt.json().get("req_id", "")

    encoded_payload = self._payload_get(req_id, mnemonics)

    buy_payload_dirt = [UNFURL='https://requests.post']requests.post[/UNFURL](url, headers=self.headers, cookies=self.cookies, data=encoded_payload)
    buy_payload = buy_payload_dirt.json()["transaction"]["messages"][0]

    address, amount, encoded_message = buy_payload["address"], buy_payload["amount"], buy_payload["payload"]
    payload = self._message_decode(encoded_message)
    [UNFURL='https://logging.info']logging.info[/UNFURL]("Payment data received!")
    logging.warning("Waiting to send transaction...")
    return address, amount, payload[/CODE]
    [CODE=python]import logging

    from TonTools import TonCenterClient, Wallet
    import asyncio
    from tonsdk.utils import from_nano


    class Transactions:
    @staticmethod
    async def _send_ton_async(mnemonics, destination_address, amount, payload, nano_amount, version, testnet, send_mode):
    provider = TonCenterClient(testnet=testnet)
    wallet = Wallet(mnemonics=mnemonics, version=version, provider=provider)
    if nano_amount:
    amount = from_nano(amount, "ton")

    clean_payload = payload.replace("\n", " ")
    logging.warning(f'Sending {amount} TON to {destination_address} with payload: {clean_payload}')
    if await wallet.transfer_ton(destination_address=destination_address, amount=amount,
    message=payload, send_mode=send_mode) != 200:
    logging.error("Transaction failed!")
    return 0
    else:
    [UNFURL='https://logging.info']logging.info[/UNFURL]("Sending successful!")
    return 1

    def send_ton(self, mnemonics, destination_address, amount, payload, nano_amount=True, version='v4r2', testnet=False, send_mode=0):
    result = [UNFURL='https://asyncio.run']asyncio.run[/UNFURL](self._send_ton_async(mnemonics, destination_address, amount, payload, nano_amount, version, testnet, send_mode))
    return result[/CODE]
    [CODE=python]import asyncio
    import logging

    from tonsdk.contract.wallet import Wallets, WalletVersionEnum
    from json import dumps
    from pathlib import Path
    from TonTools import TonCenterClient

    from tonsdk.utils import bytes_to_b64str


    class WalletUtils:
    @staticmethod
    def _return_wallet_data(mnemonics, pub_k, priv_k, wallet_address):
    return {
    "wallet_address": wallet_address.address.to_string(True, True, True),
    "mnemonics": mnemonics,
    "public_key": pub_k.hex(),
    "private_key": priv_k.hex(),
    "wallet_address_testnet": wallet_address.address.to_string(True, True, True, True)
    }

    def create_wallet(self, version="v4r2", save_to_file=False, save_dir="created_wallets/wallets_data.txt"):
    mnemonics, pub_k, priv_k, wallet_address = Wallets.create(version=getattr(WalletVersionEnum, version),
    workchain=0)

    wallet_data = self._return_wallet_data(mnemonics, pub_k, priv_k, wallet_address)

    if save_to_file:
    Path(save_dir).parent.mkdir(parents=True, exist_ok=True)
    with open(save_dir, "a+", encoding="utf-8") as f:
    f.write(dumps(wallet_data, indent=4, ensure_ascii=False) + "\n\n")

    return wallet_data, wallet_address

    def wallet_from_mnemonics(self, mnemonics: list, version="v4r2"):
    mnemonics, pub_k, priv_k, wallet_address = Wallets.from_mnemonics(mnemonics=mnemonics,
    version=getattr(WalletVersionEnum, version),
    workchain=0)

    wallet_data = self._return_wallet_data(mnemonics, pub_k, priv_k, wallet_address)

    return wallet_data, wallet_address

    def init_wallet(self, mnemonics, testnet=False):
    result = [UNFURL='https://asyncio.run']asyncio.run[/UNFURL](self._init_wallet_async(mnemonics, testnet))
    return result

    async def _init_wallet_async(self, mnemonics, testnet=False):
    wallet_data, wallet_address = self.wallet_from_mnemonics(mnemonics=mnemonics)
    provider = TonCenterClient(testnet=testnet)
    query = wallet_address.create_init_external_message()
    deploy_message = bytes_to_b64str(query['message'].to_boc(False))

    if await provider.send_boc(deploy_message) != 200:
    logging.error('Init error or wallet already init')
    else:
    [UNFURL='https://logging.info']logging.info[/UNFURL] ("Wallet inited!")[/CODE]
    [CODE=python]import json
    from FragmentApi.BuyStars import buy_stars

    if __name__ == '__main__':

    with open("created_wallets/wallets_data.txt", "r") as f:
    mnemonics = json.load(f)['mnemonics']

    recipient = input("Recipient: ")
    amount = input("Amount: ")

    buy_stars(recipient, int(amount), mnemonics, send_mode=1, testnet=False)[/CODE]

    *Проект на GitHub*

    Благодарю за прочтение! :smile_ty:
     
    Этот материал оказался полезным?
    Вы можете отблагодарить автора темы путем перевода средств на баланс
    Отблагодарить автора
    5 июн 2025 в 19:58 Изменено
    1. убежище
      Апатия, пиздец я это читать не буду. Залей архив на гитхаб и не еби голову мне спс
  2. Весть
    Весть 5 июн 2025 в 20:02 На поездку в Москву - 1ОО3\25 ООО 8689 8 авг 2019
    то есть ты просто взял и сделал пересказ официального апи?
     
    1. Апатия Автор темы
      Весть, не совсем, я разбираю библеотеку и не одну, + их документация это буквально темный лес, да в добавок разбор запросов фрагмента
  3. Арбузим
    Арбузим 5 июн 2025 в 20:20 Ух биля какой арбуз https://lolz.live/threads/8642846/ 810 22 авг 2022
    топ тема спасибо годно респект
     
  4. Георгий_Жуков
    Георгий_Жуков 5 июн 2025 в 23:04 На фото Рокоссовский 406 4 июн 2021
    Автору руки целовать, понятным русским языком написано-расписано
     
  5. Robert_PaPa
    В конструкторе для тг-бота от bot-t можно интегрировать?
     
    1. Апатия Автор темы
      Robert_PaPa, Я сам конструкторами никогда не пользовался, поэтому не подскажу
Top