Типы контрактов кошелька
Эта страница переведена сообществом на русский язык, но нуждается в улучшениях. Если вы хотите принять участие в переводе свяжитесь с @alexgton.
Возможно, вы слышали о различных версиях кошельков на блокчейне TON. Но что на самом деле означают эти версии и чем отличаются?
В этой статье мы рассмотрим различные версии и модификации кошельков TON.
Before we start, there are some terms and concepts that you should be familiar with to fully understand the article:
- Управление сообщениями – поскольку это основная функциональность кошельков.
- Язык FunC – потому что мы будем в значительной степени полагаться на реализации, сделанные с его помощью.
Общая концепция
Чтобы разрушить напряжение, для начала мы должны понять, что кошельки не являются особой сущностью в экосистеме TON. Они по-прежнему являются всего лишь смарт-контрактами, состоящими из кода и данных, и в этом смысле равны любому другому субъекту (т.е. смарт-контракту) в TON.
Как и ваш собственный или любой другой смарт-контракт, кошельки могут принимать внешние и внутренние сообщения, отправлять внутренние сообщения и логи, а также использовать GET-методы. Поэтому возникает вопрос: какую функциональность предоставляют кошельки и чем она отличается в разных версиях?
Вы можете рассматривать каждую версию кошелька как реализацию смарт-контракта, предоставляющую стандартный внешний интерфейс, позволяющий различным внешним клиентам взаимодействовать с кошельками одинаковым образом. Вы можете найти эти реализации на языках FunC и Fift в основном репозитарии TON:
Базовые кошельки
Кошелек V1
Это самый простой вариант кошелька. Он позволяет вам отправлять только четыре транзакции одновременно и не проверяет ничего, кроме вашей подписи и seqno.
Исходный код кошелька:
Эта версия кошелька даже не используется в обычных приложениях, потому что у нее есть серьезные проблемы:
- Нет простого способа получить seqno и публичный ключ из контракта.
- Нет проверки
valid_until
, поэтому вы не можете быть уверены, что транзакция не будет подтверждена слишком поздно.
Первая проблема была исправлена в V1R2
и V1R3
. Символ R
означает "ревизия". Обычно ревизии – это небольшие обновления, которые добавляют только GET-методы (вы можете найти историю изменений в файле new-wallet.fif
). Здесь и далее мы будем рассматривать только последние ревизии.
Тем не менее, поскольку каждая последующая версия наследует функциональность предыдущей, нам все равно следует придерживаться ее, поскольку это поможет нам в последующих версиях.
Расположение постоянной памяти
- seqno: 32-битный порядковый номер.
- public-key: 256-битный публичный ключ.
Оформление тела внешнего сообщения
- Данные:
- подпись: 512-битная подпись ed25519.
- msg-seqno: 32-битный порядковый номер.
- (0-4)mode: до четырех 8-битных целых чисел, определяющих режим отправки для каждого сообщения.
- До 4 ссылок на ячейки, содержащие сообщения.
Как вы можете видеть, основная функциональность кошелька заключается в обеспечении безопасного способа связи с блокчейном TON из внешнего мира. Механизм seqno
защищает от атак повторного воспроизведения, а подпись Ed25519
обеспечивает авторизованный доступ к функционалу кошелька. Мы не будем подробно останавливаться на каждом из этих механизмов, поскольку они подробно описаны на странице документации external message и довольно часто встречаются среди смарт-контрактов, получающих внешние сообщения. Данные полезной нагрузки состоят из до 4 ссылок на ячейки и соответствующего количества режимов, которые будут непосредственно переданы в метод send_raw_message(cell msg, int mode).
Обратите внимание, что кошелек не обеспечивает никакой проверки внутренних сообщений, которые вы отправляете через него. Ответственность за сериализацию данных в соответствии с макетом внутреннего сообщения лежит на программисте (т.е. внешнем клиенте).
Коды завершения
Код завершения | Описание |
---|---|
0x21 | Проверка seqno не выполнена, сработала защита ответа |
0x22 | Проверка Ed25519 signature не выполнена |
0x0 | Стандартный код завершения при успешном выполнении |
Обратите внимание, что у TVM есть стандартные коды завершения (0x0
– один из них), поэтому вы также можете получить один из них, например, если у вас закончился газ, то вы получите код 0xD
.
GET-методы
- int
seqno()
– возвращает текущий сохраненный seqno. - int
get_public_key
– возвращает текущий сохраненный публичный ключ.
Кошелек V2
Исходный код кошелька:
В данной версии появился параметр valid_until
, который используется для установки временного ограничения для транзакции, если вы не хотите, чтобы она была подтверждена слишком поздно. В этой версии также отсутствует get-метод для публичного ключа, который был добавлен в V2R2
.
Все отличия по сравнению с предыдущей версией являются следствием добавления функциональности valid_until
. Был добавлен новый код завершения: 0x23
, отмечающий неудачу при проверке valid_until
. Кроме того, в макет тела внешнего сообщения было добавлено новое поле Unix-time
, задающее временной лимит транзакции. Все GET-методы остаются прежними.
Содержимое тела внешнего сообщения
- Данные:
- signature: 512-битная подпись ed25519.
- msg-seqno: 32-битный порядковый номер.
- valid-until: 32-битное целое число Unix-time.
- (0-4)mode: до четырех 8-битных целых чисел, определяющих режим отправки для каждого сообщения.
- До 4 ссылок на ячейки, содержащие сообщения.
Кошелек V3
В этой версии появился параметр subwallet_id
, который позволяет вам создавать несколько кошельков, используя один и тот же публичный ключ (таким образом, у вас может быть только одна seed-фраза и несколько кошельков). Как и раньше, V3R2
добавляет только метод get-метод для публичного ключа.
Исходный код кошелька:
По сути, subwallet_id
– это просто число, добавляемое к состоянию контракта при его развертывании. Поскольку адрес контракта в TON представляет собой хэш его состояния и код, адрес кошелька изменится на другой subwallet_id
. Эта версия наиболее широко используется в настоящее время. Она охватывает большинство случаев использования и остается универсальной, простой и в основном такой же, как и предыдущие версии. Все GET-методы остаются прежними.
Содержимое постоянной памяти
- seqno: 32-битный порядковый н омер.
- subwallet: 32-битный идентификатор subwallet.
- public-key: 256-битный публичный ключ.
Содержимое внешнего сообщения
- Данные:
- подпись: 512-битная подпись ed25519.
- subwallet-id: 32-битный идентификатор subwallet.
- msg-seqno: 32-битный порядковый номер.
- valid-until: 32-битное целое число Unix-time.
- (0-4)mode: до четырех 8-битных целых чисел, определяющих режим отправки для каждого сообщения.
- До 4 ссылок на ячейки, содержащие сообщения.
Коды завершения
Код завершения | Описание |
---|---|
0x23 | Проверка valid_until не выполнена; попытка подтверждения транзакции была предпринята слишком поздно |
0x23 | Проверка Ed25519 signature не выполнена |
0x21 | Проверка seqno не выполнена, сработала защита ответа |
0x22 | subwallet-id не совпадает с сохраненным |
0x0 | Стандартный код завершения при успешном выполнении |
Кошелек V4
Эта версия сохраняет всю функциональность предыдущих версий, но также вводит нечто очень мощное: плагины (plugins).
Исходный код кошелька:
Эта функция позволяет разработчикам реализовать сложную логику, которая работает в тандеме с кошельком пользователя. Например, DApp может потребовать от пользователя платить небольшое количество монет каждый день, чтобы пользоваться определенными функциями. В этом случае пользователю нужно будет установить плагин на свой кошелек, подписав транзакцию. Затем плагин будет ежедневно отправлять монеты на адрес назначения по запросу из внешнего сообщения.
Плагины
Плагины – это, по сути, другие смарт-контракты на TON, которые разработчики могут реализовывать по своему усмотрению. По отношению к кошельку они представляют собой просто адреса смарт-контрактов, хранящиеся в словаре в постоянной памяти кошелька. Этим плагинам разрешено запрашивать средства и удалять себя из "разрешенного списка", отправляя внутренние сообщения кошельку.
Содержимое постоянной памяти
- seqno: 32-битный порядковый номер.
- subwallet-id: 32-битный идентификатор subwallet.
- public-key: 256-битный публичный ключ.
- plugins: словарь, содержащий плагины (может быть пустым).
Получение внутренних сообщений
Все предыдущие версии кошельков имели простую реализацию получения внутренних сообщений. Они просто принимали поступающие средства от любого отправителя, игнорируя тело внутреннего сообщения, если оно присутствовало, или, другими словами, у них был пустой метод recv_internal
. Однако, как уже упоминалось ранее, в четвертой версии кошелька появились две дополнительные доступные операции. Давайте посмотрим на структуру тела внутреннего сообщения:
- op-code: 32-битный код операции. Это необязательное поле: любое сообщение, содержащее менее 32 бит в теле сообщения, либо неправильный oпкод или адрес отправителя, не зарегистрированный в качестве плагина, будет рассматриваться как простая отправка, аналогично предыдущим версиям кошелька.
- query-id: 64-битное целое число. Это поле не влияет на поведение смарт-контракта, оно используется для отслеживания цепочек сообщений между контрактами.
- op-code = 0x706c7567, код операции запроса средств:
- toncoins: VARUINT16 количество запрашиваемых toncoin.
- extra_currencies: словарь, содержащий количество запрашиваемых дополнительных валют (может быть пустым).
- op-code = 0x64737472, запрос на удаление плагина-отправителя из "разрешенного списка".
Содержимое тела внешнего сообщения
- signature: 512-битная подпись ed25519.
- subwallet-id: 32-битный идентификатор subwallet.
- valid-until: 32-битное целое число Unix-time.
- msg-seqno: 32-битное длинное целое число последовательности.
- op-code: 32-битный код операции.
- op-code = 0x0, простая отправка:
- (0-4)mode: до четырех 8-битных целых чисел, определяющих режим отправки для каждого сообщения.
- (0-4)сообщения:До четырех ссылок на ячейки, содержащие сообщения.
- op-code = 0x1, развертывание и установка плагина:
- workchain: 8-битное целое число.
- balance: VARUINT16 количество toncoin начального баланса.
- state-init: ссылка на ячейку, содержащую начальное состояние плагина.
- body: ссылка на ячейку, содержащую тело.
- op-code = 0x2/0x3, установка плагина/удаление плагина:
- wc_n_address: 8-битный workchain_id + 256-битный адрес плагина.
- balance: VARUINT16 сумма toncoin начального баланса.
- query-id: 64-битное целое число.
Как вы можете видеть, четвертая версия по-прежнему предоставляет стандартную функциональность через оп-код 0x0
, как и предыдущие версии. Оп-коды 0x2
и 0x3
позволяют производить манипуляции со словарем плагинов. Обратите внимание, что в случае с 0x2
вам необходимо самостоятельно развернуть плагин с этим адресом, в отличие от оп-кода 0x1
, который также управляет процессом развертывания, но с помощью поля state_init
.
If state_init
doesn't make much sense from its name, take a look at the following references:
Коды завершения
Код завершения | Описание |
---|---|
0x24 | Проверка valid_until не выполнена, попытка подтверждения транзакции была предпринята слишком поздно |
0x23 | Проверка Ed25519 signature не выполнена |
0x21 | Проверка seqno не выполнена, сработала защита ответа |
0x22 | subwallet-id не совпадает с сохраненным |
0x27 | Не удалось выполнить манипуляции со словарем плагинов (0x1-0x3 recv_external op-codes) |
0x50 | Недостаточно средств для запроса средств |
0x0 | Стандартный код завершения при успешном в ыполнении |
GET-методы
- int
seqno()
– возвращает текущий сохраненный seqno. - int
get_public_key()
– возвращает текущий сохраненный публичный ключ. - int
get_subwallet_id()
– возвращает идентификатор текущего subwallet. - int
is_plugin_installed (int wc, int addr_hash)
– проверяет, установлен ли плагин с определенным воркчейн идентификатором и хэшем адреса. - Кортеж
get_plugin_list()
– возвращает список плагинов.
Кошелек V5
На данный момент это самая современная версия кошелька, разработанная командой Tonkeeper, направленная на замену четвертой версии и допускающая произвольные расширения.


Стандарт пятой версии кошелька предлагает множество преимуществ, которые улучшают опыт как для пользователей, так и для продавцов. Данная версия поддерживает транзакции без газа, делегирование и восстановление аккаунта, оплату подписки с использованием токенов и toncoin, а также недорогие мультипереводы. Помимо сохранения предыдущей функциональности, новая версия контракта позволяет отправлять до 255 сообщений одновременно.
Исходный код кошелька:
Схема TL-B:
В отличие от предыдущих спецификаций версий кошельков, мы будем использовать схему TL-B из-за относительной сложности реализации интерфейса этой версии кошелька. Мы дадим некоторое описа ние каждого из них. Тем не менее, базовое понимание все равно необходимо, в сочетании с исходным кодом кошелька этого должно быть достаточно.
Содержимое постоянной памяти
contract_state$_
is_signature_allowed:(## 1)
seqno:#
wallet_id:(## 32)
public_key:(## 256)
extensions_dict:(HashmapE 256 int1) = ContractState;
Как вы видите, contractState
по сравнению с предыдущими версиями, не претерпел серьезных изменений. Основным отличием является новый 1-битный флаг is_signature_allowed
, который ограничивает или разрешает доступ через подпись и хранящийся публичный ключ. Мы опишем важность этого изменения в дальнейшем.
Процесс аутентификации
signed_request$_ // 32 (opcode from outer)
wallet_id: # // 32
valid_until: # // 32
msg_seqno: # // 32
inner: InnerRequest //
signature: bits512 // 512
= SignedRequest; // Total: 688 .. 976 + ^Cell
internal_signed#73696e74 signed:SignedRequest = InternalMsgBody;
internal_extension#6578746e
query_id:(## 64)
inner:InnerRequest = InternalMsgBody;
external_signed#7369676e signed:SignedRequest = ExternalMsgBody;
Прежде чем мы перейдем собственно к полезной нагрузке наших сообщений – InnerRequest
, давайте сначала посмотрим, чем пятая версия отличается от предыдущих версий в части процесса аутентификации. Комбинатор InternalMsgBody
описывает два варианта доступа к действиям кошелька через внутренние сообщения. Первый вариант нам уже знаком по версии 4: аутентификация в качестве ранее зарегистрированного расширения, адрес которого хранится в extensions_dict
. Второй вариант – аутентификация с помощью хранящегося публичного ключа и подписи, аналогично внешним запросам.
Сначала это может показаться ненужной функцией, но на самом деле она позволяет обрабатывать запросы через внешние сервисы (смарт-контракты), которые не являются частью инфраструктуры расширения вашего кошелька – ключевая особенность пятой версии. Безгазовые транзакции полагаются на эту функциональность.
Обратите внимание, что простое получение средств по-прежнему является доступным. Практически же, любое полученное внутреннее сообщение, не прошедшее процесс аутентификации, будет считаться переводом.
Действия
Первое, на что следует обратить внимание, это InnerRequest
, который мы уже видели в процессе аутентификации. В отличие от предыдущей версии, и внешние и внутренние сообщения имеют доступ к одной и той же функциональности, за исключением изменения режима подписи (т.е. флага is_signature_allowed
).
out_list_empty$_ = OutList 0;
out_list$_ {n:#}
prev:^(OutList n)
action:OutAction = OutList (n + 1);
action_send_msg#0ec3c86d mode:(## 8) out_msg:^(MessageRelaxed Any) = OutAction;
// Extended actions in V5:
action_list_basic$_ {n:#} actions:^(OutList n) = ActionList n 0;
action_list_extended$_ {m:#} {n:#} action:ExtendedAction prev:^(ActionList n m) = ActionList n (m+1);
action_add_ext#02 addr:MsgAddressInt = ExtendedAction;
action_delete_ext#03 addr:MsgAddressInt = ExtendedAction;
action_set_signature_auth_allowed#04 allowed:(## 1) = ExtendedAction;
actions$_ out_actions:(Maybe OutList) has_other_actions:(## 1) {m:#} {n:#} other_actions:(ActionList n m) = InnerRequest;
Мы можем рассматривать InnerRequest
как два списка действий:OutList
– представляет собой необязательную цепочку ссылок на ячейки, каждая из которых содержит запрос на отправку сообщения, инициируемый режимом сообщения.ActionList
– инициируется однобитным флагом, has_other_actions
, который отмечает наличие расширенных действий, начиная с первой ячейки и далее в виде цепочки ссылок на ячейки. Мы уже знакомы с первыми двумя расширенными действиями, action_add_ext
и action_delete_ext
, за которыми следует внутренний адрес, который мы хотим добавить или удалить из словаря расширений. Третье действие, action_set_signature_auth_allowed
, ограничивает или разрешает аутентификацию через публичный ключ, оставляя единственный способ взаимодействия с кошельком через расширения. Эта функциональность может оказаться чрезвычайно важной в случае потери или компрометации приватного ключа.
Обратите внимание, что максимальное количество действий равно 255 – это следствие реализации через c5 TVM-регистр. Технически, вы можете сделать запрос с пустыми OutAction
и ExtendedAction
, но в таком случае это будет похоже на простое получение средств.
Коды завершения
Код завершения | Описание |
---|---|
0x84 | Попытка аутентификации через подпись, когда она отключена |
0x85 | Проверка seqno не выполнена, сработала защита ответа |
0x86 | wallet-id не соответствует сохраненному |
0x87 | Проверка Ed25519 signature не выполнена |
0x88 | Проверка valid-until не выполнена |
0x89 | Убедитесь, что в send_mode установлен бит +2 (игнорировать ошибки) для внешнего сообщения. |
0x8A | Префикс external-signed не соответствует полученному префиксу |
0x8B | Операция добавления расширения была неуспешной |
0x8C | Операция удаления расширения была неуспешной |
0x8D | Неподдерживаемый расширенный префикс сообщения |
0x8E | Попытка отключения аутентификации по подписи, пока словарь расширений пуст |
0x8F | Попытка установить подпись в уже установленное состояние |
0x90 | Попытка удаления последнего расширения, когда подпись отключена |
0x91 | Расширение имеет неправильный воркчейн |
0x92 | Попытка изменения режима подписи с помощью внешнего сообщения |
0x93 | Недопустимый c5 , проверка action_send_msg не выполнена |
0x0 | Стандартный код завершения при успешном выполнении |
Обратите внимание, что коды завершения 0x8E
, 0x90
и 0x92
кошелька предназначены для того, чтобы вы не потеряли доступ к функциям кошелька. Тем не менее, вам следует помнить, что кошелек не проверяет, существуют ли сохраненные адреса расширений в TON на самом деле. Вы также можете развернуть кошелек с начальными данными, состоящими из пустого словаря расширений и ограниченного режима подписи. В этом случае вы сможете получить доступ к кошельку через публичный ключ до тех пор, пока не добавите свое первое расширение. Поэтому будьте осторожны с такими сценариями.
GET-методы
- int
is_signature_allowed()
– возвращает сохраненныйis_signature_allowed
флаг. - int
seqno()
– возвращает текущий сохраненный seqno. - int
get_subwallet_id()
– возвращает идентификатор текущего subwallet. - int
get_public_key()
– возвращает текущий сохраненный публичный ключ. - cell
get_extensions()
– возвращает словарь расширений.
Подготовка к безгазовым транз акциям
Как было сказано ранее, смарт-контракт кошелька v5 позволяет обрабатывать внутренние сообщения, подписанные владельцем. Это также позволяет совершать безгазовые транзакции, например, оплачивать сетевые комиссии при переводе USDt в сам USDt. Общая схема выглядит следующим образом:
В следствие этого, будут существовать сервисы (такие как Tonkeeper's Battery), предоста вляющие данную функциональность: они выплачивают комиссию за транзакцию в TON от имени пользователя, но взимают данную комиссию в токенах.
Пользовательский путь
- При отправке USDt пользователь подписывает одно сообщение, содержащее два исходящих перевода USDt:
- Перевод USDt на адрес получателя.
- Перевод небольшой суммы USDt в пользу Сервиса.
- Это подписанное сообщение отправляется off-chain по HTTPS на бэкэнд Сервиса. Бэкэнд Сервиса отправляет его в блокчейн TON, выплачивая toncoin за комиссию сети.
Бета-версия безгазового бэкэнда API доступна на tonapi.io/api-v2. Если вы разрабатываете какое-либо приложение для кошелька и у вас есть отзывы об этих методах, пожалуйста, поделитесь ими в чате @tonapitech.
Исходный код кошелька:
Специальные кошельки
Иногда функциональности базовых кошельков недостаточно. Поэтому существует несколько типов специализированных кошельков: high-load
, lockup
и restricted
.
Давайте посмотрим на них.
Highload-кошельки
При работе с большим количеством сообщений за короткий промежуток времени возникает необходимость в специальном кошельке, называемом Highload Wallet. Прочитайте статью для получения дополнительной информации.
Lockup-кошелек
Если вам по какой-то причине нужно на некоторое время заблокировать монеты в кошельке без возможности их вывести до истечения этого времени, обратите внимание на lockup-кошелек.
Данный типа кошелька позволяет вам установить время, до которого вы не сможете ничего вывести из кошелька. Вы также можете настроить его, установив периоды разблокировки, чтобы получить возможность тратить монеты в течение этих периодов.
Например: вы можете создать кошелек, в котором будет храниться 1 миллион монет с общим сроком наделения правами 10 лет (total duration
). Установите продолжительность блокировки (cliff_diration
) на один год, таким образом, средства будут заблокированы в течение первого года после создания кошелька. Затем вы можете установить период разблокировки (unlock_period
) в один месяц таким образом 1 000 000 TON / 120 месяцев = ~8333 TON
будут разблокированы каждый месяц.
Исходный код кошелька:
Restricted-кошелек
Функциональность данного кошелька заключается в том, чтобы выступать в роли обычного кошелька, но ограничивать переводы только одним заранее определенным адресом назначения. Вы можете задать адрес назначения при создании этого кошелька, и тогда вы сможете переводить средства с него только на этот адрес. Но обратите внимание, что вы все еще можете переводить средства на контракты валидации, так что вы можете запустить валидатора с помощью этого кошелька.
Исходный код кошелька:
Известные оп-коды
Как видите, в TON существует множество различных версий кошельков. Но в большинстве случаев вам нужны только V3R2
или V4R2
. Вы также можете использовать один из специальных кошельков, если вам нужна какая-то дополнительная функциональность, например, периодическая разблокировка средств.