Доброго времени суток!
Сегодня хочу немного рассказать о том как работать с блокчейном TON при помоще Python, а так же разберем, как работает Fragment и купим звездочек при помощи скрипта.
Предоставленный код является лишь демонстрацией, и требует некоторых доработок для безошибочной работы. Не отправляйте большие суммы без полной отладки кода!
Код целиком и без пояснений внизу статьи
А еще под конец статьи по какой то неведомой мне причине ломаются код блоки, возможно лимит, уж извиняйте
Итого: у нас есть 2 проблемы:
Первая, это нужно разобраться как нам работать с транзакциями.
Вторая, как нам работать с фрагментом.
Итак, начать очевидно стоит с создания кошелька и его инициализации при помощи вызова контракта. В целом этот шаг можно спокойно пропустить, и создать кошелек в том же тонкипере, однако учтите, что он должен быть обязательно инициализирован. Я же хочу это сделать с помощью кода.
Создание кошелька
Для создания кошелька мы будем использовать библиотеку , а вернее нам нужно всего 2 метода из нее, этоtonsdk
иWallets
WalletVersionEnum
Устанавливаем:pip install tonsdk
Так же сразу начнем выстраивать небольшую структуру, чтобы не лепить все в кучу, условно для работы с кошельком у нас будет файл В нем мы создадим одноименный класс, в котором у нас пока будут сразу две функции,WalletUtils.py
будет отвечать за создание и сохранения нашего кошелька в файл,create_wallet
же будет вспомогательная для структуризации данных этого кошелька._return_wallet_data
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
Pythonfrom 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Давайте запустим и посмотрим что вышло, создадим файлВызваный нами метод принимает версию и воркчейн кошелька который мы хотим создать, я выбираю версию v4r2 так как она банально современнее, но сильной разницы в том какую вы выбирите в целом нет, мы можем заглянуть в этот метод и увидеть какие версии поддерживаются.Wallets.create
Воркчейн, это логическая часть блокчейна, 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
Отлично! Мы только что создали кошелек, хотя подождите, ведь блокчейн даже не знает о его существовании!from wallet.WalletUtils import WalletUtils # Импорт нашего класса
if __name__ == '__main__':
WalletUtils = WalletUtils()
print(WalletUtils.create_wallet(save_to_file=True))Pythonfrom wallet.WalletUtils import WalletUtils # Импорт нашего класса
if __name__ == '__main__':
WalletUtils = WalletUtils()
print(WalletUtils.create_wallet(save_to_file=True))
Инициализация кошелька
Для его записи в блокчейн и дальнейшей работы с ним, нам требуется его инициализировать, для этого мы для нашего класса создадим еще 2 метода,WalletUtils
который будет использоватся для получения данных о кошельке из сид фразы, он не обязателен, так как мы уже получили все данные о кошельке и записали их в файл, но очень удобен если у нас сохранена лишь сид фраза нашего кошелька.wallet_from_mnemonics
будет у нас ассинхронной функцией для взаимодействия с контрактом инициализации, ассинхронность нужна для того, чтобы наш код не зависал во время ожидания ответа от сервера, ну и третяя это функция заглушка_init_wallet_async
через которую мы будем запускать нашу ассинхронную функцию.init_wallet
Для взаимодействия с блокчейном, воспользуемся библиотекой УстанавливаемTonTools
pip install tontools
Пишем код. Так же добавим логгинг для красоты происходящего
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 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!")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!")
Pythonimport 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!")
Пробуем!
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)
Pythonimport 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)
Кажется что то пошло не так. А все потому что нам для инициализации обязательно нужен баланс, стоимость процедуры составляет 0.005 TON ≈ 1,29 рублей
Так как я работаю с тестовой сетью я просто запрошу 2 тестовых TON через бота Telegram: TESTGIVER_TON_BOT
Отправляю ему свой testnet адрес кошелька и спустя минуту получаю свои 2 ton, увидеть это можно на сканере testnet.tonviewer.com если вы делаете это в основной сети, то свой кошелек можно смотреть здесь tonviewer.com
Как можно заметить на кошельке стоит пометка о том, что кошелек еще не инициализирован.
После того как получили баланс, запускаем код еще разок.
Успех! Смотрим что на сканере.
Как видим наш клшелек теперь активен.
Отправка транзакции
Отлично, теперь давайте попробуем отправить куда-нибудь транзакцию, например создадим еще кошелек через наш метод инициализировать его не обязательно, нам просто нужно получить адрес для отправки.WalletUtils.create_wallet
Теперь напишем код для отправки средств. Для отправки средств продолжим использовать TonTools. Так же не забываем о структуре, создадим файл опять же с одноименным классом и двумя методамиTransactions.py
для отправки средств и классическая заглушка в виде_send_ton_async
send_ton
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:
if isinstance(amount, str):
amount = int(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
Pythonimport 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:
if isinstance(amount, str):
amount = int(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Давайте попробуем отправить транзакцию в 0.1 TON с каким-нибудь сообщением.Из новых и неизвестных терминов здесь имеются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 итого получатель получит , но если же мы к нашему send_mode прибавим 1 и отправим ту же транзакцию, то комиссия будет покрыта отдельно с нашего кошелька, а получатель получит ровно ту сумму которую мы указали, то есть 1 TON. Почему это важно? Те кто хоть раз оплачивал при помощи криптовалюты знает, что сервисы требуют отправки точной суммы, но ведь мы не можем заранее знать какая комиссия, чтобы заложить ее в сумму транзакции, для этого мы используем сенд мод 1, чтобы получатель получил ровно ту сумму что мы отправили.1 - 0.007 = 0.993 TON
Ура! Получили ровно ту сумму которую указали, так как указали send_mode=1 все работает!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)
Pythonimport 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)
Это был краткий экскурс в отправку TON, теперь давайте попробуем воспользоваться тем что написали и купить себе звездочек на Fragment.
Разбираем как работает Fragment
Итак, наша вторая проблема, это понять как работает Fragmnet, так как официального api у него нет.
Начнем свое исследование с того, что авторизуемся на фрагмент и подключим наш кошелек через TonKeeper, в который я импортировал созданную нами сид фразу.
Для начала давайте откроем страницу покупки звездочек и посмотрим что там происходит.
Тут нас просят ввести юзернейм и сумму, хорошо, давайте откроем вкладку Network в dev tools и попробуем ввести эти данные, например возьмем телеграм нашего многоуважаемого арбитра BDSM
При вводе username видим запрос, давайте разбираться!
Заглянем в ответ от сервера на наш запрос.
Из полезной информации хочется сразу выделить "recipient" это id пользователя который использует Fragment и "name" его можем использовать для вывода юзер френдли информации, запоминаем эти 2 параметра.
Теперь заглянем в полезную нагрузку запроса, чтобы понять как нам его повторить.
Сразу здесь видим введенный нами Username, в данном случае это "ANSWERXYZ" и метод "searchStarsRecipient" а так же ужастный строчный параметр в виде hash. Можно подумать "Ужас, что это и откуда, как нам его передавать"
Давайте разбираться, подобные хэши могут генерироваться js файлами при загрузке страницы, либо хранится прямиком на html страницы, давайте с нее и начнем, просто берем наш хэш и вставляем в поиск по странице
Нам повезло! То есть мы можем получить хэш просто отправив любой запрос на страницу. Давайте сразу попробуем воспроизвести в скрипте все что мы сейчас сделали.
Создаем файл с таким же классом, тут мы будем получать всю информацию для совершения покупки. Для начала напишем функцииPaymentGet.py
это будет наша основная функция класса и вспомогательныеget_data_for_payment
где будем получать хэш и_hash_get
с помощью которой будем обновлять нашу ссылку, добавляя в нее хэщ. Так же в обязательном порядке нам нужно вытащить куки, воспользуемся куки эдитором._update_url
Как видим тут всего 4 параметра, давайте запишем их в файл в формате json.cookies.json
Итак продолжим написание кода.
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}")
Pythonimport 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}")Отлично! Мы сымитировали запрос сделаный нами через браузер чуть ранее.from FragmentApi.PaymentGet import PaymentGet
if __name__ == '__main__':
PaymentGet = PaymentGet()
PaymentGet.get_data_for_payment("ANSWERXYZ", 50)
Pythonfrom FragmentApi.PaymentGet import PaymentGet
if __name__ == '__main__':
PaymentGet = PaymentGet()
PaymentGet.get_data_for_payment("ANSWERXYZ", 50)
Теперь давайте вернемся в браузер и нажмем кнопку купить, чтобы понять что нам делать дальше.
У нас открылось окно подтверждения покупки и отправился запрос, давайте разбираться
Итак здсь мы видим id получателя который мы успешно записали ранее, количество звезд в данном случае 50 и метод, который нам нужно использовать для отправки этого запроса. Теперь взглянем, что нам это дает
Итак здесь нам интересен только так как это очевидно id нашего запроса на покупку, остальные параметры выглядят мусорными. Следующий этап, жмем подтверждение покупки.req_id
Опа! Новый запрос. Смотрим.
Глаза разбегаются. Смотрим, device в целом понятный параметр, он не сильно важен, но на всякий случай запишем, transaction, show_sender и метод с этим тоже все понятно, далее у нас id, это тот id транзакции который мы получали в прошлом шаге и самое страшное это account, в нем много параметров, address это адрес нашего кошелька в hex, выглядит не супер полезно, пропускам, далее идет chain, это айди сети TON он в целом всегда постоянный, давайте его укажем, далее walletStateInit выглядит как абсолютный мусор, пропускаем, ну и последнее это приватный ключ нашего кошелька, возможно важный параметр, давайте его передадим, получить его очень легко, просто вызовем наш уже знакомый метод и возьмем его оттуда.WalletUtils.wallet_from_mnemonics
С запросом разобрались, теперь давайте взглянем, что мы получаем взамен.
То что нужно! Здесь мы видим адрес на который нужно отправить TON для завершения покупки, так же сумму указанную в нанотонах и зашифрованный memo tag, расшифруем его с помощью модуля и его методаbase64
b64decode
Давайте дополним нашу функцию а так же добавим два новых метода,get_data_for_payment
для составления полезной нагрузки для последнего запроса и_payload_get
для расшифровки memo tag._message_decode
По всей видимости мы готовы испытать эту машину! Но давайте объеденим получение информации о платеже и отправки транзакции. Создадим файл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, payloadPythonimport 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, payloadBuyStars.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]
Победа! Все работает замечательно. Теперь вы настоящий крутой хакер.
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:
if isinstance(amount, str):
amount = int(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]
Загрузка...

Ton + Python on the example of purchasing automation on Fragment