Разработка 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 Wallets. Полная спецификация описана в 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 на кошелёк — это примерно 0.1 USD по текущему курсу, чтобы избежать заморозки.
Как работает 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-токенам и оцените возможности для вашего бизнеса.







