Тонна переработки 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 реализуются с помощью набора смарт-контрактов, включая:
- Jetton master смарт-контракт
- Кошелек 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_supply | int | общее количество выпущенных джеттонов, измеренное в неделимых единицах. |
mintable | int | Подробно описывает, можно ли чеканить новые джеттоны или нет. Это значение равно либо -1 (можно чеканить), либо 0 (нельзя чеканить). |
admin_address | slice | |
jetton_content | ячейка | Данные в соответствии с TEP-64, подробнее смотрите на странице jetton metadata parsing page. |
jetton_wallet_code | ячейка |
Вы можете вызвать его через Toncenter API или один из SDK.
- API
- js
Запустите метод
jetton/masters
из [Toncenter API] (https://toncenter.com/api/v3/#/default/get_jetton_masters_api_v3_jetton_masters_get)
import TonWeb from "tonweb";
const tonweb = new TonWeb();
const jettonMinter = new TonWeb.token.jetton.JettonMinter(tonweb.provider, {address: "<JETTON_MASTER_ADDRESS>"});
const data = await jettonMinter.getJettonData();
console.log('Total supply:', data.totalSupply.toString());
console.log('URI to off-chain metadata:', data.jettonContentUri);
Джеттон минтер
Как уже упоминалось, джеттоны могут быть как мятными
, так и немятными
.
Если они не чеканятся, логика становится простой - нет возможности чеканить дополнительные жетоны. Для первой чеканки джеттонов обратитесь к странице Mint your first jetton.
Если джеттоны можно чеканить, в контракте minter contract есть специальная функция для чеканки дополнительных джеттонов. Эту функцию можно вызвать, отправив с адреса администратора внутреннее сообщение
с указанным опкодом.
Если администратор jetton хочет ограничить создание jetton, есть три способа сделать это:
- Если Вы не можете или не хотите обновлять код контракта, администратору необходимо передать право собственности от текущего администратора на нулевой адрес. В результате контракт останется без действующего администратора, что не позволит никому чеканить джеттоны. Однако это также предотвратит любые изменения в метаданных джеттона.
- Если у Вас есть доступ к исходному коду и Вы можете его изменить, Вы можете создать метод в контракте, который устанавливает флаг для прерывания любого процесса чеканки после его вызова, и добавить оператор для проверки этого флага в функцию mint.
- Если Вы можете обновить код контракта, Вы можете добавить ограничения, обновив код уже развернутого контракта.
Смарт-контракт для кошелька Jetton
Контракты Jetton wallet
используются для отправки, получения и сжигания джеттонов. Каждый контракт кошелька джеттона хранит информацию о балансе кошелька для конкретных пользователей.
В отдельных случаях джеттоновые кошельки используются для отдельных держателей джеттонов каждого типа.
Джеттон-кошельки
не следует путать с кошельками, предназначенными для взаимодействия с блокчейном и хранения
только актива Toncoin (например, кошельки v3R2, highload-кошельки и другие),
которые отвечают за поддержку и управление только определенным типом джеттона.
Развертывание кошелька Jetton
При передаче джеттонов
между кошельками транзакции (сообщения) требуют определенного количества TON
в качестве оплаты сетевых газовых сборов и выполнения действий в соответствии с кодом контракта кошелька Jetton.
Это означает, что получателю не нужно разворачивать кошелек jetton перед получением джеттонов.
Кошелек Jetton получателя будет развернут автоматически, пока отправитель имеет в кошельке достаточное количество TON
, чтобы оплатить необходимые сборы за газ.
Получение адресов кошельков Jetton для данного пользователя
Чтобы получить адрес
кошелька jetton
с помощью адреса владельца
(адреса кошелька TON),
главный контракт Jetton
предоставляет метод get get_wallet_address(slice owner_address)
.
- API
- js
Запустите
get_wallet_address(slice owner_address)
через метод/runGetMethod
из Toncenter API.
import TonWeb from "tonweb";
const tonweb = new TonWeb();
const jettonMinter = new TonWeb.token.jetton.JettonMinter(tonweb.provider, {address: "<JETTON_MASTER_ADDRESS>"});
const address = await jettonMinter.getJettonWalletAddress(new TonWeb.utils.Address("<OWNER_WALLET_ADDRESS>"));
// It is important to always check that wallet indeed is attributed to desired Jetton Master:
const jettonWallet = new TonWeb.token.jetton.JettonWallet(tonweb.provider, {
address: jettonWalletAddress
});
const jettonData = await jettonWallet.getData();
if (jettonData.jettonMinterAddress.toString(false) !== new TonWeb.utils.Address(info.address).toString(false)) {
throw new Error('jetton minter address from jetton wallet doesnt match config');
}
console.log('Jetton wallet address:', address.toString(true, true, true));
Получение данных для определенного кошелька Jetton
Чтобы получить баланс кошелька, идентификационные данные владельца и другую информацию, относящуюся к конкретному контракту кошелька jetton, используйте метод get_wallet_data()
для получения данных в контракте кошелька jetton.
Этот метод возвращает следующие данные:
Имя | Тип |
---|---|
баланс | int |
владелец | нарезать |
jetton | нарезать |
jetton_wallet_code | клетка |
- API
- js
Используйте метод
/jetton/wallets
get из Toncenter API, чтобы получить ранее расшифрованные данные кошелька jetton.
import TonWeb from "tonweb";
const tonweb = new TonWeb();
const walletAddress = "EQBYc3DSi36qur7-DLDYd-AmRRb4-zk6VkzX0etv5Pa-Bq4Y";
const jettonWallet = new TonWeb.token.jetton.JettonWallet(tonweb.provider,{address: walletAddress});
const data = await jettonWallet.getData();
console.log('Jetton balance:', data.balance.toString());
console.log('Jetton owner address:', data.ownerAddress.toString(true, true, true));
// It is important to always check that Jetton Master indeed recognize wallet
const jettonMinter = new TonWeb.token.jetton.JettonMinter(tonweb.provider, {address: data.jettonMinterAddress.toString(false)});
const expectedJettonWalletAddress = await jettonMinter.getJettonWalletAddress(data.ownerAddress.toString(false));
if (expectedJettonWalletAddress.toString(false) !== new TonWeb.utils.Address(walletAddress).toString(false)) {
throw new Error('jetton minter does not recognize the wallet');
}
console.log('Jetton master address:', data.jettonMinterAddress.toString(true, true, true));
Обзор коммуникаций с кошельками Jetton
Обмен данными между кошельками Jetton и кошельками TON происходит в следующей последовательности:
Сообщение 0
Отправитель -> кошелек jetton отправителя
. Сообщение Transfer содержит следующие данные:
Имя | Тип | Описание |
---|---|---|
query_id | uint64 | Позволяет приложениям связывать между собой три типа сообщений Передача , Уведомление о передаче и Излишки . Чтобы этот процесс выполнялся правильно, рекомендуется всегда использовать уникальный идентификатор запроса. |
сумма | монеты | Общее количество тонн монет , которое будет отправлено с сообщением. |
назначение | адрес | Адрес нового владельца джеттонов |
response_destination | адрес | Адрес кошелька, используемый для возврата оставшихся тонн монет с сообщением о превышении. |
custom_payload | возможно, клетка | Размер всегда >= 1 бит. Пользовательские данные (которые используются либо отправителем, либо получателем кошелька jetton для внутренней логики). |
передняя_тонна_суммы | монеты | Должно быть > 0, если Вы хотите отправить уведомление о передаче с передаваемой полезной нагрузкой . Это часть значения суммы и должно быть меньше, чем сумма . |
forward_payload | возможно, клетка | Размер всегда >= 1 биту. Если первые 32 бита = 0x0, то это простое сообщение. |
Сообщение 2'
Кошелек jetton -> payee. Сообщение-уведомление о переводе. **Отправляется только в том случае, если**
переданнаятоннаясумма` не нулевая. Содержит следующие данные:
Имя | Тип |
---|---|
query_id | uint64 |
сумма | монеты |
sender | адрес |
forward_payload | клетка |
Здесь адрес отправителя
- это адрес Джеттон-кошелька
Алисы.
Сообщение 2''
Кошелек jetton от payee
-> Отправитель`. Тело сообщения. Отправляется только в том случае, если после оплаты взносов остались монеты тонны. Содержит следующие данные:
Имя | Тип |
---|---|
query_id | uint64 |
Подробное описание полей контракта кошелька jetton можно найти в описании интерфейса TEP-74 Jetton standard
.
Отправить джеттоны с комментариями
Для этого перевода потребуется несколько тонн монет для fees и, по желанию, сообщение об уведомлении о переводе (проверьте поле "Сумма перевода").
Для отправки комментария Вам необходимо настроить передачу полезной нагрузки
. Установите первые 32 бита в 0x0 и добавьте ваш текст.
Передаваемая полезная нагрузка
отправляется во внутреннем сообщении уведомление о переводе
. Оно будет сгенерировано только в том случае, если переданная сумма
> 0.
Наконец, чтобы получить сообщение Excess
, Вы должны настроить назначение ответа
.
Проверьте лучшие практики на примере "отправлять джеттоны с комментариями".
Внецепочечная обработка Jetton
Транзакции TON становятся необратимыми после одного подтверждения. Для достижения наилучшего пользовательского опыта рекомендуется избегать ожидания дополнительных блоков после завершения транзакций на блокчейне TON. Подробнее читайте в Catchain.pdf.
Существует два способа приема джеттонов:
- в рамках централизованного горячего кошелька.
- использование кошелька с отдельным адресом для каждого отдельного пользователя.
В целях безопасности предпочтительно иметь отдельные горячие кошельки для отдельных джеттонов (много кошельков для каждого типа активов).
При обработке средств также рекомендуется предусмотреть "холодный" кошелек для хранения избыточных средств, которые не участвуют в процессах автоматического ввода и вывода.
Добавление новых джеттонов для обработки активов и первичной проверки
- Найдите правильный адрес [смарт-контракта] (/develop/dapps/asset-processing/jettons#jetton-master-smart-contract).
- Получите метаданные.
- Проверьте наличие мошенничества.
Идентификация неизвестного Jetton при получении сообщения об уведомлении о передаче
Если в Ваш кошелек поступило уведомление о переводе неизвестного Jetton, значит, Ваш кошелек был создан для хранения конкретного Jetton.
Адрес отправителя внутреннего сообщения, содержащего тело Transfer notification
, - это адрес нового кошелька Jetton.
Его не следует путать с полем отправитель
в теле сообщения Уведомление о переводе
(/develop/dapps/asset-processing/jettons#jetton-wallets-communication-overview).
- Получите главный адрес Jetton для нового кошелька Jetton, используя [получение данных кошелька] (/develop/dapps/asset-processing/jettons#retrieving-data-for-a-specific-jetton-wallet).
- Получите адрес кошелька Jetton для Вашего адреса кошелька (как владельца), используя главный контракт Jetton: Как получить адрес кошелька Jetton для данного пользователя
- Сравните адрес, возвращенный главным контрактом, и фактический адрес токена в кошельке. Если они совпадают, то это идеальный вариант. Если нет, то, скорее всего, Вы получили мошеннический токен, который является подделкой.
- Получение метаданных Jetton: [Как получить метаданные Jetton] (#retrieving-jetton-data).
- Проверьте поля
symbol
иname
на наличие признаков мошенничества. При необходимости предупредите пользователя. Добавление нового джеттона для обработки и первичной проверки.
Прием джеттонов от пользователей через централизованный кошелек
Чтобы не допустить узкого места в транзакциях, поступающих на один кошелек, рекомендуется принимать депозиты на несколько кошельков и расширять их количество по мере необходимости.
В этом сценарии платежный сервис создает уникальный идентификатор мемо для каждого отправителя, раскрывая адрес централизованного кошелька и отправляемые суммы. Отправитель посылает токены на указанный централизованный адрес с обязательной памяткой в комментарии.
Плюсы этого метода: Этот метод очень прост, поскольку при приеме токенов не взимается никаких дополнительных комиссий, и они поступают непосредственно в горячий кошелек.
Недостатки этого метода: этот метод требует, чтобы все пользователи прикрепляли комментарий к переводу, что может привести к большему количеству ошибок при внесении средств (забытые записки, неправильные записки и т.д.), что означает большую нагрузку на сотрудников службы поддержки.
Примеры Tonweb:
- Прием депозитов Jetton на индивидуальный кошелек HOT с комментариями (памятка)
- Пример снятия денег с карты Jettons
Препараты
- Подготовьте список принятых джеттонов (адреса мастеров джеттонов).
- Разверните горячий кошелек (используя v3R2, если не ожидается вывод средств с Jetton; highload v3 - если ожидается вывод средств с Jetton). Развертывание кошелька.
- Выполните тестовый перевод Jetton, используя адрес "горячего" кошелька, чтобы инициализировать кошелек.
Обработка поступающих джеттонов
- Загрузите список принятых джеттонов.
- [Получите адрес кошелька Jetton] (#retrieving-jetton-wallet-addresses-for-a-given-user) для Вашего развернутого горячего кошелька.
- Получите главный адрес Jetton для каждого кошелька Jetton, используя [получение данных кошелька] (/develop/dapps/asset-processing/jettons#retrieving-data-for-a-specific-jetton-wallet).
- Сравните адреса основных контрактов Jetton из шага 1. и шага 3 (непосредственно выше). Если адреса не совпадают, необходимо сообщить об ошибке проверки адреса Jetton.
- Получите список последних необработанных транзакций по счету "горячего" кошелька и проведите его итерацию (сортируя каждую транзакцию по очереди). См: Проверка транзакций контракта.
- Проверьте входное сообщение (in_msg) на наличие транзакций и извлеките адрес источника из входного сообщения. Пример Tonweb
- Если исходный адрес совпадает с адресом в кошельке Jetton, то необходимо продолжить обработку транзакции. Если нет, то пропустите обработку транзакции и проверьте следующую транзакцию.
- Убедитесь, что тело сообщения не пустое и что первые 32 бита сообщения соответствуют коду операции
transfer notification
0x7362d09c
. Пример Tonweb Если тело сообщения пустое или оп-код недействителен - пропустите транзакцию. - Прочитайте другие данные тела сообщения, включая
query_id
,amount
,sender
,forward_payload
. Макеты сообщений для контрактов Jetton, Пример Tonweb - Попытайтесь извлечь текстовые комментарии из данных
forward_payload
. Первые 32 бита должны совпадать с оп-кодом текстового комментария0x000000
, а остальные - с текстом в кодировке UTF-8. Пример Tonweb - Если данные
forward_payload
пусты или код операции недействителен - пропустите транзакцию. - Сравните полученный комментарий с сохраненными заметками. Если есть совпадение (идентификация пользователя всегда возможна) - пополните счет.
- Перезапустите с шага 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>,
});
Подготовка
- Подготовьте список принятых джеттонов.
- Разверните горячий кошелек (используя v3R2, если не ожидается вывод средств с Jetton; highload v3 - если ожидается вывод средств с Jetton). Развертывание кошелька.
Создание депозитов
- Примите запрос на создание нового депозита для пользователя.
- Сгенерируйте новый адрес субкошелька (v3R2) на основе семян горячего кошелька. Создание субкошелька в Tonweb
- Адрес получения может быть указан пользователю как адрес, используемый для депозитов Джеттон (это адрес владельца депозитного кошелька Джеттон). Инициализация кошелька не требуется, это можно сделать при выводе Джеттонов с депозита.
- Для получения этого адреса необходимо вычислить адрес кошелька Jetton через главный контракт Jetton. [Как получить адрес кошелька Jetton для данного пользователя] (#retrieving-jetton-wallet-addresses-for-a-given-user).
- Добавьте адрес кошелька Jetton в пул адресов для мониторинга транзакций и сохраните адрес субкошелька.
Обработка транзакций
Транзакции TON становятся необратимыми после одного подтверждения. Для достижения наилучшего пользовательского опыта рекомендуется избегать ожидания дополнительных блоков после завершения транзакций на блокчейне TON. Подробнее читайте в Catchain.pdf.
Не всегда можно определить точное количество джеттонов, полученных из сообщения, поскольку кошельки Jetton
могут не отправлять сообщения уведомления о переводе
, дополнения
и внутренний перевод
. Они не стандартизированы. Это означает,
что нет никакой гарантии, что сообщение внутренней передачи
может быть декодировано.
Поэтому, чтобы определить сумму, поступившую в кошелек, необходимо запросить баланс с помощью метода get. Для получения ключевых данных при запросе баланса используются блоки в соответствии с состоянием счета для конкретного блока на цепи. Подготовка к приему блоков с помощью Tonweb.
Этот процесс осуществляется следующим образом:
- Подготовка к приему блоков (подготовка системы к приему новых блоков).
- Извлеките новый блок и сохраните ID предыдущего блока.
- Получайте транзакции из блоков.
- Фильтруйте транзакции, используемые только с адресами из пула депозитных кошельков Jetton.
- Декодируйте сообщения, используя тело
transfer notification
, чтобы получить более подробные данные, включая адресотправителя
, сумму Джеттона и комментарий. (См.: Обработка входящих джеттонов) - Если в рамках счета
есть хотя бы одна транзакция с недекодируемыми исходящими сообщениями (в теле сообщения отсутствуют оп-коды для
уведомления о переводе
и оп-коды длявыводов
) или без исходящих сообщений, то баланс Jetton должен быть запрошен методом get для текущего блока, а для расчета разницы в балансах используется предыдущий блок . Теперь становится известно об изменении общего баланса благодаря транзакциям, проводимым внутри блока. - В качестве идентификатора для неопознанной передачи Jettons (без
уведомления о передаче
) можно использовать данные транзакции , если присутствует одна такая транзакция, или данные блока (если в блоке присутствует несколько). - Теперь необходимо проверить правильность баланса депозита. Если баланс депозита достаточен для того, чтобы инициировать перевод между горячим кошельком и существующим кошельком Jetton, необходимо вывести джеттоны, чтобы убедиться, что баланс кошелька уменьшился.
- Перезапустите с шага 2 и повторите весь процесс.
Снятие средств с депозитов
Не следует осуществлять переводы с депозита на горячий кошелек при каждом пополнении депозита, поскольку за операцию перевода берется комиссия в TON (оплачивается в виде платы за газ в сети). Важно определить определенное минимальное количество джеттонов, которое необходимо для того, чтобы перевод (и, соответственно, депозит) был выгодным.
По умолчанию владельцы депозитных кошельков Jetton не инициализируются. Это связано с тем, что не существует заранее установленного требования
платить за хранение. Депозитные кошельки Jetton могут быть развернуты при отправке сообщений с телом
transfer
, которое затем может быть немедленно уничтожено. Для этого инженер должен использовать специальный механизм
для отправки сообщений: 128 + 32.
- Получите список депозитов, помеченных для вывода на горячий кошелек
- Получите сохраненные адреса владельцев для каждого депозита
- Затем сообщения отправляются на каждый адрес владельца (путем объединения нескольких таких сообщений в пакет) с высоконагруженного кошелька
с привязанной суммой TON Jetton. Эта сумма определяется путем сложения сборов, использованных для инициализации кошелька v3R2* сборов за отправку сообщения с телом
transfer
+ произвольной суммы TON, связанной сforward_ton_amount
(если необходимо). Прилагаемая сумма TON определяется путем сложения платы за инициализацию кошелька v3R2 (значение) + платы за отправку сообщения с теломtransfer
(значение) + произвольной суммы TON дляforward_ton_amount
(значение) (если необходимо). - Когда баланс на адресе становится ненулевым, статус аккаунта меняется. Подождите несколько секунд и проверьте статус
аккаунта, вскоре он изменится с состояния
nonexists
наuninit
. - Для каждого адреса владельца (со статусом
uninit
) необходимо отправить внешнее сообщение с кошельком v3R2 init и телом с сообщениемtransfer
для пополнения кошелька Jetton = 128 + 32. Дляперевода
, пользователь должен указать адрес горячего кошелька в качественазначения
иответного назначения
. Для упрощения идентификации перевода можно добавить текстовый комментарий. - Проверить доставку Джеттонов с помощью адреса депозита на адрес горячего кошелька можно по адресу , принимая во внимание информацию об обработке входящих Джеттонов, найденную здесь.
Вывод средств из оборота Jetton
Ниже Вы найдете пошаговое руководство по снятию средств с карты jetton.
Чтобы вывести Джеттоны, кошелек отправляет сообщения с телом transfer
на соответствующий кошелек Jetton.
Затем кошелек Jetton отправляет Джеттоны получателю. По доброй воле важно прикрепить некоторое количество TON
в качестве forward_ton_amount
(и необязательный комментарий к forward_payload
), чтобы вызвать уведомление о переводе
.
См: Макеты сообщений по контрактам Jetton
Подготовка
- Подготовьте список джеттонов для снятия средств: Добавление новых джеттонов для обработки и первичной проверки
- Начато развертывание горячего кошелька. Рекомендуется использовать Highload v3. Развертывание кошелька
- Выполните перевод Jetton, используя адрес "горячего" кошелька, чтобы инициализировать кошелек Jetton и пополнить его баланс.
Обработка снятия средств
- Загрузить список обработанных джеттонов
- Получите адреса кошельков Jetton для развернутого горячего кошелька: [Как получить адреса кошельков Jetton для данного пользователя] (#retrieving-jetton-wallet-addresses-for-a-given-user)
- Получите главные адреса Jetton для каждого кошелька Jetton: Как получить данные для кошельков Jetton.
Требуется параметр
jetton
(который на самом деле является адресом мастер-контракта Jetton). - Сравните адреса из основных контрактов Jetton из шага 1. и шага 3. Если адреса не совпадают, то следует сообщить об ошибке проверки адреса в Jetton.
- Поступают запросы на вывод средств, в которых указывается тип Jetton, переводимая сумма и адрес кошелька получателя.
- Проверьте баланс кошелька Jetton, чтобы убедиться в наличии достаточного количества средств для осуществления вывода.
- Сгенерируйте сообщение.
- При использовании кошелька с высокой нагрузкой рекомендуется собирать партии сообщений и отправлять по одной партии за раз, чтобы оптимизировать комиссионные сборы.
- Сохраните время истечения срока действия для исходящих внешних сообщений (это время, пока кошелек успешно обработает сообщение, после этого кошелек больше не будет принимать сообщение)
- Отправьте одно сообщение или несколько сообщений (пакетная передача сообщений).
- Получите список последних необработанных транзакций на счете "горячего" кошелька и выполните его итерацию.
Подробнее здесь: Проверка транзакций контракта,
Пример Tonweb или
используйте метод Toncenter API
/getTransactions
. - Просмотрите исходящие сообщения в аккаунте.
- Если существует сообщение с кодом операции
transfer
, то его следует декодировать, чтобы получить значениеquery_id
. Полученныеquery_id
должны быть отмечены как успешно отправленные. - Если время, необходимое для обработки текущей отсканированной транзакции, превышает
время истечения срока действия, а исходящее сообщение с заданным
query_id
не найдено, то запрос должен (это необязательно) быть помечен как истекший и должен быть безопасно отправлен повторно. - Просмотрите входящие сообщения в аккаунте.
- Если существует сообщение, в котором используется операционный код
excesses
, сообщение должно быть декодировано и в нем должно быть найдено значениеquery_id
. Найденныйquery_id
должен быть помечен как успешно доставленный. - Перейдите к шагу 5. Просроченные запросы, которые не были успешно отправлены, должны быть перемещены обратно в список снятия.
Обработка цепи Jetton
Как правило, для приема и обработки джеттонов обработчик сообщений, отвечающий за внутренние сообщения, использует оп-код op=0x7362d09c
.
Транзакции TON становятся необратимыми после одного подтверждения. Для достижения наилучшего пользовательского опыта рекомендуется избегать ожидания дополнительных блоков после завершения транзакций на блокчейне TON. Подробнее читайте в Catchain.pdf.
Рекомендации по обработке на цепочке
Ниже приведен список рекомендаций
, которые необходимо учитывать при проведении обработки джеттона на цепи:
- Идентифицируйте входящие джеттоны по типу их кошелька, а не по их мастер-контракту Jetton. Другими словами, Ваш контракт должен взаимодействовать (получать и отправлять сообщения) с конкретным кошельком Jetton (а не с каким-то неизвестным кошельком, использующим конкретный мастер-контракт Jetton).
- При установлении связи между кошельком Jetton и мастер-контрактом Jetton, убедитесь, что эта связь является двунаправленной, когда кошелек распознает мастер-контракт и наоборот. Например, если Ваша контрактная система получает уведомление от кошелька Jetton (который считает свой MySuperJetton своим мастер-контрактом), то информация о переводе должна быть показана пользователю, прежде чем показывать
symbol
,name
иimage
контракта MySuperJetton, проверьте, что кошелек MySuperJetton использует правильную контрактную систему. В свою очередь, если Ваша контрактная система по каким-то причинам должна отправлять джеттоны, используя мастер-контракты MySuperJetton или MySuperJetton, проверьте, что кошелек X, как и кошелек, использует те же параметры контракта. Кроме того, перед отправкой запросаперевода
на X убедитесь, что он признает MySuperJetton своим мастером. - Истинная сила** децентрализованных финансов (DeFi) основана на возможности складывать протоколы друг на друга, как блоки лего. Например, скажем, джеттон А обменивается на джеттон Б, который, в свою очередь, затем используется в качестве рычага в протоколе кредитования (когда пользователь предоставляет ликвидность), который затем используется для покупки NFT .... и так далее. Таким образом, рассмотрим, как контракт может обслуживать не только пользователей вне цепи, но и сущности на цепи, присоединяя токенизированную ценность к уведомлению о передаче, добавляя пользовательскую полезную нагрузку, которая может быть отправлена вместе с уведомлением о передаче.
- Обратите внимание, что не все джеттоны следуют одним и тем же стандартам. К сожалению, некоторые джеттоны могут быть враждебными (использующими векторы атак) и созданными исключительно для того, чтобы атаковать ничего не подозревающих пользователей. В целях безопасности, если рассматриваемый протокол состоит из множества контрактов, не создавайте большое количество джеттон-кошельков одного типа. В частности, не отправляйте джеттоны внутри протокола между контрактом депозита, контрактом хранилища, контрактом пользовательского счета и т.д. Злоумышленники могут намеренно вмешиваться в логику контракта, подделывая уведомления о переводе, суммы джеттонов или параметры полезной нагрузки. Уменьшите вероятность атак, используя только один кошелек в системе для одного джеттона (для всех депозитов и снятий).
- Также часто хорошей идеей является создание субконтрактов для каждого отдельного джеттона, чтобы снизить вероятность подмены адреса (например, когда сообщение о передаче отправляется на джеттон B с использованием контракта, предназначенного для джеттона A).
- Настоятельно рекомендуется** работать с неделимыми единицами джеттона на уровне контракта. Логика, связанная с десятичными единицами, обычно используется для улучшения пользовательского интерфейса (UI) дипломата и не связана с ведением числовых записей на цепи.
Чтобы узнать больше о Secure Smart Contract Programming in FunC by CertiK, не стесняйтесь читать этот ресурс. Разработчикам рекомендуется разрабатывать все исключения смарт-контрактов, чтобы не пропустить их во время разработки приложения.
Рекомендации по обработке кошельков Jetton
Как правило, все процедуры проверки, используемые для обработки внецепочечного джеттона, подходят и для кошельков. Для обработки кошельков Jetton наши самые важные рекомендации заключаются в следующем:
- Когда кошелек получает уведомление о переводе средств от неизвестного кошелька jetton, очень важно доверять кошельку jetton и его мастер-адресу, поскольку это может быть вредоносная подделка. Чтобы защитить себя, проверьте Jetton Master (главный контракт) по указанному адресу, чтобы убедиться, что Ваши процессы верификации распознают кошелек jetton как легитимный. После того, как Вы доверитесь кошельку и он будет признан легитимным, Вы можете разрешить ему получить доступ к остаткам на Ваших счетах и другим данным в кошельке. Если Jetton Master не распознает этот кошелек, рекомендуется вообще не инициировать и не раскрывать свои переводы jetton, а показывать только входящие переводы TON (из Toncoin, прикрепленных к уведомлениям о переводе).
- На практике, если пользователь хочет взаимодействовать с Jetton, а не с кошельком Jetton. Другими словами, пользователи отправляют wTON/oUSDT/jUSDT, jUSDC, jDAI вместо
EQAjN...
/EQBLE...
и т.д.. Часто это означает, что когда пользователь инициирует перевод на jetton, кошелек спрашивает у соответствующего мастера jetton, какой кошелек jetton (принадлежащий пользователю) должен инициировать запрос на перевод. Очень важно никогда слепо не доверять этим данным от мастера (мастер-контракта). Прежде чем отправлять запрос на перевод в кошелек jetton, всегда убедитесь, что кошелек jetton действительно принадлежит тому Мастеру Jetton, за которого он себя выдает. - Примите во внимание, что недружественные Jetton Masters/jetton wallets могут со временем менять свои кошельки/мастера. Поэтому пользователи должны проявлять должную осмотрительность и проверять легитимность любых кошельков, с которыми они взаимодействуют, перед каждым использованием.
- Всегда убедитесь, что Вы отображаете джеттоны в своем интерфейсе таким образом, чтобы они не смешивались с передачей TON, системными уведомлениями и т.д.. Даже параметры
symbol
,name
иimage
могут быть составлены таким образом, чтобы ввести пользователей в заблуждение, оставив их потенциальными жертвами мошенничества. Было несколько случаев, когда вредоносные джеттоны использовались для выдачи себя за переводы TON, ошибки в уведомлениях, получение вознаграждений или объявления о замораживании активов. - Всегда будьте начеку с потенциальными злоумышленниками, которые создают поддельные джеттоны, и всегда хорошо предоставить пользователям функциональность, необходимую для устранения нежелательных джеттонов в их основном пользовательском интерфейсе.
Авторы: kosrk, krigga, EmelyanenkoK и tolya-yanot.
Лучшие практики
Если Вам нужны готовые к тестированию примеры, проверьте SDKs и попробуйте их запустить. Ниже приведены фрагменты кода, которые помогут Вам понять процесс обработки джеттонов на примерах кода.
Отправить Джеттоны с комментарием
- JS (tonweb)
- Golang
- Python
- Python
Исходный код
// 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()
Исходный код
client := liteclient.NewConnectionPool()
// connect to testnet lite server
err := client.AddConnectionsFromConfigUrl(context.Background(), "https://ton.org/global.config.json")
if err != nil {
panic(err)
}
ctx := client.StickyContext(context.Background())
// initialize ton api lite connection wrapper
api := ton.NewAPIClient(client)
// seed words of account, you can generate them with any wallet or using wallet.NewSeed() method
words := strings.Split("birth pattern then forest walnut then phrase walnut fan pumpkin pattern then cluster blossom verify then forest velvet pond fiction pattern collect then then", " ")
w, err := wallet.FromSeed(api, words, wallet.V3R2)
if err != nil {
log.Fatalln("FromSeed err:", err.Error())
return
}
token := jetton.NewJettonMasterClient(api, address.MustParseAddr("EQD0vdSA_NedR9uvbgN9EikRX-suesDxGeFg69XQMavfLqIw"))
// find our jetton wallet
tokenWallet, err := token.GetJettonWallet(ctx, w.WalletAddress())
if err != nil {
log.Fatal(err)
}
amountTokens := tlb.MustFromDecimal("0.1", 9)
comment, err := wallet.CreateCommentCell("Hello from tonutils-go!")
if err != nil {
log.Fatal(err)
}
// address of receiver's wallet (not token wallet, just usual)
to := address.MustParseAddr("EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N")
transferPayload, err := tokenWallet.BuildTransferPayload(to, amountTokens, tlb.ZeroCoins, comment)
if err != nil {
log.Fatal(err)
}
// your TON balance must be > 0.05 to send
msg := wallet.SimpleMessage(tokenWallet.Address(), tlb.MustFromTON("0.05"), transferPayload)
log.Println("sending transaction...")
tx, _, err := w.SendWaitTransaction(ctx, msg)
if err != nil {
panic(err)
}
log.Println("transaction confirmed, hash:", base64.StdEncoding.EncodeToString(tx.Hash))
Исходный код
my_wallet = Wallet(provider=client, mnemonics=my_wallet_mnemonics, version='v4r2')
# for TonCenterClient and LsClient
await my_wallet.transfer_jetton(destination_address='address', jetton_master_address=jetton.address, jettons_amount=1000, fee=0.15)
# for all clients
await my_wallet.transfer_jetton_by_jetton_wallet(destination_address='address', jetton_wallet='your jetton wallet address', jettons_amount=1000, fee=0.1)
Исходный код
from pytoniq import LiteBalancer, WalletV4R2, begin_cell
import asyncio
mnemonics = ["your", "mnemonics", "here"]
async def main():
provider = LiteBalancer.from_mainnet_config(1)
await provider.start_up()
wallet = await WalletV4R2.from_mnemonic(provider=provider, mnemonics=mnemonics)
USER_ADDRESS = wallet.address
JETTON_MASTER_ADDRESS = "EQBlqsm144Dq6SjbPI4jjZvA1hqTIP3CvHovbIfW_t-SCALE"
DESTINATION_ADDRESS = "EQAsl59qOy9C2XL5452lGbHU9bI3l4lhRaopeNZ82NRK8nlA"
USER_JETTON_WALLET = (await provider.run_get_method(address=JETTON_MASTER_ADDRESS,
method="get_wallet_address",
stack=[begin_cell().store_address(USER_ADDRESS).end_cell().begin_parse()]))[0].load_address()
forward_payload = (begin_cell()
.store_uint(0, 32) # TextComment op-code
.store_snake_string("Comment")
.end_cell())
transfer_cell = (begin_cell()
.store_uint(0xf8a7ea5, 32) # Jetton Transfer op-code
.store_uint(0, 64) # query_id
.store_coins(1 * 10**9) # Jetton amount to transfer in nanojetton
.store_address(DESTINATION_ADDRESS) # Destination address
.store_address(USER_ADDRESS) # Response address
.store_bit(0) # Custom payload is None
.store_coins(1) # Ton forward amount in nanoton
.store_bit(1) # Store forward_payload as a reference
.store_ref(forward_payload) # Forward payload
.end_cell())
await wallet.transfer(destination=USER_JETTON_WALLET, amount=int(0.05*1e9), body=transfer_cell)
await provider.close_all()
asyncio.run(main())
Примите Jetton Transfer с разбором комментария
- JS (tonweb)
- Golang
- Python
Исходный код
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});
}
Исходный код
import (
"context"
"fmt"
"log"
"github.com/xssnick/tonutils-go/address"
"github.com/xssnick/tonutils-go/liteclient"
"github.com/xssnick/tonutils-go/tlb"
"github.com/xssnick/tonutils-go/ton"
"github.com/xssnick/tonutils-go/ton/jetton"
"github.com/xssnick/tonutils-go/tvm/cell"
)
const (
MainnetConfig = "https://ton.org/global.config.json"
TestnetConfig = "https://ton.org/global.config.json"
MyWalletAddress = "INSERT-YOUR-HOT-WALLET-ADDRESS"
)
type JettonInfo struct {
address string
decimals int
}
type Jettons struct {
jettonMinter *jetton.Client
jettonWalletAddress string
jettonWallet *jetton.WalletClient
}
func prepare(api ton.APIClientWrapped, jettonsInfo map[string]JettonInfo) (map[string]Jettons, error) {
userAddress := address.MustParseAddr(MyWalletAddress)
block, err := api.CurrentMasterchainInfo(context.Background())
if err != nil {
return nil, err
}
jettons := make(map[string]Jettons)
for name, info := range jettonsInfo {
jettonMaster := jetton.NewJettonMasterClient(api, address.MustParseAddr(info.address))
jettonWallet, err := jettonMaster.GetJettonWallet(context.Background(), userAddress)
if err != nil {
return nil, err
}
jettonUserAddress := jettonWallet.Address()
jettonData, err := api.RunGetMethod(context.Background(), block, jettonUserAddress, "get_wallet_data")
if err != nil {
return nil, err
}
slice := jettonData.MustCell(0).BeginParse()
slice.MustLoadCoins() // skip balance
slice.MustLoadAddr() // skip owneer address
adminAddress := slice.MustLoadAddr()
if adminAddress.String() != info.address {
return nil, fmt.Errorf("jetton minter address from jetton wallet doesnt match config")
}
jettons[name] = Jettons{
jettonMinter: jettonMaster,
jettonWalletAddress: jettonUserAddress.String(),
jettonWallet: jettonWallet,
}
}
return jettons, nil
}
func jettonWalletAddressToJettonName(jettons map[string]Jettons, jettonWalletAddress string) string {
for name, info := range jettons {
if info.jettonWallet.Address().String() == jettonWalletAddress {
return name
}
}
return ""
}
func GetTransferTransactions(orderId string, foundTransfer chan<- *tlb.Transaction) {
jettonsInfo := map[string]JettonInfo{
"jUSDC": {address: "EQB-MPwrd1G6WKNkLz_VnV6WqBDd142KMQv-g1O-8QUA3728", decimals: 6},
"jUSDT": {address: "EQBynBO23ywHy_CgarY9NK9FTz0yDsG82PtcbSTQgGoXwiuA", decimals: 6},
}
client := liteclient.NewConnectionPool()
cfg, err := liteclient.GetConfigFromUrl(context.Background(), MainnetConfig)
if err != nil {
log.Fatalln("get config err: ", err.Error())
}
// connect to lite servers
err = client.AddConnectionsFromConfig(context.Background(), cfg)
if err != nil {
log.Fatalln("connection err: ", err.Error())
}
// initialize ton api lite connection wrapper
api := ton.NewAPIClient(client, ton.ProofCheckPolicySecure).WithRetry()
master, err := api.CurrentMasterchainInfo(context.Background())
if err != nil {
log.Fatalln("get masterchain info err: ", err.Error())
}
// address on which we are accepting payments
treasuryAddress := address.MustParseAddr("EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N")
acc, err := api.GetAccount(context.Background(), master, treasuryAddress)
if err != nil {
log.Fatalln("get masterchain info err: ", err.Error())
}
jettons, err := prepare(api, jettonsInfo)
if err != nil {
log.Fatalln("can't prepare jettons data: ", err.Error())
}
lastProcessedLT := acc.LastTxLT
transactions := make(chan *tlb.Transaction)
go api.SubscribeOnTransactions(context.Background(), treasuryAddress, lastProcessedLT, transactions)
log.Println("waiting for transfers...")
// listen for new transactions from channel
for tx := range transactions {
if tx.IO.In == nil || tx.IO.In.MsgType != tlb.MsgTypeInternal {
// external message - not related to jettons
continue
}
msg := tx.IO.In.Msg
sourceAddress := msg.SenderAddr()
// jetton master contract address check
jettonName := jettonWalletAddressToJettonName(jettons, sourceAddress.String())
if len(jettonName) == 0 {
// unknown or fake jetton transfer
continue
}
if msg.Payload() == nil || msg.Payload() == cell.BeginCell().EndCell() {
// no in_msg body
continue
}
msgBodySlice := msg.Payload().BeginParse()
op := msgBodySlice.MustLoadUInt(32)
if op != 0x7362d09c {
continue // op != transfer_notification
}
// just skip bits
msgBodySlice.MustLoadUInt(64)
amount := msgBodySlice.MustLoadCoins()
msgBodySlice.MustLoadAddr()
payload := msgBodySlice.MustLoadMaybeRef()
payloadOp := payload.MustLoadUInt(32)
if payloadOp == 0 {
log.Println("no text comment in transfer_notification")
continue
}
comment := payload.MustLoadStringSnake()
if comment != orderId {
continue
}
// process transaction
log.Printf("Got %s jetton deposit %d units with text comment %s\n", jettonName, amount, comment)
foundTransfer <- tx
}
}
Исходный код
import asyncio
from pytoniq import LiteBalancer, begin_cell
MY_WALLET_ADDRESS = "EQAsl59qOy9C2XL5452lGbHU9bI3l4lhRaopeNZ82NRK8nlA"
async def parse_transactions(provider: LiteBalancer, transactions):
for transaction in transactions:
if not transaction.in_msg.is_internal:
continue
if transaction.in_msg.info.dest.to_str(1, 1, 1) != MY_WALLET_ADDRESS:
continue
sender = transaction.in_msg.info.src.to_str(1, 1, 1)
value = transaction.in_msg.info.value_coins
if value != 0:
value = value / 1e9
if len(transaction.in_msg.body.bits) < 32:
print(f"TON transfer from {sender} with value {value} TON")
continue
body_slice = transaction.in_msg.body.begin_parse()
op_code = body_slice.load_uint(32)
if op_code != 0x7362D09C:
continue
body_slice.load_bits(64) # skip query_id
jetton_amount = body_slice.load_coins() / 1e9
jetton_sender = body_slice.load_address().to_str(1, 1, 1)
if body_slice.load_bit():
forward_payload = body_slice.load_ref().begin_parse()
else:
forward_payload = body_slice
jetton_master = (
await provider.run_get_method(
address=sender, method="get_wallet_data", stack=[]
)
)[2].load_address()
jetton_wallet = (
(
await provider.run_get_method(
address=jetton_master,
method="get_wallet_address",
stack=[
begin_cell()
.store_address(MY_WALLET_ADDRESS)
.end_cell()
.begin_parse()
],
)
)[0]
.load_address()
.to_str(1, 1, 1)
)
if jetton_wallet != sender:
print("FAKE Jetton Transfer")
continue
if len(forward_payload.bits) < 32:
print(
f"Jetton transfer from {jetton_sender} with value {jetton_amount} Jetton"
)
else:
forward_payload_op_code = forward_payload.load_uint(32)
if forward_payload_op_code == 0:
print(
f"Jetton transfer from {jetton_sender} with value {jetton_amount} Jetton and comment: {forward_payload.load_snake_string()}"
)
else:
print(
f"Jetton transfer from {jetton_sender} with value {jetton_amount} Jetton and unknown payload: {forward_payload} "
)
print(f"Transaction hash: {transaction.cell.hash.hex()}")
print(f"Transaction lt: {transaction.lt}")
async def main():
provider = LiteBalancer.from_mainnet_config(1)
await provider.start_up()
transactions = await provider.get_transactions(address=MY_WALLET_ADDRESS, count=5)
await parse_transactions(provider, transactions)
await provider.close_all()
if __name__ == "__main__":
asyncio.run(main())
SDKs
Вы можете найти список SDK для различных языков (js, python, golang, C#, Rust и т.д.) список здесь.