Перейти к основному содержимому

Тонна переработки Jetton

к сведению

Для ясного понимания читатель должен быть знаком с основными принципами обработки активов, описанными в разделе обработки платежей нашей документации.

Джеттоны - это токены на блокчейне TON - их можно рассматривать аналогично токенам ERC-20 на Ethereum.

В этом анализе мы глубже погружаемся в формальные стандарты, подробно описывающие [поведение] (https://github.com/ton-blockchain/TEPs/blob/master/text/0074-jettons-standard.md) и [метаданные] (https://github.com/ton-blockchain/TEPs/blob/master/text/0064-token-data-standard.md) jetton. Менее формальный обзор архитектуры jetton, ориентированный на шардинг, можно найти в нашем блоге анатомия jettons.

Также Вам следует помнить, что существует два подхода к работе с выводами средств из jetton:

  • Memo Deposits - Это позволяет Вам держать один депозитный кошелек, а пользователи добавляют в него памятку, чтобы быть идентифицированными Вашей системой. Это означает, что Вам не нужно сканировать весь блокчейн, но это немного менее удобно для пользователей.
  • Депозиты без мемо - Это решение также существует, но его сложнее интегрировать. Тем не менее, мы можем помочь Вам, если Вы хотите пойти этим путем. Пожалуйста, уведомите нас, прежде чем принять решение о применении этого подхода.

Jetton Architecture

Стандартизированные токены TON реализуются с помощью набора смарт-контрактов, включая:


contracts scheme

Главный смарт-контракт Jetton

Главный смарт-контракт jetton хранит общую информацию о джеттоне (включая общий запас, ссылку на метаданные или сами метаданные).

Остерегайтесь мошенничества от Jetton

Джеттоны с символом==TON или те, которые содержат системные уведомления, такие как: ERROR, SYSTEM и другие. Обязательно проверьте, что джеттоны отображаются в Вашем интерфейсе таким образом, чтобы их нельзя было смешать с передачей TON, системными уведомлениями и т.д.. Иногда даже symbol, name и image создаются так, чтобы выглядеть почти идентично оригиналу, в надежде ввести пользователей в заблуждение.

Чтобы исключить возможность мошенничества для пользователей TON, пожалуйста, посмотрите оригинальный адрес джеттона (Jetton master contract) для конкретных типов джеттонов или следуйте за официальным каналом проекта в социальных сетях или веб-сайтом, чтобы найти корректную информацию. Проверьте активы, чтобы исключить возможность мошенничества, с помощью Tonkeeper ton-assets list.

Извлечение данных Jetton

Чтобы получить более конкретные данные Jetton, используйте метод get контракта get_jetton_data().

Этот метод возвращает следующие данные:

ИмяТипОписание
total_supplyintобщее количество выпущенных джеттонов, измеренное в неделимых единицах.
mintableintПодробно описывает, можно ли чеканить новые джеттоны или нет. Это значение равно либо -1 (можно чеканить), либо 0 (нельзя чеканить).
admin_addressslice
jetton_contentячейкаДанные в соответствии с TEP-64, подробнее смотрите на странице jetton metadata parsing page.
jetton_wallet_codeячейка

Вы можете вызвать его через Toncenter API или один из SDK.

Запустите метод jetton/masters из [Toncenter API] (https://toncenter.com/api/v3/#/default/get_jetton_masters_api_v3_jetton_masters_get)

Джеттон минтер

Как уже упоминалось, джеттоны могут быть как мятными, так и немятными.

Если они не чеканятся, логика становится простой - нет возможности чеканить дополнительные жетоны. Для первой чеканки джеттонов обратитесь к странице Mint your first jetton.

Если джеттоны можно чеканить, в контракте minter contract есть специальная функция для чеканки дополнительных джеттонов. Эту функцию можно вызвать, отправив с адреса администратора внутреннее сообщение с указанным опкодом.

Если администратор jetton хочет ограничить создание jetton, есть три способа сделать это:

  1. Если Вы не можете или не хотите обновлять код контракта, администратору необходимо передать право собственности от текущего администратора на нулевой адрес. В результате контракт останется без действующего администратора, что не позволит никому чеканить джеттоны. Однако это также предотвратит любые изменения в метаданных джеттона.
  2. Если у Вас есть доступ к исходному коду и Вы можете его изменить, Вы можете создать метод в контракте, который устанавливает флаг для прерывания любого процесса чеканки после его вызова, и добавить оператор для проверки этого флага в функцию mint.
  3. Если Вы можете обновить код контракта, Вы можете добавить ограничения, обновив код уже развернутого контракта.

Смарт-контракт для кошелька Jetton

Контракты Jetton wallet используются для отправки, получения и сжигания джеттонов. Каждый контракт кошелька джеттона хранит информацию о балансе кошелька для конкретных пользователей. В отдельных случаях джеттоновые кошельки используются для отдельных держателей джеттонов каждого типа.

Джеттон-кошельки не следует путать с кошельками, предназначенными для взаимодействия с блокчейном и хранения только актива Toncoin (например, кошельки v3R2, highload-кошельки и другие), которые отвечают за поддержку и управление только определенным типом джеттона.

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

При передаче джеттонов между кошельками транзакции (сообщения) требуют определенного количества TON в качестве оплаты сетевых газовых сборов и выполнения действий в соответствии с кодом контракта кошелька Jetton. Это означает, что получателю не нужно разворачивать кошелек jetton перед получением джеттонов. Кошелек Jetton получателя будет развернут автоматически, пока отправитель имеет в кошельке достаточное количество TON , чтобы оплатить необходимые сборы за газ.

Получение адресов кошельков Jetton для данного пользователя

Чтобы получить адрес кошелька jetton с помощью адреса владельца (адреса кошелька TON), главный контракт Jetton предоставляет метод get get_wallet_address(slice owner_address).

Запустите get_wallet_address(slice owner_address) через метод /runGetMethod из Toncenter API.

Получение данных для определенного кошелька Jetton

Чтобы получить баланс кошелька, идентификационные данные владельца и другую информацию, относящуюся к конкретному контракту кошелька jetton, используйте метод get_wallet_data() для получения данных в контракте кошелька jetton.

Этот метод возвращает следующие данные:

ИмяТип
балансint
владелецнарезать
jettonнарезать
jetton_wallet_codeклетка

Используйте метод /jetton/wallets get из Toncenter API, чтобы получить ранее расшифрованные данные кошелька jetton.

Обзор коммуникаций с кошельками Jetton

Обмен данными между кошельками Jetton и кошельками TON происходит в следующей последовательности:

Сообщение 0

Отправитель -> кошелек jetton отправителя. Сообщение Transfer содержит следующие данные:

ИмяТипОписание
query_iduint64Позволяет приложениям связывать между собой три типа сообщений Передача, Уведомление о передаче и Излишки. Чтобы этот процесс выполнялся правильно, рекомендуется всегда использовать уникальный идентификатор запроса.
суммамонетыОбщее количество тонн монет, которое будет отправлено с сообщением.
назначениеадресАдрес нового владельца джеттонов
response_destinationадресАдрес кошелька, используемый для возврата оставшихся тонн монет с сообщением о превышении.
custom_payloadвозможно, клеткаРазмер всегда >= 1 бит. Пользовательские данные (которые используются либо отправителем, либо получателем кошелька jetton для внутренней логики).
передняя_тонна_суммымонетыДолжно быть > 0, если Вы хотите отправить уведомление о передаче с передаваемой полезной нагрузкой. Это часть значения суммы и должно быть меньше, чем сумма.
forward_payloadвозможно, клеткаРазмер всегда >= 1 биту. Если первые 32 бита = 0x0, то это простое сообщение.

Сообщение 2'

Кошелек jetton -> payee. Сообщение-уведомление о переводе. **Отправляется только в том случае, если** переданнаятоннаясумма` не нулевая. Содержит следующие данные:

ИмяТип
query_iduint64
суммамонеты
senderадрес
forward_payloadклетка

Здесь адрес отправителя - это адрес Джеттон-кошелька Алисы.

Сообщение 2''

Кошелек jetton от payee -> Отправитель`. Тело сообщения. Отправляется только в том случае, если после оплаты взносов остались монеты тонны. Содержит следующие данные:

ИмяТип
query_iduint64
Стандартные джеттоны

Подробное описание полей контракта кошелька jetton можно найти в описании интерфейса TEP-74 Jetton standard.

Отправить джеттоны с комментариями

Для этого перевода потребуется несколько тонн монет для fees и, по желанию, сообщение об уведомлении о переводе (проверьте поле "Сумма перевода").

Для отправки комментария Вам необходимо настроить передачу полезной нагрузки. Установите первые 32 бита в 0x0 и добавьте ваш текст.

Передаваемая полезная нагрузка отправляется во внутреннем сообщении уведомление о переводе. Оно будет сгенерировано только в том случае, если переданная сумма > 0.

Наконец, чтобы получить сообщение Excess, Вы должны настроить назначение ответа.

подсказка

Проверьте лучшие практики на примере "отправлять джеттоны с комментариями".

Внецепочечная обработка Jetton

Подтверждение транзакции

Транзакции TON становятся необратимыми после одного подтверждения. Для достижения наилучшего пользовательского опыта рекомендуется избегать ожидания дополнительных блоков после завершения транзакций на блокчейне TON. Подробнее читайте в Catchain.pdf.

Существует два способа приема джеттонов:

  • в рамках централизованного горячего кошелька.
  • использование кошелька с отдельным адресом для каждого отдельного пользователя.

В целях безопасности предпочтительно иметь отдельные горячие кошельки для отдельных джеттонов (много кошельков для каждого типа активов).

При обработке средств также рекомендуется предусмотреть "холодный" кошелек для хранения избыточных средств, которые не участвуют в процессах автоматического ввода и вывода.

Добавление новых джеттонов для обработки активов и первичной проверки

  1. Найдите правильный адрес [смарт-контракта] (/develop/dapps/asset-processing/jettons#jetton-master-smart-contract).
  2. Получите метаданные.
  3. Проверьте наличие мошенничества.

Идентификация неизвестного Jetton при получении сообщения об уведомлении о передаче

Если в Ваш кошелек поступило уведомление о переводе неизвестного Jetton, значит, Ваш кошелек был создан для хранения конкретного Jetton.

Адрес отправителя внутреннего сообщения, содержащего тело Transfer notification, - это адрес нового кошелька Jetton. Его не следует путать с полем отправитель в теле сообщения Уведомление о переводе (/develop/dapps/asset-processing/jettons#jetton-wallets-communication-overview).

  1. Получите главный адрес Jetton для нового кошелька Jetton, используя [получение данных кошелька] (/develop/dapps/asset-processing/jettons#retrieving-data-for-a-specific-jetton-wallet).
  2. Получите адрес кошелька Jetton для Вашего адреса кошелька (как владельца), используя главный контракт Jetton: Как получить адрес кошелька Jetton для данного пользователя
  3. Сравните адрес, возвращенный главным контрактом, и фактический адрес токена в кошельке. Если они совпадают, то это идеальный вариант. Если нет, то, скорее всего, Вы получили мошеннический токен, который является подделкой.
  4. Получение метаданных Jetton: [Как получить метаданные Jetton] (#retrieving-jetton-data).
  5. Проверьте поля symbol и name на наличие признаков мошенничества. При необходимости предупредите пользователя. Добавление нового джеттона для обработки и первичной проверки.

Прием джеттонов от пользователей через централизованный кошелек

к сведению

Чтобы не допустить узкого места в транзакциях, поступающих на один кошелек, рекомендуется принимать депозиты на несколько кошельков и расширять их количество по мере необходимости.

В этом сценарии платежный сервис создает уникальный идентификатор мемо для каждого отправителя, раскрывая адрес централизованного кошелька и отправляемые суммы. Отправитель посылает токены на указанный централизованный адрес с обязательной памяткой в комментарии.

Плюсы этого метода: Этот метод очень прост, поскольку при приеме токенов не взимается никаких дополнительных комиссий, и они поступают непосредственно в горячий кошелек.

Недостатки этого метода: этот метод требует, чтобы все пользователи прикрепляли комментарий к переводу, что может привести к большему количеству ошибок при внесении средств (забытые записки, неправильные записки и т.д.), что означает большую нагрузку на сотрудников службы поддержки.

Примеры Tonweb:

  1. Прием депозитов Jetton на индивидуальный кошелек HOT с комментариями (памятка)
  2. Пример снятия денег с карты Jettons

Препараты

  1. Подготовьте список принятых джеттонов (адреса мастеров джеттонов).
  2. Разверните горячий кошелек (используя v3R2, если не ожидается вывод средств с Jetton; highload v3 - если ожидается вывод средств с Jetton). Развертывание кошелька.
  3. Выполните тестовый перевод Jetton, используя адрес "горячего" кошелька, чтобы инициализировать кошелек.

Обработка поступающих джеттонов

  1. Загрузите список принятых джеттонов.
  2. [Получите адрес кошелька Jetton] (#retrieving-jetton-wallet-addresses-for-a-given-user) для Вашего развернутого горячего кошелька.
  3. Получите главный адрес Jetton для каждого кошелька Jetton, используя [получение данных кошелька] (/develop/dapps/asset-processing/jettons#retrieving-data-for-a-specific-jetton-wallet).
  4. Сравните адреса основных контрактов Jetton из шага 1. и шага 3 (непосредственно выше). Если адреса не совпадают, необходимо сообщить об ошибке проверки адреса Jetton.
  5. Получите список последних необработанных транзакций по счету "горячего" кошелька и проведите его итерацию (сортируя каждую транзакцию по очереди). См: Проверка транзакций контракта.
  6. Проверьте входное сообщение (in_msg) на наличие транзакций и извлеките адрес источника из входного сообщения. Пример Tonweb
  7. Если исходный адрес совпадает с адресом в кошельке Jetton, то необходимо продолжить обработку транзакции. Если нет, то пропустите обработку транзакции и проверьте следующую транзакцию.
  8. Убедитесь, что тело сообщения не пустое и что первые 32 бита сообщения соответствуют коду операции transfer notification 0x7362d09c. Пример Tonweb Если тело сообщения пустое или оп-код недействителен - пропустите транзакцию.
  9. Прочитайте другие данные тела сообщения, включая query_id, amount, sender, forward_payload. Макеты сообщений для контрактов Jetton, Пример Tonweb
  10. Попытайтесь извлечь текстовые комментарии из данных forward_payload. Первые 32 бита должны совпадать с оп-кодом текстового комментария 0x000000, а остальные - с текстом в кодировке UTF-8. Пример Tonweb
  11. Если данные forward_payload пусты или код операции недействителен - пропустите транзакцию.
  12. Сравните полученный комментарий с сохраненными заметками. Если есть совпадение (идентификация пользователя всегда возможна) - пополните счет.
  13. Перезапустите с шага 5 и повторяйте процесс до тех пор, пока не пройдете весь список транзакций.

Прием джеттонов с депозитных адресов пользователей

Чтобы принимать джеттоны с депозитных адресов пользователей, необходимо, чтобы платежный сервис создавал собственный индивидуальный адрес (депозит) для каждого участника, отправляющего средства. Предоставление услуг в этом случае предполагает выполнение нескольких параллельных процессов, включая создание новых депозитов, сканирование блоков на предмет транзакций, вывод средств с депозитов на горячий кошелек и так далее.

Поскольку горячий кошелек может использовать один кошелек Jetton для каждого типа Jetton, необходимо создать несколько кошельков для инициирования депозитов. Чтобы создать большое количество кошельков, но в то же время управлять ими с помощью одной начальной фразы (или закрытого ключа), необходимо указывать различные subwallet_id при создании кошелька. На TON функциональность, необходимая для создания подкошелька, поддерживается кошельками версии v3 и выше.

Создание субкошелька в Tonweb

const WalletClass = tonweb.wallet.all['v3R2'];
const wallet = new WalletClass(tonweb.provider, {
publicKey: keyPair.publicKey,
wc: 0,
walletId: <SUBWALLET_ID>,
});

Подготовка

  1. Подготовьте список принятых джеттонов.
  2. Разверните горячий кошелек (используя v3R2, если не ожидается вывод средств с Jetton; highload v3 - если ожидается вывод средств с Jetton). Развертывание кошелька.

Создание депозитов

  1. Примите запрос на создание нового депозита для пользователя.
  2. Сгенерируйте новый адрес субкошелька (v3R2) на основе семян горячего кошелька. Создание субкошелька в Tonweb
  3. Адрес получения может быть указан пользователю как адрес, используемый для депозитов Джеттон (это адрес владельца депозитного кошелька Джеттон). Инициализация кошелька не требуется, это можно сделать при выводе Джеттонов с депозита.
  4. Для получения этого адреса необходимо вычислить адрес кошелька Jetton через главный контракт Jetton. [Как получить адрес кошелька Jetton для данного пользователя] (#retrieving-jetton-wallet-addresses-for-a-given-user).
  5. Добавьте адрес кошелька Jetton в пул адресов для мониторинга транзакций и сохраните адрес субкошелька.

Обработка транзакций

Подтверждение транзакции

Транзакции TON становятся необратимыми после одного подтверждения. Для достижения наилучшего пользовательского опыта рекомендуется избегать ожидания дополнительных блоков после завершения транзакций на блокчейне TON. Подробнее читайте в Catchain.pdf.

Не всегда можно определить точное количество джеттонов, полученных из сообщения, поскольку кошельки Jetton могут не отправлять сообщения уведомления о переводе, дополнения и внутренний перевод. Они не стандартизированы. Это означает, что нет никакой гарантии, что сообщение внутренней передачи может быть декодировано.

Поэтому, чтобы определить сумму, поступившую в кошелек, необходимо запросить баланс с помощью метода get. Для получения ключевых данных при запросе баланса используются блоки в соответствии с состоянием счета для конкретного блока на цепи. Подготовка к приему блоков с помощью Tonweb.

Этот процесс осуществляется следующим образом:

  1. Подготовка к приему блоков (подготовка системы к приему новых блоков).
  2. Извлеките новый блок и сохраните ID предыдущего блока.
  3. Получайте транзакции из блоков.
  4. Фильтруйте транзакции, используемые только с адресами из пула депозитных кошельков Jetton.
  5. Декодируйте сообщения, используя тело transfer notification, чтобы получить более подробные данные, включая адрес отправителя, сумму Джеттона и комментарий. (См.: Обработка входящих джеттонов)
  6. Если в рамках счета есть хотя бы одна транзакция с недекодируемыми исходящими сообщениями (в теле сообщения отсутствуют оп-коды для уведомления о переводе и оп-коды для выводов) или без исходящих сообщений, то баланс Jetton должен быть запрошен методом get для текущего блока, а для расчета разницы в балансах используется предыдущий блок . Теперь становится известно об изменении общего баланса благодаря транзакциям, проводимым внутри блока.
  7. В качестве идентификатора для неопознанной передачи Jettons (без уведомления о передаче) можно использовать данные транзакции , если присутствует одна такая транзакция, или данные блока (если в блоке присутствует несколько).
  8. Теперь необходимо проверить правильность баланса депозита. Если баланс депозита достаточен для того, чтобы инициировать перевод между горячим кошельком и существующим кошельком Jetton, необходимо вывести джеттоны, чтобы убедиться, что баланс кошелька уменьшился.
  9. Перезапустите с шага 2 и повторите весь процесс.

Снятие средств с депозитов

Не следует осуществлять переводы с депозита на горячий кошелек при каждом пополнении депозита, поскольку за операцию перевода берется комиссия в TON (оплачивается в виде платы за газ в сети). Важно определить определенное минимальное количество джеттонов, которое необходимо для того, чтобы перевод (и, соответственно, депозит) был выгодным.

По умолчанию владельцы депозитных кошельков Jetton не инициализируются. Это связано с тем, что не существует заранее установленного требования платить за хранение. Депозитные кошельки Jetton могут быть развернуты при отправке сообщений с телом transfer, которое затем может быть немедленно уничтожено. Для этого инженер должен использовать специальный механизм для отправки сообщений: 128 + 32.

  1. Получите список депозитов, помеченных для вывода на горячий кошелек
  2. Получите сохраненные адреса владельцев для каждого депозита
  3. Затем сообщения отправляются на каждый адрес владельца (путем объединения нескольких таких сообщений в пакет) с высоконагруженного кошелька с привязанной суммой TON Jetton. Эта сумма определяется путем сложения сборов, использованных для инициализации кошелька v3R2* сборов за отправку сообщения с телом transfer + произвольной суммы TON, связанной с forward_ton_amount (если необходимо). Прилагаемая сумма TON определяется путем сложения платы за инициализацию кошелька v3R2 (значение) + платы за отправку сообщения с телом transfer (значение) + произвольной суммы TON для forward_ton_amount (значение) (если необходимо).
  4. Когда баланс на адресе становится ненулевым, статус аккаунта меняется. Подождите несколько секунд и проверьте статус аккаунта, вскоре он изменится с состояния nonexists на uninit.
  5. Для каждого адреса владельца (со статусом uninit) необходимо отправить внешнее сообщение с кошельком v3R2 init и телом с сообщением transfer для пополнения кошелька Jetton = 128 + 32. Для перевода, пользователь должен указать адрес горячего кошелька в качестве назначения и ответного назначения. Для упрощения идентификации перевода можно добавить текстовый комментарий.
  6. Проверить доставку Джеттонов с помощью адреса депозита на адрес горячего кошелька можно по адресу , принимая во внимание информацию об обработке входящих Джеттонов, найденную здесь.

Вывод средств из оборота Jetton

Важно

Ниже Вы найдете пошаговое руководство по снятию средств с карты jetton.

Чтобы вывести Джеттоны, кошелек отправляет сообщения с телом transfer на соответствующий кошелек Jetton. Затем кошелек Jetton отправляет Джеттоны получателю. По доброй воле важно прикрепить некоторое количество TON в качестве forward_ton_amount (и необязательный комментарий к forward_payload), чтобы вызвать уведомление о переводе. См: Макеты сообщений по контрактам Jetton

Подготовка

  1. Подготовьте список джеттонов для снятия средств: Добавление новых джеттонов для обработки и первичной проверки
  2. Начато развертывание горячего кошелька. Рекомендуется использовать Highload v3. Развертывание кошелька
  3. Выполните перевод Jetton, используя адрес "горячего" кошелька, чтобы инициализировать кошелек Jetton и пополнить его баланс.

Обработка снятия средств

  1. Загрузить список обработанных джеттонов
  2. Получите адреса кошельков Jetton для развернутого горячего кошелька: [Как получить адреса кошельков Jetton для данного пользователя] (#retrieving-jetton-wallet-addresses-for-a-given-user)
  3. Получите главные адреса Jetton для каждого кошелька Jetton: Как получить данные для кошельков Jetton. Требуется параметр jetton (который на самом деле является адресом мастер-контракта Jetton).
  4. Сравните адреса из основных контрактов Jetton из шага 1. и шага 3. Если адреса не совпадают, то следует сообщить об ошибке проверки адреса в Jetton.
  5. Поступают запросы на вывод средств, в которых указывается тип Jetton, переводимая сумма и адрес кошелька получателя.
  6. Проверьте баланс кошелька Jetton, чтобы убедиться в наличии достаточного количества средств для осуществления вывода.
  7. Сгенерируйте сообщение.
  8. При использовании кошелька с высокой нагрузкой рекомендуется собирать партии сообщений и отправлять по одной партии за раз, чтобы оптимизировать комиссионные сборы.
  9. Сохраните время истечения срока действия для исходящих внешних сообщений (это время, пока кошелек успешно обработает сообщение, после этого кошелек больше не будет принимать сообщение)
  10. Отправьте одно сообщение или несколько сообщений (пакетная передача сообщений).
  11. Получите список последних необработанных транзакций на счете "горячего" кошелька и выполните его итерацию. Подробнее здесь: Проверка транзакций контракта, Пример Tonweb или используйте метод Toncenter API /getTransactions.
  12. Просмотрите исходящие сообщения в аккаунте.
  13. Если существует сообщение с кодом операции transfer, то его следует декодировать, чтобы получить значение query_id. Полученные query_id должны быть отмечены как успешно отправленные.
  14. Если время, необходимое для обработки текущей отсканированной транзакции, превышает время истечения срока действия, а исходящее сообщение с заданным query_id не найдено, то запрос должен (это необязательно) быть помечен как истекший и должен быть безопасно отправлен повторно.
  15. Просмотрите входящие сообщения в аккаунте.
  16. Если существует сообщение, в котором используется операционный код excesses, сообщение должно быть декодировано и в нем должно быть найдено значение query_id . Найденный query_id должен быть помечен как успешно доставленный.
  17. Перейдите к шагу 5. Просроченные запросы, которые не были успешно отправлены, должны быть перемещены обратно в список снятия.

Обработка цепи Jetton

Как правило, для приема и обработки джеттонов обработчик сообщений, отвечающий за внутренние сообщения, использует оп-код op=0x7362d09c.

Подтверждение транзакции

Транзакции TON становятся необратимыми после одного подтверждения. Для достижения наилучшего пользовательского опыта рекомендуется избегать ожидания дополнительных блоков после завершения транзакций на блокчейне TON. Подробнее читайте в Catchain.pdf.

Рекомендации по обработке на цепочке

Ниже приведен список рекомендаций, которые необходимо учитывать при проведении обработки джеттона на цепи:

  1. Идентифицируйте входящие джеттоны по типу их кошелька, а не по их мастер-контракту Jetton. Другими словами, Ваш контракт должен взаимодействовать (получать и отправлять сообщения) с конкретным кошельком Jetton (а не с каким-то неизвестным кошельком, использующим конкретный мастер-контракт Jetton).
  2. При установлении связи между кошельком Jetton и мастер-контрактом Jetton, убедитесь, что эта связь является двунаправленной, когда кошелек распознает мастер-контракт и наоборот. Например, если Ваша контрактная система получает уведомление от кошелька Jetton (который считает свой MySuperJetton своим мастер-контрактом), то информация о переводе должна быть показана пользователю, прежде чем показывать symbol, name и image контракта MySuperJetton, проверьте, что кошелек MySuperJetton использует правильную контрактную систему. В свою очередь, если Ваша контрактная система по каким-то причинам должна отправлять джеттоны, используя мастер-контракты MySuperJetton или MySuperJetton, проверьте, что кошелек X, как и кошелек, использует те же параметры контракта. Кроме того, перед отправкой запроса перевода на X убедитесь, что он признает MySuperJetton своим мастером.
  3. Истинная сила** децентрализованных финансов (DeFi) основана на возможности складывать протоколы друг на друга, как блоки лего. Например, скажем, джеттон А обменивается на джеттон Б, который, в свою очередь, затем используется в качестве рычага в протоколе кредитования (когда пользователь предоставляет ликвидность), который затем используется для покупки NFT .... и так далее. Таким образом, рассмотрим, как контракт может обслуживать не только пользователей вне цепи, но и сущности на цепи, присоединяя токенизированную ценность к уведомлению о передаче, добавляя пользовательскую полезную нагрузку, которая может быть отправлена вместе с уведомлением о передаче.
  4. Обратите внимание, что не все джеттоны следуют одним и тем же стандартам. К сожалению, некоторые джеттоны могут быть враждебными (использующими векторы атак) и созданными исключительно для того, чтобы атаковать ничего не подозревающих пользователей. В целях безопасности, если рассматриваемый протокол состоит из множества контрактов, не создавайте большое количество джеттон-кошельков одного типа. В частности, не отправляйте джеттоны внутри протокола между контрактом депозита, контрактом хранилища, контрактом пользовательского счета и т.д. Злоумышленники могут намеренно вмешиваться в логику контракта, подделывая уведомления о переводе, суммы джеттонов или параметры полезной нагрузки. Уменьшите вероятность атак, используя только один кошелек в системе для одного джеттона (для всех депозитов и снятий).
  5. Также часто хорошей идеей является создание субконтрактов для каждого отдельного джеттона, чтобы снизить вероятность подмены адреса (например, когда сообщение о передаче отправляется на джеттон B с использованием контракта, предназначенного для джеттона A).
  6. Настоятельно рекомендуется** работать с неделимыми единицами джеттона на уровне контракта. Логика, связанная с десятичными единицами, обычно используется для улучшения пользовательского интерфейса (UI) дипломата и не связана с ведением числовых записей на цепи.

Чтобы узнать больше о Secure Smart Contract Programming in FunC by CertiK, не стесняйтесь читать этот ресурс. Разработчикам рекомендуется разрабатывать все исключения смарт-контрактов, чтобы не пропустить их во время разработки приложения.

Рекомендации по обработке кошельков Jetton

Как правило, все процедуры проверки, используемые для обработки внецепочечного джеттона, подходят и для кошельков. Для обработки кошельков Jetton наши самые важные рекомендации заключаются в следующем:

  1. Когда кошелек получает уведомление о переводе средств от неизвестного кошелька jetton, очень важно доверять кошельку jetton и его мастер-адресу, поскольку это может быть вредоносная подделка. Чтобы защитить себя, проверьте Jetton Master (главный контракт) по указанному адресу, чтобы убедиться, что Ваши процессы верификации распознают кошелек jetton как легитимный. После того, как Вы доверитесь кошельку и он будет признан легитимным, Вы можете разрешить ему получить доступ к остаткам на Ваших счетах и другим данным в кошельке. Если Jetton Master не распознает этот кошелек, рекомендуется вообще не инициировать и не раскрывать свои переводы jetton, а показывать только входящие переводы TON (из Toncoin, прикрепленных к уведомлениям о переводе).
  2. На практике, если пользователь хочет взаимодействовать с Jetton, а не с кошельком Jetton. Другими словами, пользователи отправляют wTON/oUSDT/jUSDT, jUSDC, jDAI вместо EQAjN.../EQBLE... и т.д.. Часто это означает, что когда пользователь инициирует перевод на jetton, кошелек спрашивает у соответствующего мастера jetton, какой кошелек jetton (принадлежащий пользователю) должен инициировать запрос на перевод. Очень важно никогда слепо не доверять этим данным от мастера (мастер-контракта). Прежде чем отправлять запрос на перевод в кошелек jetton, всегда убедитесь, что кошелек jetton действительно принадлежит тому Мастеру Jetton, за которого он себя выдает.
  3. Примите во внимание, что недружественные Jetton Masters/jetton wallets могут со временем менять свои кошельки/мастера. Поэтому пользователи должны проявлять должную осмотрительность и проверять легитимность любых кошельков, с которыми они взаимодействуют, перед каждым использованием.
  4. Всегда убедитесь, что Вы отображаете джеттоны в своем интерфейсе таким образом, чтобы они не смешивались с передачей TON, системными уведомлениями и т.д.. Даже параметры symbol, name и image могут быть составлены таким образом, чтобы ввести пользователей в заблуждение, оставив их потенциальными жертвами мошенничества. Было несколько случаев, когда вредоносные джеттоны использовались для выдачи себя за переводы TON, ошибки в уведомлениях, получение вознаграждений или объявления о замораживании активов.
  5. Всегда будьте начеку с потенциальными злоумышленниками, которые создают поддельные джеттоны, и всегда хорошо предоставить пользователям функциональность, необходимую для устранения нежелательных джеттонов в их основном пользовательском интерфейсе.

Авторы: kosrk, krigga, EmelyanenkoK и tolya-yanot.

Лучшие практики

Если Вам нужны готовые к тестированию примеры, проверьте SDKs и попробуйте их запустить. Ниже приведены фрагменты кода, которые помогут Вам понять процесс обработки джеттонов на примерах кода.

Отправить Джеттоны с комментарием

Исходный код
// first 4 bytes are tag of text comment
const comment = new Uint8Array([... new Uint8Array(4), ... new TextEncoder().encode('text comment')]);

await wallet.methods.transfer({
secretKey: keyPair.secretKey,
toAddress: JETTON_WALLET_ADDRESS, // address of Jetton wallet of Jetton sender
amount: TonWeb.utils.toNano('0.05'), // total amount of TONs attached to the transfer message
seqno: seqno,
payload: await jettonWallet.createTransferBody({
jettonAmount: TonWeb.utils.toNano('500'), // Jetton amount (in basic indivisible units)
toAddress: new TonWeb.utils.Address(WALLET2_ADDRESS), // recepient user's wallet address (not Jetton wallet)
forwardAmount: TonWeb.utils.toNano('0.01'), // some amount of TONs to invoke Transfer notification message
forwardPayload: comment, // text comment for Transfer notification message
responseAddress: walletAddress // return the TONs after deducting commissions back to the sender's wallet address
}),
sendMode: 3,
}).send()

Примите Jetton Transfer с разбором комментария

Исходный код
import {
Address,
TonClient,
Cell,
beginCell,
storeMessage,
JettonMaster,
OpenedContract,
JettonWallet,
Transaction
} from '@ton/ton';


export async function retry<T>(fn: () => Promise<T>, options: { retries: number, delay: number }): Promise<T> {
let lastError: Error | undefined;
for (let i = 0; i < options.retries; i++) {
try {
return await fn();
} catch (e) {
if (e instanceof Error) {
lastError = e;
}
await new Promise(resolve => setTimeout(resolve, options.delay));
}
}
throw lastError;
}

export async function tryProcessJetton(orderId: string) : Promise<string> {

const client = new TonClient({
endpoint: 'https://toncenter.com/api/v2/jsonRPC',
apiKey: 'TONCENTER-API-KEY', // https://t.me/tonapibot
});

interface JettonInfo {
address: string;
decimals: number;
}

interface Jettons {
jettonMinter : OpenedContract<JettonMaster>,
jettonWalletAddress: Address,
jettonWallet: OpenedContract<JettonWallet>
}

const MY_WALLET_ADDRESS = 'INSERT-YOUR-HOT-WALLET-ADDRESS'; // your HOT wallet

const JETTONS_INFO : Record<string, JettonInfo> = {
'jUSDC': {
address: 'EQB-MPwrd1G6WKNkLz_VnV6WqBDd142KMQv-g1O-8QUA3728', //
decimals: 6
},
'jUSDT': {
address: 'EQBynBO23ywHy_CgarY9NK9FTz0yDsG82PtcbSTQgGoXwiuA',
decimals: 6
},
}
const jettons: Record<string, Jettons> = {};

const prepare = async () => {
for (const name in JETTONS_INFO) {
const info = JETTONS_INFO[name];
const jettonMaster = client.open(JettonMaster.create(Address.parse(info.address)));
const userAddress = Address.parse(MY_WALLET_ADDRESS);

const jettonUserAddress = await jettonMaster.getWalletAddress(userAddress);

console.log('My jetton wallet for ' + name + ' is ' + jettonUserAddress.toString());

const jettonWallet = client.open(JettonWallet.create(jettonUserAddress));

//const jettonData = await jettonWallet;
const jettonData = await client.runMethod(jettonUserAddress, "get_wallet_data")

jettonData.stack.pop(); //skip balance
jettonData.stack.pop(); //skip owneer address
const adminAddress = jettonData.stack.readAddress();


if (adminAddress.toString() !== (Address.parse(info.address)).toString()) {
throw new Error('jetton minter address from jetton wallet doesnt match config');
}

jettons[name] = {
jettonMinter: jettonMaster,
jettonWalletAddress: jettonUserAddress,
jettonWallet: jettonWallet
};
}
}

const jettonWalletAddressToJettonName = (jettonWalletAddress : Address) => {
const jettonWalletAddressString = jettonWalletAddress.toString();
for (const name in jettons) {
const jetton = jettons[name];

if (jetton.jettonWallet.address.toString() === jettonWalletAddressString) {
return name;
}
}
return null;
}

// Subscribe
const Subscription = async ():Promise<Transaction[]> =>{

const client = new TonClient({
endpoint: 'https://toncenter.com/api/v2/jsonRPC',
apiKey: 'TONCENTER-API-KEY', // https://t.me/tonapibot
});

const myAddress = Address.parse('INSERT-YOUR-HOT-WALLET'); // Address of receiver TON wallet
const transactions = await client.getTransactions(myAddress, {
limit: 5,
});
return transactions;
}

return retry(async () => {

await prepare();
const Transactions = await Subscription();

for (const tx of Transactions) {

const sourceAddress = tx.inMessage?.info.src;
if (!sourceAddress) {
// external message - not related to jettons
continue;
}

if (!(sourceAddress instanceof Address)) {
continue;
}

const in_msg = tx.inMessage;

if (in_msg?.info.type !== 'internal') {
// external message - not related to jettons
continue;
}

// jetton master contract address check
const jettonName = jettonWalletAddressToJettonName(sourceAddress);
if (!jettonName) {
// unknown or fake jetton transfer
continue;
}

if (tx.inMessage === undefined || tx.inMessage?.body.hash().equals(new Cell().hash())) {
// no in_msg or in_msg body
continue;
}

const msgBody = tx.inMessage;
const sender = tx.inMessage?.info.src;
const originalBody = tx.inMessage?.body.beginParse();
let body = originalBody?.clone();
const op = body?.loadUint(32);
if (!(op == 0x7362d09c)) {
continue; // op != transfer_notification
}

console.log('op code check passed', tx.hash().toString('hex'));

const queryId = body?.loadUint(64);
const amount = body?.loadCoins();
const from = body?.loadAddress();
const maybeRef = body?.loadBit();
const payload = maybeRef ? body?.loadRef().beginParse() : body;
const payloadOp = payload?.loadUint(32);
if (!(payloadOp == 0)) {
console.log('no text comment in transfer_notification');
continue;
}

const comment = payload?.loadStringTail();
if (!(comment == orderId)) {
continue;
}

console.log('Got ' + jettonName + ' jetton deposit ' + amount?.toString() + ' units with text comment "' + comment + '"');
const txHash = tx.hash().toString('hex');
return (txHash);
}
throw new Error('Transaction not found');
}, {retries: 30, delay: 1000});
}

SDKs

Вы можете найти список SDK для различных языков (js, python, golang, C#, Rust и т.д.) список здесь.

См. также