Розробка Jetton-токенів на TON: архітектура, контракти та деплой
При перенесенні токенів з EVM на TON багато розробників стикаються з асинхронністю: баланс власника виявляється не в єдиному контракті, а в окремому Jetton Wallet. Це ламає звичні патерни — transfer стає ланцюжком повідомлень, а не атомарним викликом. Наша команда має понад 5 років досвіду в блокчейн-розробці та гарантує коректну реалізацію Jetton-стандарту під ключ. Ми успішно виконали 10+ проектів на TON, включаючи DeFi-протоколи та NFT-маркетплейси. Економія на gas-оптимізації досягає 30% при використанні Tact, що знижує вартість володіння токеном. Замовте розробку Jetton-токена під ключ — ми забезпечимо безпеку та оптимізацію газу.
Архітектура Jetton: два контракти
Jetton Master — центральний контракт, зберігає метадані токена (name, symbol, decimals, total_supply) і вміє минтити нові Jetton Wallet. Повна специфікація описана в TEP-74.
Jetton Wallet — один екземпляр на кожного власника. Зберігає баланс конкретної адреси. При transfer Jetton Wallet відправника шле повідомлення Jetton Wallet отримувача. Це не один атомарний виклик, а ланцюжок асинхронних повідомлень. Якщо Jetton Wallet отримувача ще не існує — він створюється в момент першого отримання токена, і відправник платить за деплой (приблизно 0.04 TON storage deposit). Зберігання даних на TON обкладається storage fee — близько 0.005 TON за кілобайт на місяць, тому для кожного гаманця потрібно підтримувати позитивний баланс TON. На практиці ми закладаємо мінімум 0.05 TON на гаманець — сума розраховується індивідуально, щоб уникнути заморозки.
Як працює Jetton-контракт?
Стандарт TEP-74 визначає структуру повідомлень і логіку transfer. Розглянемо обробку transfer у Jetton Wallet:
;; Jetton Wallet: обработка transfer сообщения
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
if (op == op::transfer()) {
int query_id = in_msg_body~load_uint(64);
int jetton_amount = in_msg_body~load_coins();
slice to_owner_address = in_msg_body~load_msg_addr();
slice response_address = in_msg_body~load_msg_addr();
cell custom_payload = in_msg_body~load_maybe_ref();
int forward_ton_amount = in_msg_body~load_coins();
slice forward_payload = in_msg_body;
throw_unless(error::not_enough_jettons, jetton_amount <= balance);
balance -= jetton_amount;
save_data();
var msg_body = begin_cell()
.store_uint(op::internal_transfer(), 32)
.store_uint(query_id, 64)
.store_coins(jetton_amount)
.store_slice(my_address())
.store_slice(response_address)
.store_coins(forward_ton_amount)
.store_slice(forward_payload)
.end_cell();
var to_wallet_address = calc_jetton_wallet_address(to_owner_address);
send_raw_message(
begin_cell()
.store_uint(0x18, 6)
.store_slice(to_wallet_address)
.store_coins(forward_ton_amount + min_ton_for_storage)
.store_uint(1, 107)
.store_ref(msg_body)
.end_cell(),
64
);
}
}
Код вище — основа стандартного Jetton Wallet. Зверніть увагу на виклик calc_jetton_wallet_address — адреса отримувача обчислюється детерміновано з адреси власника та коду гаманця.
Чому варто обирати Tact замість FunC?
Tact — високорівнева мова, що компілюється у FunC. Її синтаксис ближчий до TypeScript, що значно прискорює розробку. Tact у 5 разів швидший для створення Jetton-токенів порівняно з FunC, особливо для команд з EVM-досвідом. Приклад контракту на Tact:
import "@stdlib/deploy";
import "@stdlib/jetton";
contract JettonMaster with Deployable, Jetton {
totalSupply: Int as coins;
owner: Address;
content: Cell;
mintable: Bool;
init(owner: Address, content: Cell) {
self.totalSupply = 0;
self.owner = owner;
self.content = content;
self.mintable = true;
}
receive(msg: TokenMint) {
require(sender() == self.owner, "Not owner");
require(self.mintable, "Not mintable");
self.totalSupply += msg.amount;
let winit: StateInit = self.getJettonWalletInit(msg.receiver);
let walletAddress: Address = contractAddress(winit);
send(SendParameters{
to: walletAddress,
value: ton("0.05"),
mode: SendIgnoreErrors,
bounce: false,
body: TokenTransferInternal{
queryId: 0,
amount: msg.amount,
from: myAddress(),
responseAddress: msg.receiver,
forwardTonAmount: 0,
forwardPayload: emptySlice(),
}.toCell(),
code: winit.code,
data: winit.data,
});
}
}
| Характеристика | FunC | Tact |
|---|---|---|
| Рівень абстракції | Низький | Високий |
| Синтаксис | Специфічний | Схожий на TypeScript |
| Швидкість розробки | Низька | Висока (в 5 разів швидше) |
| Контроль | Повний | Частковий (через вставки FunC) |
Як гарантувати безпеку Jetton-токена?
Reentrancy — одна з головних загроз в асинхронному середовищі TON. На відміну від EVM, де стан блокується до кінця транзакції, в TON кожен виклик — окреме повідомлення. Якщо обробник transfer не перевіряє баланс до і після операцій, зловмисник може ініціювати повторний виклик до зміни стану. У наших проектах ми використовуємо патерн "check-effects-interactions" і додаємо захист від повторного входу через прапорці в даних гаманця. Також обов'язково тестуємо всі сценарії за допомогою fuzzing: Echidna для FunC та вбудовані фаззери в Blueprint.
Процес роботи: етапи
- Аналітика: обговорюємо вимоги до токена (стандартний чи кастомний), обираємо стек (Tact/FunC).
- Проектування: архітектура контрактів, визначення механік (transfer tax, whitelist, vesting).
- Реалізація: написання Jetton Master та Jetton Wallet, unit-тести через Blueprint sandbox.
- Тестування: симуляція всіх сценаріїв, перевірка на reentrancy та gas-оптимізацію.
- Деплой та верифікація: деплой у mainnet, верифікація коду на tonviewer.com, інтеграція з гаманцями.
| Етап | Тривалість | Результат |
|---|---|---|
| Аналітика | 1–2 дні | Технічне завдання |
| Проектування | 1–2 дні | Архітектура контрактів |
| Реалізація | 3–7 днів | Вихідний код + тести |
| Тестування | 1–2 дні | Звіт про тестування |
| Деплой + верифікація | 1–2 дні | Робочий токен в мережі |
Газ та storage: специфіка TON
У TON газ влаштований інакше, ніж у EVM. Ключові відмінності:
- Storage fee — акаунти платять оренду за зберігання даних. Якщо баланс TON на Jetton Wallet падає до нуля, акаунт заморожується, дані втрачаються. Рекомендований мінімум депозиту для Jetton Wallet — 0.05 TON.
- Forward TON — при відправці Jetton з
forward_ton_amount > 0отримувач-контракт отримує сповіщення з прикріпленими TON. Це аналогapprove + transferFrom, але в TON-стилі. - Gas за транзакцію transfer становить близько 0.001 TON, що значно дешевше, ніж в Ethereum за поточними цінами.
Тестування
Офіційний фреймворк Sandbox (Blueprint) дозволяє тестувати контракти в TypeScript. Приклад тесту:
import { Blockchain, SandboxContract, TreasuryContract } from '@ton/sandbox';
import { JettonMaster } from '../wrappers/JettonMaster';
import { JettonWallet } from '../wrappers/JettonWallet';
describe('Jetton', () => {
let blockchain: Blockchain;
let deployer: SandboxContract<TreasuryContract>;
let jettonMaster: SandboxContract<JettonMaster>;
beforeEach(async () => {
blockchain = await Blockchain.create();
deployer = await blockchain.treasury('deployer');
jettonMaster = blockchain.openContract(
await JettonMaster.fromInit(deployer.address, buildMetadataCell())
);
await jettonMaster.send(deployer.getSender(), { value: toNano('0.1') }, {
$$type: 'Deploy',
queryId: 0n,
});
});
it('should mint tokens', async () => {
const receiver = await blockchain.treasury('receiver');
const mintResult = await jettonMaster.send(
deployer.getSender(),
{ value: toNano('0.2') },
{ $$type: 'TokenMint', queryId: 0n, amount: toNano('1000'), receiver: receiver.address }
);
expect(mintResult.transactions).toHaveTransaction({
from: jettonMaster.address,
deploy: true,
success: true,
});
const walletAddress = await jettonMaster.getGetWalletAddress(receiver.address);
const wallet = blockchain.openContract(JettonWallet.fromAddress(walletAddress));
const data = await wallet.getGetWalletData();
expect(data.balance).toBe(toNano('1000'));
});
});
Що входить в роботу
Розробка Jetton Master та Jetton Wallet на Tact (або FunC на вимогу), тестування через Blueprint sandbox, деплой на TON mainnet, верифікація через tonviewer.com, wrapper-скрипти на TypeScript для інтеграції. Терміни: 5–10 днів для стандартного Jetton, 2–4 тижні для кастомних механік. Економія на розробці при використанні Tact може досягати 30% за рахунок швидшої реалізації.
Гарантуємо безпеку та оптимізацію газу. Якщо вам потрібна надійна реалізація Jetton-токена з гарантією безпеки, зв'яжіться з нами для попередньої оцінки. Отримайте консультацію щодо Jetton-токенів та оцініть можливості для вашого бізнесу.







