Jetton-токен: архитектура, которая работает не как ERC-20
При переносе проекта с Ethereum на TON (The Open Network) многие сталкиваются с проблемой: токены на TON не работают, хотя код на Solidity выглядит безупречно. Дело не в языке — разница в фундаменте. TON использует шардинг, и токены здесь устроены иначе: не один контракт с mapping балансов, а система из двух контрактов. Jetton-токен — стандарт TEP-74, разработанный для масштабирования. За 5 лет работы мы запустили более 20 таких проектов и знаем все подводные камни. Оценим ваш проект бесплатно — просто напишите.
Как устроен Jetton-токен?
Jetton — стандарт TEP-74, аналог ERC-20, но с принципиально другой архитектурой. В TON каждый держатель токенов получает собственный контракт кошелька (Jetton Wallet), а общую логику (минтинг, метаданные) хранит центр (Jetton Master). Это обеспечивает локальность транзакций в шардах — transfer между двумя кошельками не требует cross-shard взаимодействия, что даёт прирост производительности в 10 раз по сравнению с ERC-20 при высоких нагрузках. Каждая операция перевода требует отправки TON для газа: типичная стоимость — 0.05–0.1 TON, а для новых получателей — до 0.15 TON с учётом storage fees. Благодаря gas-оптимизации мы достигаем экономии до 30% по сравнению с неоптимизированными контрактами.
Архитектура: Jetton Master и Jetton Wallet
Jetton Master — один контракт на токен. Он хранит метаданные (name, symbol, decimals, totalSupply), логику минтинга и список зарегистрированных кошельков. Не хранит балансы — он их не знает.
Jetton Wallet — отдельный контракт для каждого адреса. Развёртывается автоматически при первой отправке токенов на адрес. Хранит баланс пользователя и обрабатывает transfer.
Схема работы:
Alice → (transfer) → Alice's Jetton Wallet
Alice's Jetton Wallet → (internal message) → Bob's Jetton Wallet
Bob's Jetton Wallet: принимает токены, увеличивает баланс
Пример контракта на Tact
Tact — высокоуровневый язык для TON, предпочтительнее FunC. Ниже пример полной реализации Jetton Master и Jetton Wallet в одном контракте:
import "@stdlib/jetton";
message(0x178d4519) TokenTransferInternal {
queryId: Int as uint64;
amount: Int as coins;
from: Address;
responseAddress: Address?;
forwardTonAmount: Int as coins;
forwardPayload: Slice as remaining;
}
contract JettonMaster with Jetton {
totalSupply: Int as coins = 0;
owner: Address;
jettonContent: Cell;
mintable: Bool;
init(owner: Address, content: Cell) {
self.owner = owner;
self.jettonContent = content;
self.mintable = true;
}
receive(msg: JettonMint) {
require(sender() == self.owner, "Only owner can mint");
require(self.mintable, "Minting disabled");
self.totalSupply += msg.amount;
let initData: StateInit = self.getJettonWalletInit(msg.receiver);
let walletAddress: Address = contractAddress(initData);
send(SendParameters{
to: walletAddress,
body: TokenTransferInternal{
queryId: msg.queryId,
amount: msg.amount,
from: myAddress(),
responseAddress: msg.responseAddress,
forwardTonAmount: msg.forwardTonAmount,
forwardPayload: emptySlice(),
}.toCell(),
value: msg.tonAmount,
mode: SendIgnoreErrors,
code: initData.code,
data: initData.data,
});
}
fun getJettonWalletInit(owner: Address): StateInit {
return initOf JettonWallet(owner, myAddress());
}
}
contract JettonWallet with JettonWallet {
balance: Int as coins = 0;
owner: Address;
jettonMaster: Address;
init(owner: Address, jettonMaster: Address) {
self.owner = owner;
self.jettonMaster = jettonMaster;
}
receive(msg: TokenTransfer) {
require(sender() == self.owner, "Not owner");
require(msg.amount > 0, "Zero amount");
require(self.balance >= msg.amount, "Insufficient balance");
self.balance -= msg.amount;
let receiverWalletInit: StateInit = initOf JettonWallet(msg.destination, self.jettonMaster);
let receiverWallet: Address = contractAddress(receiverWalletInit);
send(SendParameters{
to: receiverWallet,
value: msg.forwardTonAmount + context().readForwardFee() * 2,
mode: SendIgnoreErrors,
body: TokenTransferInternal{
queryId: msg.queryId,
amount: msg.amount,
from: self.owner,
responseAddress: msg.responseAddress,
forwardTonAmount: msg.forwardTonAmount,
forwardPayload: msg.forwardPayload,
}.toCell(),
code: receiverWalletInit.code,
data: receiverWalletInit.data,
});
}
}
Сравнение Jetton и ERC-20
| Параметр | Jetton (TON) | ERC-20 (Ethereum) |
|---|---|---|
| Архитектура | Двухуровневая: Master + Wallet | Один контракт с mapping |
| Масштабирование | Шардинг, горизонтальное | Один поток, лимит газа |
| Стоимость перевода | 0.05–0.1 TON | ~$1-5 при загруженности |
| Безопасность | Bounce-механизм | Reentrancy guard |
| Gas-оптимизация | Встроенный bounce | Требует явной обработки |
Этапы разработки Jetton-токена
| Этап | Длительность | Описание |
|---|---|---|
| Аналитика и проектирование | 1 день | Определение параметров токена, архитектура |
| Написание контрактов на Tact | 2-4 дня | Master, Wallet, тесты |
| Аудит и gas-оптимизация | 1-2 дня | Slither, Mythril, формальная верификация |
| Деплой и верификация | 0.5 дня | Testnet → Mainnet, проверка в обозревателе |
| Поддержка | 30 дней | Исправление багов, апгрейды |
Как развернуть Jetton-токен?
- Определите параметры токена (name, symbol, decimals, totalSupply, mintable).
- Напишите контракты Master и Wallet на Tact (или адаптируйте наш шаблон).
- Протестируйте на тестовой сети TON (testnet) с помощью Tenderly или локально через TON Sandbox.
- Проведите аудит безопасности (мы используем Slither и Mythril, также возможна формальная верификация).
- Деплой в основную сеть и верификация контракта в обозревателе.
Gas-оптимизация и безопасность
Как снизить стоимость транзакций?
В TON каждая операция требует отправки TON для газа. При transfer вы должны отправить достаточно, чтобы покрыть: gas кошелька отправителя, gas кошелька получателя (включая деплой, если новый), и опциональный forwardAmount для уведомления. Типичная стоимость: 0.05–0.1 TON на операцию. Для новых получателей — до 0.15 TON с учётом storage fees. Благодаря gas-оптимизации мы достигаем экономии до 30% по сравнению с неоптимизированными контрактами.
Какие риски bounce-обработки?
Bounce-обработка обязательна. Если транзакция не прошла (например, недостаточно газа), TON возвращает сообщение отправителю. Jetton Wallet должен обработать bounced-сообщение и вернуть баланс:
bounced(msg: bounced<TokenTransferInternal>) {
self.balance += msg.amount;
}
Без этого обработчика баланс отправителя пропадает навсегда. Это одна из самых частых ошибок в продакшене.
Гарантии и поддержка
Мы предоставляем письменную гарантию на безопасность: если контракт взломают из-за ошибки в коде — исправим бесплатно в течение 30 дней. В разработку входит: архитектурная документация, полный код контрактов с тестами, интеграция с TON Connect 2.0, инструкция по деплою. Опишите свой проект — мы оценим сроки и стоимость. Закажите разработку Jetton-токена у профессионалов.







