Переробка TON Jetton
Для чіткого розуміння читач повинен бути знайомий з основними принципами обробки активів, описаними в [розділі обробки платежів] (/develop/dapps/asset-processing/) нашої документації.
Джеттони - це токени в блокчейні 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). Менш формальний огляд архітектури джеттонів, зосереджений на шардингу, можна знайти у нашому блозі анатомія джеттонів.
Також слід пам'ятати, що існує два підходи до роботи з джеттон-виводами:
- [Memo Deposits] (https://github.com/toncenter/examples/blob/main/deposits-jettons.js) - дозволяє вести один депозитний гаманець, а користувачі додають записку, щоб бути ідентифікованими вашою системою. Це означає, що вам не потрібно сканувати весь блокчейн, але це трохи менш зручно для користувачів.
- [Депозити без авізо] (https://github.com/gobicycle/bicycle) - Це рішення також існує, але його складніше інтегрувати. Однак ми можемо допомогти вам з цим, якщо ви віддаєте перевагу цьому шляху. Будь ласка, повідомте нас перед тим, як прийняти рішення про застосування цього підходу.
Jetton Architecture
Стандартизовані токени на TON реалізуються за допомогою набору смарт-контрактів, в тому числі:
- Jetton master смарт-контракт
- Гаманець Jetton смарт-контракти
Головний смарт-контракт Jetton
Головний смарт-контракт джеттона зберігає загальну інформацію про джеттон (включаючи загальну пропозицію, посилання на метадані або самі метадані).
Джеттони з символом ==
TON`` або ті, що містять системні повідомлення, такі як:
ERROR
, SYSTEM
та інші. Переконайтеся, що джеттони відображаються у вашому інтерфейсі таким чином, щоб їх не можна було
змішати з TON-передачами, системними повідомленнями тощо. Іноді навіть символ
, ім'я
та зображення
створюються майже ідентичними до оригіналу з надією ввести користувачів в оману.
Щоб виключити можливість шахрайства для користувачів TON, будь ласка, шукайте оригінальну адресу джеттона (основний договір на джеттон) для конкретних типів джеттонів або перейдіть на офіційний канал проекту в соціальних мережах чи веб-сайт, щоб знайти правильну інформацію. Перевірте активи, щоб виключити можливість шахрайства за допомогою [Tonkeeper ton-assets list] (https://github.com/tonkeeper/ton-assets).
Отримання даних Jetton
Для отримання більш конкретних даних Jetton використовуйте метод get контракту get_jetton_data()
.
Цей метод повертає наступні дані:
Ім'я | Тип | Опис |
---|---|---|
total_supply | int | загальна кількість випущених джеттонів, виміряна в неподільних одиницях. |
"монетний двір". | int | деталізує, чи можна карбувати нові джеттони чи ні. Це значення дорівнює -1 (можна карбувати) або 0 (не можна карбувати). |
адреса_адміністратора | "шматочок | |
`jetton_content | "Cell | дані відповідно до TEP-64, див. сторінку розбору метаданих jetton для отримання додаткової інформації. |
jetton_wallet_code | "Cell |
Ви можете викликати його за допомогою Toncenter API або одного з SDK.
- API
- js
Запустіть метод
jetton/masters
з Toncenter API
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);
Jetton minter
Як згадувалося раніше, джеттони можуть бути як "карбованими", так і "некарбованими".
Якщо вони не карбуються, логіка стає простою - немає можливості карбувати додаткові токени. Для того, щоб вперше покарбувати джеттони, зверніться до сторінки Покарбуйте свій перший джеттон.
Якщо джеттони можна карбувати, в [контракті з майнером] (https://github.com/ton-blockchain/minter-contract/blob/main/contracts/jetton-minter.fc) є спеціальна функція для карбування додаткових джеттонів. Цю функцію можна викликати, відправивши внутрішнє повідомлення
з вказаним опкодом з адреси адміністратора.
Якщо адміністратор джеттона хоче обмежити створення джеттонів, є три способи зробити це:
- Якщо ви не можете або не хочете оновлювати код контракту, адміністратор повинен передати право власності від поточного адміністратора на нульову адресу. Це призведе до того, що контракт залишиться без дійсного адміністратора, і ніхто не зможе карбувати джеттони. Однак, це також унеможливить будь-які зміни в метаданих джеттона.
- Якщо у вас є доступ до вихідного коду і ви можете його змінити, ви можете створити в контракті метод, який встановлює прапорець для переривання будь-якого процесу карбування після його виклику, і додати оператор для перевірки цього прапорця в функцію карбування.
- Якщо ви можете оновити код контракту, ви можете додати обмеження, оновивши код вже розгорнутого контракту.
Смарт-контракт гаманця Jetton
Контракти гаманця Джеттона
використовуються для відправлення, отримання та спалювання джеттонів. Кожен контракт гаманця джеттона зберігає інформацію про баланс гаманця для конкретних користувачів.
В окремих випадках джеттон-гаманці використовуються для окремих власників джеттонів для кожного типу джеттонів.
"Гаманці джеттонів" не слід плутати з гаманцями, призначеними для взаємодії з блокчейном і зберігання тільки активу Toncoin (наприклад, гаманці v3R2, гаманці з високим навантаженням та інші), які відповідають за підтримку і управління тільки певним типом джеттона.
Розгортання гаманця Jetton
При передачі джеттонів
між гаманцями транзакції (повідомлення) вимагають певної суми TON
в якості оплати мережевих платежів і виконання дій відповідно до коду контракту гаманця Jetton.
Це означає, що одержувачу не потрібно розгортати джеттон-гаманець перед отриманням джеттонів.
Гаманець одержувача буде розгорнутий автоматично, якщо відправник має на гаманці достатню кількість TON
для сплати необхідних зборів за газ.
Отримання адрес гаманців Jetton для певного користувача
Щоб отримати адресу
гаманця Jettonза допомогою
адреси власника(адреси гаманця TON),
в
магістерському контракті Jettonпередбачено метод get
getwallet_address(slice_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
Щоб отримати баланс рахунку гаманця, ідентифікаційну інформацію про власника та іншу інформацію, пов'язану з конкретним контрактом джеттон-гаманця, використовуйте метод get get_wallet_data()
в контракті джеттон-гаманця.
Цей метод повертає наступні дані:
Ім'я | Тип |
---|---|
"Баланс | int |
"власник | шматочок |
"Джеттон | шматочок |
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
Відправник -> джеттон-гаманець відправника
. Повідомлення Transfer містить наступні дані:
Ім'я | Тип | Опис |
---|---|---|
query_id | uint64 | Дозволяє програмам зв'язувати між собою три типи повідомлень Передача , Повідомлення про передачу і Перевищення . Для коректного виконання цього процесу рекомендується завжди використовувати унікальний ідентифікатор запиту. |
"сума | монети | Загальна сума "тонни монет", яка буде надіслана у повідомленні. |
"місце призначення | адреса | Адреса нового власника джеттонів |
відповідь_призначення | адреса | Адреса гаманця для повернення монет з повідомленням про надлишок. |
custom_payload | можливо, в камері. | Розмір завжди >= 1 біт. Користувацькі дані (які використовуються джеттон-гаманцем відправника або одержувача для внутрішньої логіки). |
forward_ton_amount | монети | Повинно бути > 0, якщо ви хочете надіслати повідомлення про передачу з переадресованим корисним навантаженням . Це **частина значення mount і повинна бути меншою за mount . |
forward_payload | можливо, в камері. | Розмір завжди >= 1 біт. Якщо перші 32 біти = 0x0, то це просте повідомлення. |
Послання 2''.
Jetton гаманець одержувача -> одержувач
. Повідомлення про переказ. Відправляється тільки якщо сума_переказу_тонн
не нульова. Містить наступні дані:
Ім'я | Тип |
---|---|
query_id | uint64 |
"сума | монети |
"Відправник | адреса |
forward_payload | клітина |
Тут адреса відправника - це адреса "джеттон-гаманця" Аліси.
Послання 2''.
Jetton гаманець одержувача -> Відправник
. Надлишкове тіло повідомлення. Відправляється лише у випадку, якщо після сплати комісій залишилися монети тонни. Містить наступні дані:
Ім'я | Тип |
---|---|
query_id | uint64 |
Детальний опис полів контрактів джеттон-гаманця можна знайти в TEP-74 Jetton standard
опис інтерфейсу.
Надсилайте джеттони з коментарями
Для цього переказу потрібно кілька тонних монет для сплати комісії і, за бажанням, повідомлення про переказ (поставте галочку в полі "Сума переказу").
Щоб надіслати коментар, вам потрібно налаштувати переднє корисне навантаження
. Встановіть перші 32 біти в 0x0 і додайте ваш текст.
Внутрішнє повідомлення forward payload
надсилається у внутрішньому повідомленні transfer notification
. Воно буде згенероване тільки якщо сума переказу
> 0.
Нарешті, щоб отримати повідомлення Excess
, ви повинні налаштувати призначення відповіді
.
Перевірте кращі практики для прикладу "надсилати jettons з коментарями".
Позачергова обробка Jetton
Транзакції TON є незворотними після одного підтвердження. Для кращого користувацького досвіду рекомендується уникати очікування додаткових блоків після завершення транзакцій в блокчейні TON. Детальніше читайте в [Catchain.pdf] (https://docs.ton.org/catchain.pdf#page=3).
Існує два способи прийняття джеттонів:
- в рамках централізованого гарячого гаманця.
- використання гаманця з окремою адресою для кожного окремого користувача.
З міркувань безпеки бажано мати окремі гарячі гаманці для окремих джеттонів (багато гаманців для кожного типу активів).
При обробці коштів також рекомендується передбачити "холодний гаманець" для зберігання надлишкових коштів, які не беруть участі в автоматичних процесах поповнення та зняття коштів.
Додавання нових джеттонів для обробки активів та первинної верифікації
- Знайдіть правильну [адресу смарт-контракту] (/develop/dapps/asset-processing/jettons#jetton-master-smart-contract).
- Отримати метадані.
- Перевірте, чи немає [шахрайства] (/develop/dapps/asset-processing/jettons#jetton-master-smart-contract).
Ідентифікація невідомого Jetton при отриманні повідомлення про переказ
Якщо на ваш гаманець надійшло повідомлення про переказ на невідомий Jetton, це означає, що ваш гаманець було створено для зберігання конкретного Jetton.
Адреса відправника внутрішнього повідомлення, що містить тіло Transfer notification
- це адреса нового гаманця Jetton.
Її не слід плутати з полем "відправник" у тілі "Повідомлення про переказ" (/develop/dapps/asset-processing/jettons#jetton-wallets-communication-oview).
- Отримайте головну адресу Jetton для нового гаманця Jetton за допомогою отримання даних гаманця.
- Отримайте адресу гаманця Jetton для вашої адреси гаманця (як власника) за допомогою генерального договору Jetton: [Як отримати адресу гаманця Jetton для даного користувача] (#retrieving-jetton-wallet-addresses-for-a-given-user)
- Порівняйте адресу, повернуту головним контрактом, і фактичну адресу токена гаманця. Якщо вони збігаються, це ідеально. Якщо ні, то, швидше за все, ви отримали шахрайський токен, який є підробкою.
- Отримати метадані Jetton: Як отримати метадані Jetton.
- Перевірте поля "символ" та "ім'я" на наявність ознак шахрайства. За необхідності попередьте користувача. [Додавання нового джеттона для обробки та початкової перевірки] (#adding-new-jettons-for-asset-processing-and-initial-verification).
Прийом джеттонів від користувачів через централізований гаманець
Для того, щоб запобігти вузькому місцю у вхідних транзакціях на один гаманець, пропонується приймати депозити на декілька гаманців і розширювати кількість цих гаманців за потреби.
У цьому сценарії платіжний сервіс створює унікальний ідентифікатор мемо для кожного відправника, який розкриває адресу централізованого гаманця та суми, що надсилаються. Відправник надсилає токени на вказану централізовану адресу з обов'язковим мемо в коментарі.
Плюси цього методу: цей метод дуже простий, оскільки немає додаткових комісій при прийомі токенів, і вони отримуються безпосередньо в гарячому гаманці.
Недоліки цього методу: цей метод вимагає, щоб усі користувачі додавали коментар до переказу, що може призвести до більшої кількості помилок при переказі (забуті примітки, неправильні примітки тощо), а отже, до більшого навантаження на працівників служби підтримки.
Приклади з Tonweb:
- [Прийом депозитів Jetton на індивідуальний гаманець HOT з коментарями (пам'ятка)] (https://github.com/toncenter/examples/blob/main/deposits-jettons.js)
- Приклад виведення коштів з Jettons
Підготовка
- Підготуйте список прийнятих джеттонів (основні адреси джеттонів).
- Розгорніть гарячий гаманець (використовуючи v3R2, якщо не очікується виведення коштів через Jetton; highload v3 - якщо очікується виведення коштів через Jetton). Розгортання гаманця.
- Виконайте тестовий переказ Jetton, використовуючи адресу гарячого гаманця для ініціалізації гаманця.
Обробка вхідних джеттонів
- Завантажте список прийнятих джеттонів.
- [Отримати адресу гаманця Jetton] (#retrieving-jetton-wallet-addresses-for-a-given-user) для вашого розгорнутого гарячого гаманця.
- Отримайте головну адресу Jetton для кожного гаманця Jetton за допомогою отримання даних гаманця.
- Порівняйте адреси генеральних контрактів Jetton з кроку 1 та кроку 3 (безпосередньо вище). Якщо адреси не збігаються, необхідно повідомити про помилку перевірки адреси Jetton.
- Отримайте список останніх необроблених транзакцій за допомогою облікового запису гарячого гаманця та ітераційно перегляньте його (сортуючи кожну транзакцію по черзі). Див: [Перевірка транзакцій за контрактом] (https://docs.ton.org/develop/dapps/asset-processing/#checking-contracts-transactions).
- Перевірити вхідне повідомлення (in_msg) на наявність транзакцій і отримати адресу джерела з вхідного повідомлення. Приклад Tonweb
- Якщо адреса джерела збігається з адресою в гаманці Jetton, то необхідно продовжити обробку транзакції. Якщо ні, то пропустіть обробку транзакції і перевірте наступну транзакцію.
- Переконайтеся, що тіло повідомлення не порожнє і що перші 32 біти повідомлення збігаються з оперативним кодом
transfer notification
0x7362d09c
. Приклад Tonweb Якщо тіло повідомлення порожнє або операційний код невірний - пропустіть транзакцію. - Прочитайте інші дані тіла повідомлення, зокрема
query_id
,amount
,sender
,forward_payload
. Макети повідомлень Jetton контрактів, Приклад Tonweb - Спробуйте отримати текстові коментарі з даних
forward_payload
. Перші 32 біти мають збігатися з опкодом текстового коментаря0x00000000
, а решта - з текстом у кодуванні 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) на основі seed'а гарячого гаманця. [Створення субгаманця в Tonweb] (#creating-a-subwallet-in-tonweb)
- Адреса отримання може бути надана користувачеві як адреса, що використовується для депозитів Jetton (це адреса власника депозитного гаманця Jetton). Ініціалізація гаманця не потрібна, це можна зробити при знятті Джеттонів з депозиту.
- Для цієї адреси необхідно обчислити адресу гаманця Jetton через майстер-контракт Jetton. [Як отримати адресу гаманця Jetton для заданого користувача] (#retrieving-jetton-wallet-addresses-for-a-given-user).
- Додайте адресу гаманця Jetton до пулу адрес для моніторингу транзакцій і збережіть адресу субгаманця.
Обробка транзакцій
Транзакції TON є незворотними після одного підтвердження. Для кращого користувацького досвіду рекомендується уникати очікування додаткових блоків після завершення транзакцій в блокчейні TON. Детальніше читайте в [Catchain.pdf] (https://docs.ton.org/catchain.pdf#page=3).
Не завжди можна визначити точну кількість отриманих джеттонів з повідомлення, оскільки гаманці Jetton можуть не надсилати повідомлення про "повідомлення про переказ", "надлишки" та "внутрішній переказ". Вони не стандартизовані. Це означає, що немає гарантії, що повідомлення про внутрішній переказ можна буде розшифрувати.
Тому для визначення суми, що надійшла на гаманець, необхідно запитувати баланси за допомогою методу get. Для отримання ключових даних при запиті балансів використовуються блоки відповідно до стану рахунку для конкретного блоку в ланцюжку. [Підготовка до прийому блоків за допомогою Tonweb] (https://github.com/toncenter/tonweb/blob/master/src/test-block-subscribe.js).
Цей процес відбувається наступним чином:
- Підготовка до приймання блоків (підготовка системи до приймання нових блоків).
- Отримати новий блок і зберегти ідентифікатор попереднього блоку.
- Отримуйте транзакції з блоків.
- Фільтр транзакцій, що використовуються тільки з адресами з пулу депозитних гаманців Jetton.
- Декодуйте повідомлення за допомогою тіла
transfer notification
, щоб отримати детальнішу інформацію, зокрема адресу відправника, кількість джеттонів та коментар. (Дивіться: Обробка вхідних джеттонів) - Якщо на рахунку
є хоча б одна транзакція з нерозшифрованими вихідними повідомленнями (тіло повідомлення не містить операційних кодів для
transfer notification
та операційних кодів дляexcesses
) або без вихідних повідомлень, то баланс Jetton повинен бути запитаний методом get для поточного блоку, а для розрахунку різниці балансів використовується попередній блок . Тепер загальна зміна балансу на рахунку відображається за допомогою транзакцій, що проводяться в межах блоку. - Як ідентифікатор для неідентифікованої передачі джеттонів (без "повідомлення про передачу") можна використовувати дані транзакції , якщо є одна така транзакція, або дані блоку (якщо в блоці присутні декілька).
- Тепер необхідно перевірити, чи правильний баланс депозиту. Якщо баланс депозиту достатній для ініціювання переказу між гарячим гаманцем та існуючим гаманцем Jetton, необхідно зняти джеттони, щоб переконатися, що баланс гаманця зменшився.
- Почніть з кроку 2 і повторіть весь процес.
Зняття коштів з депозитів
Не слід здійснювати перекази з депозиту на гарячий гаманець при кожному поповненні депозиту , оскільки за операцію переказу стягується комісія в ТОНах (сплачується в тарифах на газ у мережі). Важливо визначити певну мінімальну суму джеттонів, яка необхідна для того, щоб переказ на був виправданим (і, відповідно, поповнення депозиту).
За замовчуванням власники депозитних гаманців 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. Дляtransfer
, користувач повинен вказати адресу гарячого гаманця в якостіпризначення
іпризначення відповіді
. Для спрощення ідентифікації переказу можна додати текстовий коментар. - Перевірити доставку Джеттона можна за допомогою адреси депозиту на адресу гарячого гаманця за посиланням , враховуючи [інформацію про обробку вхідних Джеттонів, яку можна знайти тут] (#processing-incoming-jettons).
Виведення коштів з Jetton
Нижче ви знайдете покрокову інструкцію, як обробляти виведення коштів з джеттона.
Щоб вивести джеттони, гаманець надсилає повідомлення з тілом "переказу" на відповідний гаманець Jetton.
Потім гаманець Jetton відправляє джеттони одержувачу. Для того, щоб сповіщення про переказ надійшло, важливо додати деяку кількість TON
в якості forward_ton_amount
(і необов'язковий коментар до forward_payload
).
Див: Макети повідомлень джеттон-контрактів
Підготовка
- Підготуйте список джеттонів для виведення коштів: [Додавання нових джеттонів для обробки та початкової верифікації] (#adding-new-jettons-for-asset-processing-and-initial-verification)
- Розгортання гарячого гаманця розпочато. Рекомендується 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 або
використовуйте метод API Toncenter
/getTransactions
. - Подивіться вихідні повідомлення в акаунті.
- Якщо існує повідомлення з операторним кодом
transfer
, то його слід декодувати, щоб отримати значенняquery_id
. Отриманийquery_id
потрібно позначити як успішно відправлений. - Якщо час, необхідний для обробки поточної відсканованої транзакції, перевищує
час закінчення терміну дії, а вихідне повідомлення з заданим
query_id
не знайдено, то запит слід (це необов'язково) позначити як прострочений і безпечно відправити його повторно. - Шукайте вхідні повідомлення в акаунті.
- Якщо існує повідомлення, яке використовує операційний код
excesses
, його слід розшифрувати і отримати значенняquery_id
. Знайденийquery_id
слід позначити як успішно доставлений. - Перейдіть до кроку 5. Прострочені запити, які не були успішно відправлені, слід повернути до списку відкликання.
Обробка в ланцюжку Jetton
Зазвичай, щоб приймати та обробляти джеттони, обробник повідомлень, відповідальний за внутрішні повідомлення, використовує операційний код op=0x7362d09c
.
Транзакції TON є незворотними після одного підтвердження. Для кращого користувацького досвіду рекомендується уникати очікування додаткових блоків після завершення транзакцій в блокчейні TON. Детальніше читайте в [Catchain.pdf] (https://docs.ton.org/catchain.pdf#page=3).
Рекомендації щодо обробки в ланцюжку
Нижче наведено "список рекомендацій", які необхідно враховувати при проведенні обробки джеттону в ланцюжку:
- Ідентифікувати вхідні джеттони за типом гаманця, а не за основним контрактом Jetton. Іншими словами, ваш контракт повинен взаємодіяти (отримувати і відправляти повідомлення) з конкретним гаманцем джеттона (а не з якимось невідомим гаманцем, що використовує конкретний основний контракт Jetton).
- Під час зв'язування гаманця Jetton та головного контракту Jetton переконайтеся, що цей зв'язок є двонаправленим, тобто гаманець розпізнає головний контракт і навпаки. Наприклад, якщо ваша контрактна система отримує повідомлення від гаманця Jetton (який вважає свій MySuperJetton головним контрактом), інформація про переказ повинна відображатися користувачеві, перш ніж показати символ, ім'я та зображення контракту MySuperJetton, переконайтеся, що гаманець MySuperJetton використовує правильну контрактну систему. У свою чергу, якщо ваша контрактна система з якихось причин повинна відправляти джеттони з використанням майстер-контрактів MySuperJetton або MySuperJetton, перевірте, що гаманець X використовує ті ж параметри контракту, що і гаманець MySuperJetton. Крім того, перед тим, як надсилати запит на переказ на X, переконайтеся, що він визнає MySuperJetton своїм головним.
- Справжня сила децентралізованих фінансів (DeFi) базується на можливості накладати протоколи один на одного, як кубики лего. Наприклад, скажімо, джеттон А обмінюється на джеттон Б, який, в свою чергу, використовується як кредитне плече в рамках протоколу кредитування (коли користувач надає ліквідність), який потім використовується для купівлі NFT .... і так далі. Таким чином, розглянемо, як контракт може обслуговувати не тільки позамережевих користувачів, але й мережевих суб'єктів, прикріплюючи токенізовану вартість до повідомлення про переказ, додаючи користувацьке корисне навантаження, яке може бути надіслане разом із повідомленням про переказ.
- **Майте на увазі, що не всі контракти дотримуються однакових стандартів. На жаль, деякі джеттони можуть бути ворожими (з використанням векторів атак) і створюватися з єдиною метою - атакувати користувачів, які нічого не підозрюють. З метою безпеки, якщо протокол, про який йде мова, складається з багатьох контрактів, не створюйте велику кількість однотипних джеттон-гаманців. Зокрема, не надсилайте джеттони всередині протоколу між депозитним контрактом, контрактом на зберігання, контрактом на обліковий запис користувача тощо. Зловмисники можуть навмисно втрутитися в логіку контракту, підробивши повідомлення про переказ, суми джеттонів або параметри корисного навантаження. Зменшіть ймовірність атак, використовуючи лише один гаманець у системі на один джеттон (для всіх депозитів і зняття коштів).
- Також часто корисно створювати субконтракти для кожного окремого джеттона, щоб зменшити ймовірність підміни адреси (наприклад, коли повідомлення про переказ надсилається на джеттон B з використанням контракту, призначеного для джеттона A).
- Наполегливо рекомендується працювати з неподільними одиницями джеттон на рівні контрактів. Логіка, пов'язана з десятковою системою числення, зазвичай використовується для покращення користувацького інтерфейсу (UI) diplay і не пов'язана з числовим веденням записів у ланцюжку.
Щоб дізнатися більше про [Безпечне програмування смарт-контрактів у FunC від CertiK] (https://blog.ton.org/secure-smart-contract-programming-in-func), ознайомтеся з цим ресурсом. Рекомендується, щоб розробники обробляли всі винятки смарт-контрактів, щоб вони ніколи не були пропущені під час розробки програми.
Рекомендації щодо обробки гаманця Jetton
Як правило, всі процедури верифікації, що використовуються для обробки джеттонів, підходять і для гаманців. Для обробки джеттон-гаманців наші найважливіші рекомендації наступні:
- Коли гаманець отримує повідомлення про переказ з невідомого джеттон-гаманця, дуже важливо довіряти джеттон-гаманцю та його головній адресі, оскільки він може бути зловмисною підробкою. Щоб захистити себе, перевірте Jetton Master (основний договір), використовуючи надану адресу, щоб переконатися, що ваші процеси верифікації визнають джеттон-гаманець легітимним. Після того, як ви довіритеся гаманцю і він буде підтверджений як легітимний, ви можете дозволити йому доступ до залишків на ваших рахунках та інших даних у гаманці. Якщо Jetton Master не розпізнає цей гаманець, рекомендується взагалі не ініціювати і не розкривати свої джеттон-перекази, а показувати тільки вхідні перекази TON (Toncoin, прикріплені до повідомлень про перекази).
- На практиці, якщо користувач хоче взаємодіяти з джеттоном, а не з джеттон-гаманцем. Іншими словами, користувачі відправляють wTON/oUSDT/jUSDT, jUSDC, jDAI замість
EQAjN...
/EQBLE...
тощо. Часто це означає, що коли користувач ініціює джеттон-переказ, гаманець запитує відповідного джеттон-майстра, який джеттон-гаманець (що належить користувачеві) повинен ініціювати запит на переказ. Важливо ніколи не довіряти сліпо** цим даним від майстра (майстер-контракту). Перш ніж надсилати запит на переказ на джеттон-гаманець, завжди переконайтеся, що джеттон-гаманець дійсно належить тому майстру джеттонів, на якого він посилається. - **Майте на увазі, що ворожі Jetton-майстри/jetton-гаманці з часом можуть змінювати свої гаманці/майстри. Тому вкрай важливо, щоб користувачі проявляли належну обачність і перевіряли легітимність будь-яких гаманців, з якими вони взаємодіють, перед кожним використанням.
- **Завжди переконайтеся, що ви відображаєте джеттони в інтерфейсі таким чином, щоб вони не змішувалися з TON-переказами, системними сповіщеннями тощо. Навіть параметри "символ", "ім'я" та "зображення" можуть бути створені таким чином, щоб вводити в оману користувачів, роблячи їх потенційними жертвами шахрайства. Було зафіксовано кілька випадків, коли шкідливі джеттони використовувалися для імітації 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())
SDK
Список SDK для різних мов (js, python, golang, C#, Rust і т.д.) можна знайти тут.