Перейти до основного вмісту

Переробка 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 реалізуються за допомогою набору смарт-контрактів, в тому числі:


contracts scheme

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

Головний смарт-контракт джеттона зберігає загальну інформацію про джеттон (включаючи загальну пропозицію, посилання на метадані або самі метадані).

Остерігайтеся шахрайства з Jetton

Джеттони з символом ==TON`` або ті, що містять системні повідомлення, такі як: ERROR, SYSTEM та інші. Переконайтеся, що джеттони відображаються у вашому інтерфейсі таким чином, щоб їх не можна було змішати з TON-передачами, системними повідомленнями тощо. Іноді навіть символ, ім'я та зображення створюються майже ідентичними до оригіналу з надією ввести користувачів в оману.

Щоб виключити можливість шахрайства для користувачів TON, будь ласка, шукайте оригінальну адресу джеттона (основний договір на джеттон) для конкретних типів джеттонів або перейдіть на офіційний канал проекту в соціальних мережах чи веб-сайт, щоб знайти правильну інформацію. Перевірте активи, щоб виключити можливість шахрайства за допомогою [Tonkeeper ton-assets list] (https://github.com/tonkeeper/ton-assets).

Отримання даних Jetton

Для отримання більш конкретних даних Jetton використовуйте метод get контракту get_jetton_data().

Цей метод повертає наступні дані:

Ім'яТипОпис
total_supplyintзагальна кількість випущених джеттонів, виміряна в неподільних одиницях.
"монетний двір".intдеталізує, чи можна карбувати нові джеттони чи ні. Це значення дорівнює -1 (можна карбувати) або 0 (не можна карбувати).
адреса_адміністратора"шматочок
`jetton_content"Cellдані відповідно до TEP-64, див. сторінку розбору метаданих jetton для отримання додаткової інформації.
jetton_wallet_code"Cell

Ви можете викликати його за допомогою Toncenter API або одного з SDK.

Запустіть метод jetton/masters з Toncenter API

Jetton minter

Як згадувалося раніше, джеттони можуть бути як "карбованими", так і "некарбованими".

Якщо вони не карбуються, логіка стає простою - немає можливості карбувати додаткові токени. Для того, щоб вперше покарбувати джеттони, зверніться до сторінки Покарбуйте свій перший джеттон.

Якщо джеттони можна карбувати, в [контракті з майнером] (https://github.com/ton-blockchain/minter-contract/blob/main/contracts/jetton-minter.fc) є спеціальна функція для карбування додаткових джеттонів. Цю функцію можна викликати, відправивши внутрішнє повідомлення з вказаним опкодом з адреси адміністратора.

Якщо адміністратор джеттона хоче обмежити створення джеттонів, є три способи зробити це:

  1. Якщо ви не можете або не хочете оновлювати код контракту, адміністратор повинен передати право власності від поточного адміністратора на нульову адресу. Це призведе до того, що контракт залишиться без дійсного адміністратора, і ніхто не зможе карбувати джеттони. Однак, це також унеможливить будь-які зміни в метаданих джеттона.
  2. Якщо у вас є доступ до вихідного коду і ви можете його змінити, ви можете створити в контракті метод, який встановлює прапорець для переривання будь-якого процесу карбування після його виклику, і додати оператор для перевірки цього прапорця в функцію карбування.
  3. Якщо ви можете оновити код контракту, ви можете додати обмеження, оновивши код вже розгорнутого контракту.

Смарт-контракт гаманця Jetton

Контракти гаманця Джеттона використовуються для відправлення, отримання та спалювання джеттонів. Кожен контракт гаманця джеттона зберігає інформацію про баланс гаманця для конкретних користувачів. В окремих випадках джеттон-гаманці використовуються для окремих власників джеттонів для кожного типу джеттонів.

"Гаманці джеттонів" не слід плутати з гаманцями, призначеними для взаємодії з блокчейном і зберігання тільки активу Toncoin (наприклад, гаманці v3R2, гаманці з високим навантаженням та інші), які відповідають за підтримку і управління тільки певним типом джеттона.

Розгортання гаманця Jetton

При передачі джеттонів між гаманцями транзакції (повідомлення) вимагають певної суми TON в якості оплати мережевих платежів і виконання дій відповідно до коду контракту гаманця Jetton. Це означає, що одержувачу не потрібно розгортати джеттон-гаманець перед отриманням джеттонів. Гаманець одержувача буде розгорнутий автоматично, якщо відправник має на гаманці достатню кількість TON для сплати необхідних зборів за газ.

Отримання адрес гаманців Jetton для певного користувача

Щоб отримати адресу гаманця Jettonза допомогоюадреси власника(адреси гаманця TON), вмагістерському контракті Jettonпередбачено метод getgetwallet_address(slice_addressвласника)`.

Запустіть get_wallet_address(slice owner_address) за допомогою методу /runGetMethod з Toncenter API.

Отримання даних для конкретного гаманця Jetton

Щоб отримати баланс рахунку гаманця, ідентифікаційну інформацію про власника та іншу інформацію, пов'язану з конкретним контрактом джеттон-гаманця, використовуйте метод get get_wallet_data() в контракті джеттон-гаманця.

Цей метод повертає наступні дані:

Ім'яТип
"Балансint
"власникшматочок
"Джеттоншматочок
jetton_wallet_codeклітина

Використовуйте метод /jetton/wallets get з Toncenter API, щоб отримати раніше декодовані дані jetton-гаманців.

Огляд комунікації гаманців Jetton

Зв'язок між гаманцями Jetton і TON відбувається в наступній послідовності:

Повідомлення 0

Відправник -> джеттон-гаманець відправника. Повідомлення Transfer містить наступні дані:

Ім'яТипОпис
query_iduint64Дозволяє програмам зв'язувати між собою три типи повідомлень Передача, Повідомлення про передачу і Перевищення. Для коректного виконання цього процесу рекомендується завжди використовувати унікальний ідентифікатор запиту.
"сумамонетиЗагальна сума "тонни монет", яка буде надіслана у повідомленні.
"місце призначенняадресаАдреса нового власника джеттонів
відповідь_призначенняадресаАдреса гаманця для повернення монет з повідомленням про надлишок.
custom_payloadможливо, в камері.Розмір завжди >= 1 біт. Користувацькі дані (які використовуються джеттон-гаманцем відправника або одержувача для внутрішньої логіки).
forward_ton_amountмонетиПовинно бути > 0, якщо ви хочете надіслати повідомлення про передачу з переадресованим корисним навантаженням. Це **частина значення mount і повинна бути меншою за mount.
forward_payloadможливо, в камері.Розмір завжди >= 1 біт. Якщо перші 32 біти = 0x0, то це просте повідомлення.

Послання 2''.

Jetton гаманець одержувача -> одержувач. Повідомлення про переказ. Відправляється тільки якщо сума_переказу_тонн не нульова. Містить наступні дані:

Ім'яТип
query_iduint64
"сумамонети
"Відправникадреса
forward_payloadклітина

Тут адреса відправника - це адреса "джеттон-гаманця" Аліси.

Послання 2''.

Jetton гаманець одержувача -> Відправник. Надлишкове тіло повідомлення. Відправляється лише у випадку, якщо після сплати комісій залишилися монети тонни. Містить наступні дані:

Ім'яТип
query_iduint64
Джеттони стандартні

Детальний опис полів контрактів джеттон-гаманця можна знайти в 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).

Існує два способи прийняття джеттонів:

  • в рамках централізованого гарячого гаманця.
  • використання гаманця з окремою адресою для кожного окремого користувача.

З міркувань безпеки бажано мати окремі гарячі гаманці для окремих джеттонів (багато гаманців для кожного типу активів).

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

Додавання нових джеттонів для обробки активів та первинної верифікації

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

Ідентифікація невідомого Jetton при отриманні повідомлення про переказ

Якщо на ваш гаманець надійшло повідомлення про переказ на невідомий Jetton, це означає, що ваш гаманець було створено для зберігання конкретного Jetton.

Адреса відправника внутрішнього повідомлення, що містить тіло Transfer notification - це адреса нового гаманця Jetton. Її не слід плутати з полем "відправник" у тілі "Повідомлення про переказ" (/develop/dapps/asset-processing/jettons#jetton-wallets-communication-oview).

  1. Отримайте головну адресу Jetton для нового гаманця Jetton за допомогою отримання даних гаманця.
  2. Отримайте адресу гаманця Jetton для вашої адреси гаманця (як власника) за допомогою генерального договору Jetton: [Як отримати адресу гаманця Jetton для даного користувача] (#retrieving-jetton-wallet-addresses-for-a-given-user)
  3. Порівняйте адресу, повернуту головним контрактом, і фактичну адресу токена гаманця. Якщо вони збігаються, це ідеально. Якщо ні, то, швидше за все, ви отримали шахрайський токен, який є підробкою.
  4. Отримати метадані Jetton: Як отримати метадані Jetton.
  5. Перевірте поля "символ" та "ім'я" на наявність ознак шахрайства. За необхідності попередьте користувача. [Додавання нового джеттона для обробки та початкової перевірки] (#adding-new-jettons-for-asset-processing-and-initial-verification).

Прийом джеттонів від користувачів через централізований гаманець

інформація

Для того, щоб запобігти вузькому місцю у вхідних транзакціях на один гаманець, пропонується приймати депозити на декілька гаманців і розширювати кількість цих гаманців за потреби.

У цьому сценарії платіжний сервіс створює унікальний ідентифікатор мемо для кожного відправника, який розкриває адресу централізованого гаманця та суми, що надсилаються. Відправник надсилає токени на вказану централізовану адресу з обов'язковим мемо в коментарі.

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

Недоліки цього методу: цей метод вимагає, щоб усі користувачі додавали коментар до переказу, що може призвести до більшої кількості помилок при переказі (забуті примітки, неправильні примітки тощо), а отже, до більшого навантаження на працівників служби підтримки.

Приклади з Tonweb:

  1. [Прийом депозитів Jetton на індивідуальний гаманець HOT з коментарями (пам'ятка)] (https://github.com/toncenter/examples/blob/main/deposits-jettons.js)
  2. Приклад виведення коштів з Jettons

Підготовка

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

Обробка вхідних джеттонів

  1. Завантажте список прийнятих джеттонів.
  2. [Отримати адресу гаманця Jetton] (#retrieving-jetton-wallet-addresses-for-a-given-user) для вашого розгорнутого гарячого гаманця.
  3. Отримайте головну адресу Jetton для кожного гаманця Jetton за допомогою отримання даних гаманця.
  4. Порівняйте адреси генеральних контрактів Jetton з кроку 1 та кроку 3 (безпосередньо вище). Якщо адреси не збігаються, необхідно повідомити про помилку перевірки адреси Jetton.
  5. Отримайте список останніх необроблених транзакцій за допомогою облікового запису гарячого гаманця та ітераційно перегляньте його (сортуючи кожну транзакцію по черзі). Див: [Перевірка транзакцій за контрактом] (https://docs.ton.org/develop/dapps/asset-processing/#checking-contracts-transactions).
  6. Перевірити вхідне повідомлення (in_msg) на наявність транзакцій і отримати адресу джерела з вхідного повідомлення. Приклад Tonweb
  7. Якщо адреса джерела збігається з адресою в гаманці Jetton, то необхідно продовжити обробку транзакції. Якщо ні, то пропустіть обробку транзакції і перевірте наступну транзакцію.
  8. Переконайтеся, що тіло повідомлення не порожнє і що перші 32 біти повідомлення збігаються з оперативним кодом transfer notification 0x7362d09c. Приклад Tonweb Якщо тіло повідомлення порожнє або операційний код невірний - пропустіть транзакцію.
  9. Прочитайте інші дані тіла повідомлення, зокрема query_id, amount, sender, forward_payload. Макети повідомлень Jetton контрактів, Приклад Tonweb
  10. Спробуйте отримати текстові коментарі з даних forward_payload. Перші 32 біти мають збігатися з опкодом текстового коментаря 0x00000000, а решта - з текстом у кодуванні UTF-8. Приклад Tonweb
  11. Якщо дані forward_payload порожні або код операції невірний - пропустити транзакцію.
  12. Порівняйте отриманий коментар зі збереженими нотатками. Якщо вони збігаються (ідентифікація користувача завжди можлива) - відправляйте переказ.
  13. Почніть з кроку 5 і повторюйте процес, поки не пройдете весь список транзакцій.

Прийом джеттонів з депозитних адрес користувачів

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

Оскільки гарячий гаманець може використовувати один гаманець Jetton для кожного типу Jetton, необхідно створити декілька гаманців для ініціювання депозитів. Для того, щоб створити велику кількість гаманців, але при цьому керувати ними за допомогою однієї ключової фрази (або приватного ключа), необхідно при створенні гаманця вказувати різний "subwallet_id". На TON функціонал, необхідний для створення субгаманця, підтримується гаманцями версії v3 і вище.

Створення субгаманця в Tonweb

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

Підготовка

  1. [Підготувати список прийнятих джеттонів] (#додавання нових джеттонів для обробки активів та початкової перевірки).
  2. Розгорніть гарячий гаманець (використовуючи v3R2, якщо не очікується виведення коштів через Jetton; highload v3 - якщо очікується виведення коштів через Jetton). Розгортання гаманця.

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

  1. Прийняти запит на створення нового депозиту для користувача.
  2. Згенеруйте адресу нового субгаманця (v3R2) на основі seed'а гарячого гаманця. [Створення субгаманця в Tonweb] (#creating-a-subwallet-in-tonweb)
  3. Адреса отримання може бути надана користувачеві як адреса, що використовується для депозитів Jetton (це адреса власника депозитного гаманця Jetton). Ініціалізація гаманця не потрібна, це можна зробити при знятті Джеттонів з депозиту.
  4. Для цієї адреси необхідно обчислити адресу гаманця Jetton через майстер-контракт Jetton. [Як отримати адресу гаманця Jetton для заданого користувача] (#retrieving-jetton-wallet-addresses-for-a-given-user).
  5. Додайте адресу гаманця 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).

Цей процес відбувається наступним чином:

  1. Підготовка до приймання блоків (підготовка системи до приймання нових блоків).
  2. Отримати новий блок і зберегти ідентифікатор попереднього блоку.
  3. Отримуйте транзакції з блоків.
  4. Фільтр транзакцій, що використовуються тільки з адресами з пулу депозитних гаманців Jetton.
  5. Декодуйте повідомлення за допомогою тіла transfer notification, щоб отримати детальнішу інформацію, зокрема адресу відправника, кількість джеттонів та коментар. (Дивіться: Обробка вхідних джеттонів)
  6. Якщо на рахунку є хоча б одна транзакція з нерозшифрованими вихідними повідомленнями (тіло повідомлення не містить операційних кодів для transfer notification та операційних кодів для excesses) або без вихідних повідомлень, то баланс Jetton повинен бути запитаний методом get для поточного блоку, а для розрахунку різниці балансів використовується попередній блок . Тепер загальна зміна балансу на рахунку відображається за допомогою транзакцій, що проводяться в межах блоку.
  7. Як ідентифікатор для неідентифікованої передачі джеттонів (без "повідомлення про передачу") можна використовувати дані транзакції , якщо є одна така транзакція, або дані блоку (якщо в блоці присутні декілька).
  8. Тепер необхідно перевірити, чи правильний баланс депозиту. Якщо баланс депозиту достатній для ініціювання переказу між гарячим гаманцем та існуючим гаманцем Jetton, необхідно зняти джеттони, щоб переконатися, що баланс гаманця зменшився.
  9. Почніть з кроку 2 і повторіть весь процес.

Зняття коштів з депозитів

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

За замовчуванням власники депозитних гаманців Jetton не ініціалізуються. Це пов'язано з тим, що не існує заздалегідь визначеної вимоги сплачувати комісію за зберігання. Депозитні гаманці Jetton можуть бути розгорнуті при відправці повідомлень з тілом transfer, яке потім може бути негайно знищене. Для цього інженер повинен використовувати спеціальний механізм відправки повідомлень: 128 + 32.

  1. Отримати список депозитів, позначених для виведення на гарячий гаманець
  2. Отримати збережені адреси власників для кожного депозиту
  3. Потім повідомлення надсилаються на адресу кожного власника (шляхом об'єднання декількох таких повідомлень в пакет) з високонавантаженого гаманця з прикріпленою сумою TON Jetton. Вона визначається шляхом додавання комісій за ініціалізацію гаманця v3R2* комісій за відправку повідомлення з тілом transfer + довільної суми TON, пов'язаної з forward_ton_amount (якщо необхідно). Приєднана сума TON визначається шляхом додавання комісії за ініціалізацію гаманця v3R2 (значення) + комісії за відправлення повідомлення з тілом transfer (значення) + довільна сума TON для forward_ton_amount (значення) (за необхідності).
  4. Коли баланс за адресою стає ненульовим, статус акаунта змінюється. Зачекайте кілька секунд і перевірте статус акаунта, незабаром він зміниться зі стану nonexists на uninit.
  5. Для кожної адреси власника (зі статусом uninit) необхідно відправити зовнішнє повідомлення з гаманцем v3R2 init і тіло з повідомленням transfer для поповнення гаманця Jetton = 128 + 32. Для transfer, користувач повинен вказати адресу гарячого гаманця в якості призначення і призначення відповіді. Для спрощення ідентифікації переказу можна додати текстовий коментар.
  6. Перевірити доставку Джеттона можна за допомогою адреси депозиту на адресу гарячого гаманця за посиланням , враховуючи [інформацію про обробку вхідних Джеттонів, яку можна знайти тут] (#processing-incoming-jettons).

Виведення коштів з Jetton

Важливо

Нижче ви знайдете покрокову інструкцію, як обробляти виведення коштів з джеттона.

Щоб вивести джеттони, гаманець надсилає повідомлення з тілом "переказу" на відповідний гаманець Jetton. Потім гаманець Jetton відправляє джеттони одержувачу. Для того, щоб сповіщення про переказ надійшло, важливо додати деяку кількість TON в якості forward_ton_amount (і необов'язковий коментар до forward_payload). Див: Макети повідомлень джеттон-контрактів

Підготовка

  1. Підготуйте список джеттонів для виведення коштів: [Додавання нових джеттонів для обробки та початкової верифікації] (#adding-new-jettons-for-asset-processing-and-initial-verification)
  2. Розгортання гарячого гаманця розпочато. Рекомендується Highload v3. Розгортання гаманця
  3. Здійсніть переказ Jetton, використовуючи адресу гарячого гаманця, щоб ініціалізувати гаманець Jetton і поповнити його баланс.

Обробка зняття коштів

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

Обробка в ланцюжку Jetton

Зазвичай, щоб приймати та обробляти джеттони, обробник повідомлень, відповідальний за внутрішні повідомлення, використовує операційний код op=0x7362d09c.

Підтвердження транзакції

Транзакції TON є незворотними після одного підтвердження. Для кращого користувацького досвіду рекомендується уникати очікування додаткових блоків після завершення транзакцій в блокчейні TON. Детальніше читайте в [Catchain.pdf] (https://docs.ton.org/catchain.pdf#page=3).

Рекомендації щодо обробки в ланцюжку

Нижче наведено "список рекомендацій", які необхідно враховувати при проведенні обробки джеттону в ланцюжку:

  1. Ідентифікувати вхідні джеттони за типом гаманця, а не за основним контрактом Jetton. Іншими словами, ваш контракт повинен взаємодіяти (отримувати і відправляти повідомлення) з конкретним гаманцем джеттона (а не з якимось невідомим гаманцем, що використовує конкретний основний контракт Jetton).
  2. Під час зв'язування гаманця Jetton та головного контракту Jetton переконайтеся, що цей зв'язок є двонаправленим, тобто гаманець розпізнає головний контракт і навпаки. Наприклад, якщо ваша контрактна система отримує повідомлення від гаманця Jetton (який вважає свій MySuperJetton головним контрактом), інформація про переказ повинна відображатися користувачеві, перш ніж показати символ, ім'я та зображення контракту MySuperJetton, переконайтеся, що гаманець MySuperJetton використовує правильну контрактну систему. У свою чергу, якщо ваша контрактна система з якихось причин повинна відправляти джеттони з використанням майстер-контрактів MySuperJetton або MySuperJetton, перевірте, що гаманець X використовує ті ж параметри контракту, що і гаманець MySuperJetton. Крім того, перед тим, як надсилати запит на переказ на X, переконайтеся, що він визнає MySuperJetton своїм головним.
  3. Справжня сила децентралізованих фінансів (DeFi) базується на можливості накладати протоколи один на одного, як кубики лего. Наприклад, скажімо, джеттон А обмінюється на джеттон Б, який, в свою чергу, використовується як кредитне плече в рамках протоколу кредитування (коли користувач надає ліквідність), який потім використовується для купівлі NFT .... і так далі. Таким чином, розглянемо, як контракт може обслуговувати не тільки позамережевих користувачів, але й мережевих суб'єктів, прикріплюючи токенізовану вартість до повідомлення про переказ, додаючи користувацьке корисне навантаження, яке може бути надіслане разом із повідомленням про переказ.
  4. **Майте на увазі, що не всі контракти дотримуються однакових стандартів. На жаль, деякі джеттони можуть бути ворожими (з використанням векторів атак) і створюватися з єдиною метою - атакувати користувачів, які нічого не підозрюють. З метою безпеки, якщо протокол, про який йде мова, складається з багатьох контрактів, не створюйте велику кількість однотипних джеттон-гаманців. Зокрема, не надсилайте джеттони всередині протоколу між депозитним контрактом, контрактом на зберігання, контрактом на обліковий запис користувача тощо. Зловмисники можуть навмисно втрутитися в логіку контракту, підробивши повідомлення про переказ, суми джеттонів або параметри корисного навантаження. Зменшіть ймовірність атак, використовуючи лише один гаманець у системі на один джеттон (для всіх депозитів і зняття коштів).
  5. Також часто корисно створювати субконтракти для кожного окремого джеттона, щоб зменшити ймовірність підміни адреси (наприклад, коли повідомлення про переказ надсилається на джеттон B з використанням контракту, призначеного для джеттона A).
  6. Наполегливо рекомендується працювати з неподільними одиницями джеттон на рівні контрактів. Логіка, пов'язана з десятковою системою числення, зазвичай використовується для покращення користувацького інтерфейсу (UI) diplay і не пов'язана з числовим веденням записів у ланцюжку.

Щоб дізнатися більше про [Безпечне програмування смарт-контрактів у FunC від CertiK] (https://blog.ton.org/secure-smart-contract-programming-in-func), ознайомтеся з цим ресурсом. Рекомендується, щоб розробники обробляли всі винятки смарт-контрактів, щоб вони ніколи не були пропущені під час розробки програми.

Рекомендації щодо обробки гаманця Jetton

Як правило, всі процедури верифікації, що використовуються для обробки джеттонів, підходять і для гаманців. Для обробки джеттон-гаманців наші найважливіші рекомендації наступні:

  1. Коли гаманець отримує повідомлення про переказ з невідомого джеттон-гаманця, дуже важливо довіряти джеттон-гаманцю та його головній адресі, оскільки він може бути зловмисною підробкою. Щоб захистити себе, перевірте Jetton Master (основний договір), використовуючи надану адресу, щоб переконатися, що ваші процеси верифікації визнають джеттон-гаманець легітимним. Після того, як ви довіритеся гаманцю і він буде підтверджений як легітимний, ви можете дозволити йому доступ до залишків на ваших рахунках та інших даних у гаманці. Якщо Jetton Master не розпізнає цей гаманець, рекомендується взагалі не ініціювати і не розкривати свої джеттон-перекази, а показувати тільки вхідні перекази TON (Toncoin, прикріплені до повідомлень про перекази).
  2. На практиці, якщо користувач хоче взаємодіяти з джеттоном, а не з джеттон-гаманцем. Іншими словами, користувачі відправляють wTON/oUSDT/jUSDT, jUSDC, jDAI замість EQAjN.../EQBLE... тощо. Часто це означає, що коли користувач ініціює джеттон-переказ, гаманець запитує відповідного джеттон-майстра, який джеттон-гаманець (що належить користувачеві) повинен ініціювати запит на переказ. Важливо ніколи не довіряти сліпо** цим даним від майстра (майстер-контракту). Перш ніж надсилати запит на переказ на джеттон-гаманець, завжди переконайтеся, що джеттон-гаманець дійсно належить тому майстру джеттонів, на якого він посилається.
  3. **Майте на увазі, що ворожі Jetton-майстри/jetton-гаманці з часом можуть змінювати свої гаманці/майстри. Тому вкрай важливо, щоб користувачі проявляли належну обачність і перевіряли легітимність будь-яких гаманців, з якими вони взаємодіють, перед кожним використанням.
  4. **Завжди переконайтеся, що ви відображаєте джеттони в інтерфейсі таким чином, щоб вони не змішувалися з TON-переказами, системними сповіщеннями тощо. Навіть параметри "символ", "ім'я" та "зображення" можуть бути створені таким чином, щоб вводити в оману користувачів, роблячи їх потенційними жертвами шахрайства. Було зафіксовано кілька випадків, коли шкідливі джеттони використовувалися для імітації TON-переказів, помилок у сповіщеннях, нарахування винагороди або оголошень про заморожування активів.
  5. Завжди слідкуйте за потенційними зловмисниками, які створюють підроблені джеттони, тому завжди корисно надати користувачам функціонал, необхідний для усунення небажаних джеттонів в їхньому основному інтерфейсі користувача.

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

Найкращі практики

Якщо вам потрібні готові приклади для тестування, перевірте SDKs і спробуйте їх запустити. Нижче наведено фрагменти коду, які допоможуть вам зрозуміти обробку джеттонів на прикладах коду.

Надсилайте джеттони з коментарями

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

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

Прийняти Jetton Transfer з розбором коментарів

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


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

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

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

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

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

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

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

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

const jettonUserAddress = await jettonMaster.getWalletAddress(userAddress);

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

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

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

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


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

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

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

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

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

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

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

return retry(async () => {

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

for (const tx of Transactions) {

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

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

const in_msg = tx.inMessage;

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

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

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

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

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

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

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

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

SDK

Список SDK для різних мов (js, python, golang, C#, Rust і т.д.) можна знайти тут.

Дивіться також