Перейти к содержанию
Learning Platform
Глоссарий Troubleshooting
Урок 04.09 · 25 мин
Средний
Walletsv3R2v4R2v5GaslessDeployment

Кошельковые контракты

В TON даже кошелёк пользователя — это смарт-контракт, а не просто пара ключей. Это фундаментальное отличие от Ethereum, где аккаунты бывают «внешними» (EOA) и контрактами. Понимание устройства кошельковых контрактов (v3, v4, v5) необходимо для реализации авторизации, мультиподписи и интеграции с TON Connect.

В Ethereum кошелёк — это просто пара ключей (EOA): закрытый ключ подписывает транзакции, а адрес вычисляется из открытого ключа. В TON всё иначе: каждый кошелёк — это полноценный смарт-контракт. Когда вы отправляете TON через Tonkeeper или другое приложение, на самом деле ваш кошелёк-контракт получает подписанное внешнее сообщение, проверяет подпись и отправляет внутренние сообщения от вашего имени.

Понимание внутренней структуры кошельков помогает отлаживать транзакции, строить более надёжные dApp-интеграции и понимать поток газа в TON.


Основы кошельковых контрактов

Кошелёк как смарт-контракт

Каждый кошелёк TON хранит в своём persistent storage:

  • Открытый ключ (public key) — для проверки подписей входящих внешних сообщений
  • seqno (sequence number) — порядковый номер следующего сообщения (защита от повторного воспроизведения)
  • subwallet_id — идентификатор подкошелька (позволяет создавать несколько кошельков из одного ключа)
NOTE

Баланс TON не хранится в данных контракта. Баланс — это свойство аккаунта на уровне протокола, а не переменная в коде контракта. Это отличает TON от токенов ERC-20 в Ethereum, где балансы хранятся в mapping внутри контракта.

Поток обработки сообщений

Поток обработки сообщений кошелька
Пользователь
(приложение)
Внешнее сообщение (подписанное)
Кошелёк-контракт
1. Проверить подпись
2. Проверить seqno
3. Проверить valid_until
4. Увеличить seqno
5. Отправить внутренние сообщения
Внутренние сообщения (до 4 или 255)
Контракт A
Контракт B
Контракт C
TIP

Подробнее о том, как формируются и отправляются транзакции через кошелёк, см. в M07-L03 (Sending Transactions) и M03-L08 (Деплой в тестнет).


Эволюция версий

Сравнительная таблица

Возможностьv3R2v4R2v5 (W5)
Защита seqnoДаДаДа
Subwallet IDДаДаДа
Макс. сообщений44255
Плагины/расширенияНетПлагиныРасширения
Gasless-транзакцииНетНетДа
Снижение комиссий~25%
СтатусLegacyLegacyТекущий стандарт

Wallet v3R2

Базовый кошелёк, ставший стандартом на ранних этапах TON:

  • seqno — каждое сообщение увеличивает порядковый номер на 1, что предотвращает повторное воспроизведение (replay) одного и того же подписанного сообщения
  • subwallet_id — позволяет создать несколько независимых кошельков из одной пары ключей (каждый с уникальным subwallet_id)
  • Максимум 4 исходящих сообщения за одну транзакцию
  • Простая и надёжная конструкция, но ограниченная функциональность

Wallet v4R2

Расширение v3R2 с системой плагинов:

  • Всё то же, что в v3R2 (seqno, subwallet_id, 4 сообщения)
  • Система плагинов — доверенные контракты, которые могут запрашивать TON с кошелька без подписи пользователя
  • Основной сценарий плагинов — подписки: сервис-контракт периодически списывает оплату с кошелька
  • Пользователь устанавливает и удаляет плагины явно

Wallet v5 (W5)

Текущий стандарт, разработанный командой Tonkeeper:

  • До 255 исходящих сообщений за одну транзакцию (вместо 4)
  • Gasless-транзакции — возможность оплаты газа через релееров (пользователь не тратит TON)
  • Расширения — более гибкая замена плагинов v4, с поддержкой произвольных действий
  • ~25% снижение комиссий за счёт оптимизации кода
  • Поддержка аутентификации через внутренние сообщения (не только внешние) — основа для gasless

Gasless-транзакции (v5)

Одно из ключевых нововведений v5 — возможность проведения транзакций без TON на балансе кошелька:

Классическая vs Gasless-транзакция (v5)
Классическая транзакция
Пользователь
Внешнее сообщение
Кошелёк
Контракт
платит газ из своего TON
Gasless-транзакция (v5)
Пользователь
Подписанное сообщение
Релеер
Внутреннее сообщение
Кошелёк
Контракт
релеер платит газ

Как это работает:

  1. Пользователь подписывает сообщение и передаёт его релееру (off-chain серверу)
  2. Релеер оборачивает подписанное сообщение во внутреннее сообщение и отправляет его кошельку v5
  3. Кошелёк v5 проверяет подпись (аутентификация через внутреннее сообщение — уникальная возможность v5)
  4. Кошелёк выполняет запрошенные действия
  5. Релеер получает компенсацию — например, в USDT вместо TON
NOTE

Gasless-транзакции не означают “бесплатные”. Газ всё равно оплачивается — просто не пользователем напрямую. Релеер берёт компенсацию в другой форме (стейблкоинами, через модель подписки сервиса и т.д.).


Чтение исходного кода кошелька

NOTE

FunC-код показан исключительно для навыка чтения существующих контрактов. Для разработки новых контрактов используйте Tact или Tolk.

Рассмотрим ключевые фрагменты recv_external кошелька v4R2 на FunC:

() recv_external(slice in_msg) impure {
    var signature = in_msg~load_bits(512);  ;; 1. Читаем подпись
    var cs = in_msg;
    var (subwallet_id, valid_until, msg_seqno) =
        (cs~load_uint(32), cs~load_uint(32), cs~load_uint(32));

    ;; 2. Проверяем срок действия
    throw_if(35, valid_until <= now());

    ;; 3. Загружаем данные контракта
    var (stored_seqno, stored_subwallet, public_key, plugins) =
        get_data().begin_parse()...;

    ;; 4. Проверяем seqno (защита от replay)
    throw_unless(33, msg_seqno == stored_seqno);

    ;; 5. Проверяем subwallet_id
    throw_unless(34, subwallet_id == stored_subwallet);

    ;; 6. Проверяем подпись Ed25519
    throw_unless(35,
        check_signature(slice_hash(in_msg), signature, public_key));

    ;; 7. Увеличиваем seqno и сохраняем
    set_data(...stored_seqno + 1...);

    ;; 8. Отправляем внутренние сообщения
    ...send_raw_message(msg, mode)...
}

Ключевые моменты:

  • Подпись проверяется первой — если подпись неверна, транзакция отклоняется с минимальным расходом газа
  • seqno увеличивается атомарно — повторная отправка того же сообщения не пройдёт
  • valid_until предотвращает выполнение устаревших сообщений

Развёртывание кошелька

Как кошелёк появляется в блокчейне:

  1. Вычисление адреса — адрес = hash(initial_code + initial_data). Адрес кошелька можно вычислить до деплоя
  2. Пополнение — на вычисленный адрес отправляются TON (кошелёк ещё не существует как контракт, но адрес уже принимает средства)
  3. Первое внешнее сообщение — содержит state_init (код + начальные данные) + подписанное сообщение. Блокчейн создаёт контракт и выполняет первую транзакцию
state_init = {
    code:    <байткод кошелька v5>,
    data:    <seqno=0, subwallet_id=N, public_key=...>
}

address = hash(state_init) → EQ...
TIP

Этот механизм объяснён подробнее в M01-L05 (Addresses and Accounts) и M03-L08 (Деплой в тестнет). Ключевой момент: адрес детерминирован — разные версии кошелька с одним и тем же ключом дают разные адреса.


Практические последствия для разработчиков

Понимание кошельковых контрактов важно при разработке dApp:

  1. TON Connect интеграция — нужно учитывать, что разные пользователи могут использовать разные версии кошельков с разными возможностями (количество сообщений, gasless)
  2. Батчинг транзакций — v5 позволяет отправить до 255 сообщений одной транзакцией, что значительно упрощает сложные операции (multi-swap, batch transfer)
  3. Gasless UX — v5 позволяет строить интерфейсы, где пользователь вообще не взаимодействует с TON напрямую, оплачивая всё в стейблкоинах
  4. Replay protection — при построении сервисов, принимающих транзакции, учитывайте seqno-механизм: одно и то же подписанное сообщение не может быть выполнено дважды

Ключевые выводы

  1. Каждый кошелёк TON — это смарт-контракт, а не просто пара ключей: он хранит public key, seqno и subwallet_id
  2. Баланс TON — свойство протокола, а не переменная в контракте
  3. v5 (W5) — текущий стандарт: 255 сообщений, gasless-транзакции, расширения, ~25% экономия на газе
  4. Gasless работает через релееров: пользователь подписывает, релеер оплачивает газ и получает компенсацию в другой форме
  5. Адрес кошелька = hash(code + data): разные версии кошелька с одним ключом дают разные адреса

Частые ошибки

  1. Предполагают, что все кошельки работают одинаково, хотя версии v3, v4 и v5 имеют разную функциональность (plugins, extensions) и разные форматы сообщений.
  2. Не учитывают seqno при отправке транзакций: каждое внешнее сообщение кошелька должно содержать корректный sequence number, иначе будет отклонено.
  3. Забывают, что неинициализированный кошелёк (без деплоя) не может отправлять сообщения — сначала нужно пополнить и развернуть контракт.
  4. Хардкодят wallet_id, хотя разные сети (mainnet/testnet) используют разные subwallet_id для одного seed.

Проверка знанийKnowledge check
Почему кошелёк v5 может проводить gasless-транзакции, а v3R2 и v4R2 — нет?
ОтветAnswer
Кошелёк v5 поддерживает аутентификацию не только через внешние сообщения (signed by user), но и через внутренние сообщения. Это позволяет использовать схему с релеером: пользователь подписывает сообщение и передаёт его off-chain релееру, который оборачивает его во внутреннее сообщение и отправляет кошельку v5 за свой счёт. Кошелёк проверяет подпись пользователя из внутреннего сообщения и выполняет действия. Кошельки v3R2 и v4R2 принимают аутентифицированные команды только через внешние сообщения, поэтому кто-то должен оплатить газ за доставку внешнего сообщения — и это всегда сам кошелёк.

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 10