Протокол TON Connect 2.0
TON Connect — это стандартный протокол для подключения кошельков к dApps в экосистеме TON, аналог WalletConnect в Ethereum. Без TON Connect ваше приложение не сможет безопасно взаимодействовать с кошельками пользователей. Понимание протокола на уровне архитектуры позволяет реализовать надёжную интеграцию, правильно обрабатывать edge-cases и обеспечить безопасность пользовательских средств.
В предыдущих модулях мы изучили архитектуру TON, написали смарт-контракты на Tact и разобрали токены Jetton 2.0. Теперь перейдём к интеграции: как пользовательские приложения (dApps) взаимодействуют с кошельками для подписания транзакций?
TON Connect 2.0 — это открытый протокол, который решает эту задачу. Он позволяет dApp безопасно соединяться с кошельком пользователя, даже если они работают на разных устройствах — например, dApp в браузере ноутбука, а кошелёк на телефоне.
TON Connect 1.0 устарел
TON Connect 1.0 использовал простые deeplinks и требовал, чтобы dApp и кошелёк были на одном устройстве. Версия 2.0 полностью заменила его, добавив bridge-архитектуру и end-to-end шифрование. Используйте только TON Connect 2.0.
Зачем нужен bridge?
Главная проблема: dApp (веб-сайт в браузере) и кошелёк (мобильное приложение) не имеют прямого сетевого соединения. Они работают в разных средах:
Bridge (мост) решает эту проблему. Это HTTP-сервер, который ретранслирует сообщения между dApp и кошельком через Server-Sent Events (SSE):
Bridge видит только зашифрованные данные. Он ничего не знает о содержимом сообщений — просто передаёт байты от одного Client ID к другому.
Каждый кошелёк использует свой bridge
Tonkeeper использует bridge.tonapi.io, MyTonWallet — свой endpoint. Нет единого “центрального” bridge-сервера. dApp подключается к bridge того кошелька, с которым устанавливает соединение.
Client ID и X25519
Для идентификации сторон TON Connect использует криптографические ключи, а не логины или токены.
Как работает идентификация
Каждая сторона (dApp и кошелёк) генерирует пару ключей X25519 (Curve25519 Diffie-Hellman):
- Публичный ключ = Client ID (идентификатор на bridge)
- Приватный ключ = для расшифровки входящих сообщений
dApp:
KeyPair A = X25519.generate()
Client ID dApp = hex(publicKey_A)
Кошелёк:
KeyPair B = X25519.generate()
Client ID Wallet = hex(publicKey_B)
Шифрование через общий секрет
Когда обе стороны знают публичные ключи друг друга, они вычисляют общий секрет (shared secret) через X25519 Diffie-Hellman:
shared_secret = X25519(privateKey_A, publicKey_B)
= X25519(privateKey_B, publicKey_A) // тот же результат
Все сообщения шифруются с помощью NaCl box (Curve25519-XSalsa20-Poly1305) на основе этого общего секрета. Bridge не может расшифровать сообщения, потому что не имеет ни одного приватного ключа.
QR-код и Universal Link
Первый шаг подключения — dApp должен передать кошельку свой Client ID и параметры подключения. Для этого используется QR-код или Universal Link:
tc://connect?v=2&id=<hex(pubkeyA)>&r=<ConnectRequest>
Параметры URL:
| Параметр | Описание |
|---|---|
v | Версия протокола (2) |
id | Client ID dApp (hex публичного ключа A) |
r | ConnectRequest — JSON с запрашиваемыми данными |
ConnectRequest
dApp указывает, какие данные хочет получить от кошелька:
{
"manifestUrl": "https://myapp.com/tonconnect-manifest.json",
"items": [
{ "name": "ton_addr" },
{ "name": "ton_proof", "payload": "random-nonce-from-backend" }
]
}
ton_addr— адрес кошелька (запрашивается всегда)ton_proof— криптографическое доказательство владения кошельком (опционально, для аутентификации)
SSE-подписка
После отображения QR-кода dApp подписывается на SSE-поток bridge, чтобы получать входящие сообщения:
GET https://bridge.tonapi.io/bridge/events?client_id=<hex(pubkeyA)>
Ответ: Server-Sent Events поток
event: message
data: {"from": "<client_id_B>", "message": "<encrypted_base64>"}
Кошелёк, отсканировав QR-код, также подключается к bridge по своему Client ID. Теперь обе стороны слушают bridge и могут обмениваться зашифрованными сообщениями.
5-шаговый handshake
Рассмотрим полный процесс установления соединения:
Шаг за шагом
1. dApp генерирует KeyPair A и формирует QR-код / Universal Link с Client ID и ConnectRequest.
2. dApp подписывается на SSE bridge по своему Client ID. Bridge начинает ожидать сообщения для этого ID.
3. Кошелёк сканирует QR-код, извлекает Client ID dApp и ConnectRequest. Генерирует собственный KeyPair B и подключается к bridge.
4. Кошелёк отправляет ConnectResponse — зашифрованный ответ с адресом кошелька и запрошенными данными. Bridge ретранслирует его в SSE-поток dApp.
5. Сессия установлена. Обе стороны имеют общий секрет (X25519). Дальнейшие запросы (SendTransactionRequest и др.) передаются через тот же bridge, зашифрованные end-to-end.
ConnectResponse
{
"items": [
{
"name": "ton_addr",
"address": "0:ca6e321c...",
"network": "-239",
"walletStateInit": "..."
}
],
"device": {
"platform": "android",
"appName": "Tonkeeper",
"appVersion": "3.5.0"
}
}
Сохранение сессии
После успешного handshake SDK сохраняет параметры сессии в localStorage браузера:
- Client ID dApp (KeyPair A)
- Client ID кошелька (публичный ключ B)
- Bridge URL
- Адрес кошелька
При повторном визите dApp восстанавливает сессию без нового QR-сканирования. Пользователь видит, что кошелёк уже подключён.
Сравнение с Ethereum
| Аспект | TON Connect 2.0 | WalletConnect (Ethereum) |
|---|---|---|
| Транспорт | Bridge + SSE | Relay server + WebSocket |
| Шифрование | X25519 + NaCl box | X25519 + ChaCha20-Poly1305 |
| Идентификация | Client ID (публичный ключ) | Pairing key (shared secret) |
| QR-код | tc://connect?... | wc:... |
| Cross-device | Да (через bridge) | Да (через relay) |
Оба протокола решают одну задачу — безопасное кросс-девайсное подключение кошелька к dApp. Архитектурные решения схожи: relay-сервер, end-to-end шифрование, QR-код для первичного соединения.
Итоги
| Компонент | Роль |
|---|---|
| Bridge | HTTP-сервер, ретранслирует зашифрованные сообщения через SSE |
| Client ID | Публичный ключ X25519 — идентификатор стороны на bridge |
| X25519 | Алгоритм обмена ключами (Diffie-Hellman на Curve25519) |
| NaCl box | Шифрование сообщений (XSalsa20-Poly1305) |
| ConnectRequest | Запрос dApp на подключение (через QR / Universal Link) |
| ConnectResponse | Ответ кошелька с адресом и запрошенными данными |
В следующем уроке мы перейдём от теории к практике — настроим TON Connect в React-приложении, создадим manifest и добавим кнопку подключения кошелька.
Частые ошибки
- Путают TON Connect 1.0 и 2.0: версия 2.0 использует другой протокол коммуникации (HTTP bridge вместо JS bridge) и несовместима с 1.0.
- Не сохраняют сессию подключения, из-за чего пользователю приходится переподключать кошелёк при каждом обновлении страницы.
- Хардкодят список поддерживаемых кошельков вместо использования динамического списка из wallets-list, и новые кошельки не будут поддерживаться.
- Не обрабатывают отклонение транзакции пользователем: UI зависает в состоянии «ожидание подтверждения» без возможности отмены.
Проверка знанийПочему bridge-сервер не может прочитать содержимое сообщений между dApp и кошельком?
Проверьте понимание
Закончили урок?
Отметьте его как пройденный, чтобы отслеживать свой прогресс
Войдите чтобы оценить урок