Интеграция TON Connect
Введение
Это руководство объясняет, как интегрировать TON Connect в Python-приложения с использованием библиотеки tonutils
— высокоуровневого SDK, разработанного для удобного взаимодействия с TON. В нём рассматриваются инициализация подключения, аутентификация кошельков, отправка транзакций и подпись данных, обеспечивая практическую основу для создания безопасных dApp-приложений с поддержкой кошельков в сети TON.
Реализация
Установка
Для хранения данных о подключениях кошельков пользователей необходимо реализовать интерфейс хранилища. Для этого требуется дополнительная зависимость. В данном примере используется реализация на основе файла с использованием aiofiles
:
Создание Manifest
Создайте файл manifest.json
для вашего приложения в соответствии с инструкцией и разместите его по публично доступному URL.
Реализация хранилища
Для хранения данных о соединениях с пользовательскими кошельками необходимо реализовать интерфейс хранилища.
Пример реализации
import json
import os
from asyncio import Lock
from typing import Dict, Optional
import aiofiles
from tonutils.tonconnect import IStorage
class FileStorage(IStorage):
def __init__(self, file_path: str):
self.file_path = file_path
self.lock = Lock()
if not os.path.exists(self.file_path):
with open(self.file_path, "w") as f:
json.dump({}, f) # type: ignore
async def _read_data(self) -> Dict[str, str]:
async with self.lock:
async with aiofiles.open(self.file_path, "r") as f:
content = await f.read()
if content:
return json.loads(content)
return {}
async def _write_data(self, data: Dict[str, str]) -> None:
async with self.lock:
async with aiofiles.open(self.file_path, "w") as f:
await f.write(json.dumps(data, indent=4))
async def set_item(self, key: str, value: str) -> None:
data = await self._read_data()
data[key] = value
await self._write_data(data)
async def get_item(self, key: str, default_value: Optional[str] = None) -> Optional[str]:
data = await self._read_data()
return data.get(key, default_value)
async def remove_item(self, key: str) -> None:
data = await self._read_data()
if key in data:
del data[key]
await self._write_data(data)
Инициализация TON Connect
Создайте экземпляр TonConnect
, указав URL манифеста и объект хранилища:
from storage import FileStorage
from tonutils.tonconnect import TonConnect
TC_MANIFEST_URL = "https://raw.githubusercontent.com/nessshon/tonutils/main/examples/tonconnect/tonconnect-manifest.json"
TC_STORAGE = FileStorage("connection.json")
tc = TonConnect(
storage=TC_STORAGE,
manifest_url=TC_MANIFEST_URL,
wallets_fallback_file_path="./wallets.json"
)
Параметр wallets_fallback_file_path
используется как резервный источник данных о кошельках (например, Tonkeeper, Wallet) в случае недоступности внешнего API. Со всеми входными параметрами можно ознакомиться по следующей ссылке.
Инициализация Connector
Создайте экземпляр Connector
для работы с конкретным пользователем:
user_id
может быть целым числом (int
) или строкой (str
).- Если не указан, будет автоматически присвоен инкрементный идентификатор.
- Вы можете сохранить
user_id
для последующего использования:
Подключение кошелька
Получение списка кошельков
Чтобы отобразить доступные кошельки в пользовательском интерфейсе, получите список поддерживаемых кошельков:
Затем вам нужно отобразить список, используя например имя каждого кошелька через wallet.name
.
Подключение к кошельку
После того как пользователь выберет кошелек из списка, инициируйте подключение (для примера, выберем кошелек с индексом 1):
Вы должны отобразить connect_url
пользователю в вашем приложении.
С использованием Redirect URL
Если вы хотите автоматически перенаправить пользователя после успешного подключения, передайте параметр redirect_url
:
redirect_url = "https://example.com/"
connect_url = await connector.connect_wallet(selected_wallet, redirect_url=redirect_url)
С использованием TON Proof
Чтобы убедиться, что пользователь действительно владеет указанным адресом, вы можете включить проверку владения адресом с помощью ton_proof
. Сгенерируйте полезную нагрузку (payload) для проверки и передайте её через параметр ton_proof
. Подробнее о механизме и генерации payload можно узнать здесь.
Вы также можете использовать встроенную функцию generate_proof_payload
из tonutils
:
from tonutils.tonconnect.utils import generate_proof_payload
redirect_url = "https://example.com/"
proof_payload = generate_proof_payload()
connect_url = await connector.connect_wallet(
selected_wallet,
redirect_url=redirect_url,
ton_proof=proof_payload
)
Обработка подключения
Для обработки результата подключения кошелька используйте контекстный менеджер connect_wallet_context
.
Пример использования контекстного менеджера:
from tonutils.tonconnect.utils.exceptions import TonConnectError, UserRejectsError
async with connector.connect_wallet_context() as response:
if isinstance(response, TonConnectError):
if isinstance(response, UserRejectsError):
print("The user rejected the connection.")
else:
print(f"Connection error: {response.message}")
else:
print(f"Connected wallet: {response.account.address.to_str(is_bounceable=False)}")
Пример с проверкой ton_proof
:
from tonutils.tonconnect.utils.exceptions import TonConnectError, UserRejectsError
async with connector.connect_wallet_context() as response:
if isinstance(response, TonConnectError):
if isinstance(response, UserRejectsError):
print("The user rejected the connection.")
else:
print(f"Connection error: {response.message}")
else:
if connector.wallet.verify_proof_payload(proof_payload):
print(f"Connected wallet: {response.account.address.to_str(is_bounceable=False)}")
else:
await connector.disconnect_wallet()
print("Proof verification failed.")
Контекстный менеджер приостанавливает выполнение до тех пор, пока:
- пользователь не подключит кошелёк;
- не произойдёт тайм-аут;
- или не произойдёт ошибка.
В случае успеха вы получите объект WalletInfo, содержащий данные подключенного кошелька. В случае ошибки будет возвращён экземпляр TonConnectError
, который можно обработать, как описано в разделе обработка ошибок.
Отправка запросов
Отправка транзакции
Чтобы отправить Toncoin на определённый адрес, используйте метод send_transaction
:
from tonutils.tonconnect.models import Transaction, Message
rpc_request_id = await connector.send_transaction(
transaction=Transaction(
valid_until=int(time.time() + 5 * 60),
messages=[
Message(
address="UQCZq3_Vd21-4y4m7Wc-ej9NFOhh_qvdfAkAYAOHoQ__Ness",
amount=str(int(1 * 1e9)),
)
]
)
)
valid_until
– время истечения транзакции в секундахaddress
– получатель в user-friendly форматеamount
– сумма в нанотонах
Метод возвращает rpc_request_id
— уникальный идентификатор для отслеживания запроса.
Упрощённая отправка транзакции
Библиотека tonutils
также предоставляет более удобные высокоуровневые методы для отправки транзакций.
- Поле
valid_until
по умолчанию равна 5 минут. - Поле
address
заменено наdestination
(строка или объектAddress
). - Сумма указывается в TON, а не в нанотонах.
-
Параметр
body
может быть: -
объектом
Cell
— для передачи пользовательских данных; - строкой — используется как комментарий или мемо;
Одиночный перевод
rpc_request_id = await connector.send_transfer(
destination="UQCZq3_Vd21-4y4m7Wc-ej9NFOhh_qvdfAkAYAOHoQ__Ness",
amount=1,
body="Hello from tonutils!",
)
Пакетная отправка
Прежде чем отправлять несколько сообщений в одной транзакции, убедитесь, что кошелёк поддерживает необходимое количество сообщений:
Максимальное количество сообщений в одной транзакции зависит от версии кошелька. Учитывайте это ограничение при построении логики отправки транзакции.
from tonutils.tonconnect.models.transfer import TransferMessage
rpc_request_id = await connector.send_batch_transfer(
messages=[
TransferMessage(
destination="UQCZq3_Vd21-4y4m7Wc-ej9NFOhh_qvdfAkAYAOHoQ__Ness",
amount=1,
body="Hello from tonutils!",
),
TransferMessage(
destination="UQCZq3_Vd21-4y4m7Wc-ej9NFOhh_qvdfAkAYAOHoQ__Ness",
amount=2,
body="Hello from tonutils!",
),
]
)
Эти методы также возвращают rpc_request_id
.
Отправка запроса на подпись данных
Метод sign_data
позволяет запросить криптографическую подпись произвольного содержимого от кошелька пользователя. Эта подпись подтверждает явное согласие и может быть проверена вне блокчейна или передана в смарт-контракт.
TON Connect поддерживает три формата данных:
Текст
Используется, когда содержимое предназначено для прочтения человеком.
- Понятно пользователю.
- Идеально подходит для подтверждений вне блокчейна.
from tonutils.tonconnect.models import SignDataPayloadText
text = "I confirm deletion of my account and all associated data."
payload = SignDataPayloadText(text=text)
Бинарный
Используется для хэшей, файлов или нечитаемого содержимого.
- Подходит для цифровых квитанций, доказательств или непрозрачных данных.
from tonutils.tonconnect.models import SignDataPayloadBinary
data = "I confirm deletion of my account and all associated data.".encode("utf-8")
payload = SignDataPayloadBinary(bytes=data)
Ячейка
Используется, когда подпись должна быть проверяема на блокчейне.
- Поддерживает TL-B схемы.
- Позволяет проводить проверку в смарт-контракте.
from pytoniq_core import begin_cell
from tonutils.tonconnect.models import SignDataPayloadCell
comment = "I confirm deletion of my account and all associated data."
cell = begin_cell().store_uint(0, 32).store_snake_string(comment).end_cell()
schema = "text_comment#00000000 text:Snakedata = InMsgBody;"
payload = SignDataPayloadCell(cell=cell, schema=schema)
Последний тип в предоставленной TL-B схеме используется в качестве корневого типа для сериализации.
Отправка запроса
Прежде чем отправить запрос, необходимо убедиться, что подключённый кошелёк поддерживает функцию подписи данных. Используйте следующую проверку:
Если функция не поддерживается, будет вызвано исключение WalletNotSupportFeatureError
. Вы можете обработать его следующим образом:
from tonutils.tonconnect.utils.exceptions import WalletNotSupportFeatureError
try:
connector.device.verify_sign_data_feature(connector.wallet, payload)
except WalletNotSupportFeatureError:
print("Wallet does not support sign data feature!")
# Handle fallback logic or abort
Если функция поддерживается, продолжайте отправку запроса:
Обработка результатов запроса
Для ожидания ответа пользователя на транзакцию или запрос подписи используйте контекстный менеджер pending_request_context
.
Контекстный менеджер приостанавливает выполнение до тех пор, пока:
- пользователь не выполнит действие в своём кошельке;
- не произойдёт тайм-аут;
- или не произойдёт ошибка.
В случае успеха возвращается объект ответа, соответствующий типу запроса:
В случае ошибки возвращается TonConnectError
, который можно обработать, как описано в разделе ошибки запроса.
Обработка подписанных данных
from tonutils.tonconnect.utils.exceptions import TonConnectError, UserRejectsError
async with connector.pending_request_context(rpc_request_id) as response:
if isinstance(response, TonConnectError):
if isinstance(response, UserRejectsError):
print("The user rejected the signing request.")
else:
print(f"Sign data error: {response.message}")
else:
key = connector.wallet.account.public_key
if response.verify_sign_data(key):
print("Verified sign data!")
else:
print("Failed to verify sign data!")
Обработка транзакции
from tonutils.tonconnect.utils.exceptions import TonConnectError, UserRejectsError
async with connector.pending_request_context(rpc_request_id) as response:
if isinstance(response, TonConnectError):
if isinstance(response, UserRejectsError):
print("The user rejected the transaction.")
else:
print(f"Transaction error: {response.message}")
else:
print(f"Transaction sent successfully! Hash: {response.normalized_hash}")
Проверка статуса запроса
Чтобы проверить, ожидает ли запрос подтверждения от пользователя:
Этот метод возвращает True
, если запрос ещё не был подтверждён в кошельке пользователя; в противном случае возвращается False
.
Отмена запроса
Если необходимо отменить запрос по какой-либо причине, используйте:
Это полностью останавливает обработку запроса на уровне приложения, даже если пользователь позже подтвердит его в кошельке.
Отключение кошелька
Чтобы отключить кошелёк пользователя, вызовите метод disconnect_wallet
:
Обработка событий
Помимо контекстных менеджеров, tonutils предоставляет единый событийно-ориентированный интерфейс для реагирования на действия кошелька. Это позволяет обрабатывать события, такие как подключение и отключение кошелька, выполнение транзакций и подписание данных, с помощью зарегистрированных обработчиков.
Типы событий
TON Connect генерирует два типа событий:
Успешные события
Эти события срабатывают при успешном завершении действия:
-
Event.CONNECT
— кошелёк успешно подключён.
Параметры:user_id: int
,wallet: WalletInfo
-
Event.DISCONNECT
— кошелёк отключён.
Параметры:user_id: int
,wallet: WalletInfo
-
Event.TRANSACTION
— пользователь подтвердил транзакцию.
Параметры:user_id: int
,transaction: SendTransactionResponse
,rpc_request_id: int
-
Event.SIGN_DATA
— пользователь одобрил запрос на подписание данных.
Параметры:user_id: int
,sign_data: SignDataResponse
,rpc_request_id: int
События ошибок
Эти события срабатывают в случае сбоя действия — из-за отказа пользователя, тайм-аута или ошибки внутри кошелька/приложения:
EventError.CONNECT
— ошибка при подключении кошелька.EventError.DISCONNECT
— ошибка при отключении кошелька.EventError.TRANSACTION
— ошибка при подтверждении транзакции.EventError.SIGN_DATA
— ошибка при подписании данных.
Все события ошибок содержат:
user_id: int
error: TonConnectError
Примечание: Все типы ошибок подробно описаны в разделе обработка ошибок.
Вы можете обрабатывать эти ошибки так же, как обычные события, используя register_event
или декораторы. Такое разделение позволяет чётко отделять логику обработки успеха и ошибок.
Обработка событий
Вы можете зарегистрировать обработчики событий двумя способами:
С помощью метода
def on_transaction(user_id: int, transaction: SendTransactionResponse):
print(f"Transaction received for user {user_id}")
tc.register_event(Event.TRANSACTION, on_transaction)
С использованием декораторов
@tc.on_event(Event.TRANSACTION)
async def on_transaction(user_id: int, transaction: SendTransactionResponse):
print(f"Transaction confirmed for user {user_id}")
Дополнительные параметры
Помимо стандартных аргументов событий, вы можете передавать пользовательские параметры (например, сессию базы данных, объекты контекста, комментарии) в обработчики событий.
Привязка параметров к конкретному событию:
Вызовите add_event_kwargs
до входа в контекстный менеджер или запуска запроса:
connector.add_event_kwargs(
event=Event.CONNECT,
comment="Hello from tonutils!",
db_session=session,
)
Дополнительные именованные аргументы будут переданы напрямую в соответствующий обработчик:
@tc.on_event(Event.CONNECT)
async def on_connect(user_id: int, wallet: WalletInfo, comment: str, db_session: Session):
...
Определение глобальных параметров для всех событий:
Вы можете задать параметры по умолчанию для всех событий на уровне connector
:
tc = TonConnect(
storage=TC_STORAGE,
manifest_url=TC_MANIFEST_URL,
wallets_fallback_file_path="./wallets.json"
)
tc["db_session"] = session
tc["comment"] = "Shared message"
Эти параметры будут доступны во всех обработчиках событий:
@tc.on_event(Event.SIGN_DATA)
async def on_sign_data(user_id: int, sign_data: SignDataResponse, db_session: Session, comment: str):
...
Обработка ошибок
При работе с TON Connect могут возникать ошибки как во время подключения кошелька, так и при отправке запросов.
Ошибки подключения
Эти ошибки могут возникнуть на этапе инициализации подключения к кошельку:
Код | Ошибка | Описание |
---|---|---|
0 | UnknownError | Неизвестная ошибка в кошельке при обработке запроса. |
1 | BadRequestError | Запрос некорректен или содержит недопустимые параметры. |
2 | ManifestNotFoundError | Указанный manifest_url недоступен или не существует. |
3 | ManifestContentError | Содержимое манифеста имеет некорректную структуру или форматирование. |
100 | UnknownAppError | Ошибка логики приложения при подготовке данных запроса. |
300 | UserRejectsError | Пользователь отказался подключать кошелёк к вашему приложению. |
400 | MethodNotSupportedError | Выбранный кошелёк не поддерживает запрашиваемую операцию или метод. |
500 | RequestTimeoutError | Пользователь не завершил подключение в установленный срок. |
Ошибки запросов
Эти ошибки могут возникать при обработке запросов.
Код | Ошибка | Описание |
---|---|---|
0 | UnknownError | Неизвестная ошибка в кошельке при обработке запроса. |
1 | BadRequestError | Запрос некорректен или содержит недопустимые параметры. |
100 | UnknownAppError | Ошибка логики приложения при подготовке данных запроса. |
300 | UserRejectsError | Пользователь отклонил запрос или отказался подписывать данные. |
400 | MethodNotSupportedError | Выбранный кошелёк не поддерживает запрашиваемую операцию или метод. |
500 | RequestTimeoutError | Пользователь не подтвердил запрос в установленный срок. |
Заключение
TON Connect предоставляет безопасный и удобный способ взаимодействия с кошельками в сети TON. Благодаря поддержке подписи транзакций, верификации личности и обработки событий, он позволяет легко интегрировать функциональность кошельков в Python-приложения.