Деплой Jetton в Tact
Деплой собственного Jetton — это один из самых практичных навыков на TON, необходимый для создания токенов проекта, наградных систем и DeFi-протоколов. Практика деплоя на Tact закрепляет понимание двухуровневой архитектуры Jetton и даёт реальный код, который можно адаптировать для своих проектов.
В предыдущих уроках мы разобрали архитектуру Jetton 2.0 и поток перевода. Теперь реализуем собственный Jetton-токен на языке Tact, используя стандартную библиотеку @stdlib/jetton. Это практический урок — мы напишем код, скомпилируем и разберём каждую часть контракта.
Tact stdlib: готовые реализации
Tact предоставляет стандартную библиотеку @stdlib/jetton, которая реализует все контракты по стандарту TEP-74:
- JettonMaster — трейт для мастер-контракта (metadata, total_supply, minting)
- JettonWallet — трейт для контракта баланса (balance, transfer, internal_transfer)
Используя эти трейты, разработчику не нужно реализовывать логику переводов с нуля — стандартная библиотека обрабатывает все opcodes, bounce-сообщения и gas forwarding.
Только Jetton 2.0 — никаких v1 паттернов! Jetton v1 (до августа 2025) использовал устаревшие практики оценки газа и не гарантировал размещение Jetton Wallet в шардчейне владельца. Стандартная библиотека Tact реализует Jetton 2.0 с правильным шардированием. Не копируйте код из устаревших репозиториев.
Структура контракта
Создадим файл SampleJetton.tact:
import "@stdlib/deploy";
import "@stdlib/jetton";
contract SampleJetton with Jetton, Deployable {
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("Mint:100") {
let ctx: Context = context();
require(ctx.sender == self.owner, "Not owner");
let msg = JettonMint{
origin: ctx.sender,
receiver: ctx.sender,
amount: ton("100"),
};
self._mint(msg);
}
}
Разберём каждую часть.
Импорты
import "@stdlib/deploy";
import "@stdlib/jetton";
@stdlib/deploy— трейтDeployable, обрабатывающий деплой контракта@stdlib/jetton— трейтыJetton(мастер) иJettonWallet(кошелёк)
Трейты контракта
contract SampleJetton with Jetton, Deployable {
Jetton предоставляет:
- Хранение
totalSupply,owner,content,mintable - Обработку
JettonMint,JettonBurnNotificationсообщений - Автоматический деплой Jetton Wallet при первом переводе
- Все opcodes из TEP-74
Deployable предоставляет:
- Обработку сообщения
Deployдля инициализации контракта
Состояние (state variables)
totalSupply: Int as coins; // Общее количество выпущенных токенов
owner: Address; // Владелец (может минтить)
content: Cell; // Metadata по TEP-64
mintable: Bool; // Разрешено ли создание новых токенов
as coins — сериализация как VarUInteger 16 (стандарт TON для хранения количества нанотонов/токенов).
Инициализация
init(owner: Address, content: Cell) {
self.totalSupply = 0;
self.owner = owner;
self.content = content;
self.mintable = true;
}
При деплое контракт создаётся с нулевым totalSupply. Адрес контракта детерминирован из init-данных: hash(code + init(owner, content)).
Mint receiver
receive("Mint:100") {
let ctx: Context = context();
require(ctx.sender == self.owner, "Not owner");
let msg = JettonMint{
origin: ctx.sender,
receiver: ctx.sender,
amount: ton("100"),
};
self._mint(msg);
}
Только owner может минтить токены. self._mint() — метод из трейта Jetton, который:
- Увеличивает
totalSupply - Создаёт
internal_transferв Jetton Wallet получателя - Если Jetton Wallet не существует — деплоит его
TEP-64: метаданные токена
Jetton Master хранит метаданные в формате TEP-64. Для off-chain metadata:
{
"name": "Sample Jetton",
"symbol": "SMPL",
"decimals": "9",
"description": "A sample Jetton 2.0 token built with Tact",
"image": "https://example.com/token-logo.png"
}
Этот JSON размещается на HTTPS или IPFS, а URI сохраняется в Cell контракта с префиксом 0x01.
Стандарт TON использует 9 десятичных знаков (decimals = 9) по умолчанию, аналогично нанотону. Если ваш токен представляет валюту, используйте decimals: "9". Для NFT-подобных токенов с целочисленными количествами — decimals: "0".
Компиляция и тестирование в Blueprint
В M03 мы уже работали с Blueprint — инструментарием для разработки смарт-контрактов на TON. Процесс для Jetton аналогичен:
1. Компиляция
npx blueprint build
Blueprint компилирует Tact-контракт в TVM bytecode. На выходе:
SampleJetton.compiled.json— скомпилированный контрактSampleJetton.abi— ABI для взаимодействия
2. Тестирование в Sandbox
import { Blockchain, SandboxContract } from '@ton/sandbox';
import { toNano } from '@ton/core';
import { SampleJetton } from '../wrappers/SampleJetton';
describe('SampleJetton', () => {
let blockchain: Blockchain;
let jettonMaster: SandboxContract<SampleJetton>;
beforeEach(async () => {
blockchain = await Blockchain.create();
const deployer = await blockchain.treasury('deployer');
jettonMaster = blockchain.openContract(
await SampleJetton.fromInit(
deployer.address,
buildOnchainMetadata({ name: "Test", symbol: "TST" })
)
);
await jettonMaster.send(
deployer.getSender(),
{ value: toNano('0.1') },
{ $$type: 'Deploy', queryId: 0n }
);
});
it('should deploy and mint', async () => {
const deployer = await blockchain.treasury('deployer');
await jettonMaster.send(
deployer.getSender(),
{ value: toNano('0.1') },
"Mint:100"
);
const data = await jettonMaster.getGetJettonData();
expect(data.totalSupply).toBeGreaterThan(0n);
});
});
3. Деплой в testnet
npx blueprint run deployJetton --testnet
Blueprint подключается к testnet, запрашивает подтверждение и отправляет деплой-транзакцию. После деплоя вы получите адрес Jetton Master в testnet.
Для практики деплоя используйте Blueprint Docker Lab из M03. Лабораторная работа предоставляет готовое окружение с настроенным Blueprint, Tact-компилятором и подключением к testnet.
Jetton Wallet: автоматический деплой
Одна из ключевых особенностей — Jetton Wallet деплоится автоматически при первом переводе. Когда вы минтите токены на адрес нового получателя:
- Jetton Master формирует
internal_transferсinit-данными Jetton Wallet - TON видит, что контракт по целевому адресу не существует
- TON деплоит Jetton Wallet из
init-данных и затем доставляет сообщение - Jetton Wallet инициализируется и увеличивает баланс
Разработчику не нужно вручную деплоить Jetton Wallet — это происходит прозрачно.
Кастомизация Jetton
Трейт Jetton можно расширить:
Ограничение минтинга
receive("Stop Minting") {
let ctx: Context = context();
require(ctx.sender == self.owner, "Not owner");
self.mintable = false;
}
Передача владения
receive(msg: ChangeOwner) {
let ctx: Context = context();
require(ctx.sender == self.owner, "Not owner");
self.owner = msg.newOwner;
}
Кастомный минт с указанием получателя
receive(msg: MintTo) {
let ctx: Context = context();
require(ctx.sender == self.owner, "Not owner");
require(self.mintable, "Minting disabled");
let mintMsg = JettonMint{
origin: ctx.sender,
receiver: msg.receiver,
amount: msg.amount,
};
self._mint(mintMsg);
}
message MintTo {
receiver: Address;
amount: Int as coins;
}
Кейс-стади: USDT на TON
Самый показательный пример продакшен-Jetton — это USD₮ на TON, выпущенный Tether в 2024 году. К Q1 2026 это крупнейший актив в TON-экосистеме за пределами самого Toncoin: оборот ~$1.28B, и распространяется он среди 150M+ пользователей TON Wallet прямо внутри Telegram. Полезно разобрать, как реальная стейблмонета упакована в стандарт TEP-74 — это объясняет, какие компромиссы делает Tact stdlib и почему стандартный шаблон достаточен для серьёзного продукта.
Архитектура: обычный Jetton с фиксированными параметрами
Внутри USD₮ — это стандартный Jetton по TEP-74 + TEP-89, без никаких специальных opcode. Контракт открыто опубликован в репозитории ton-blockchain/stablecoin-contract и использует те же концепции, что мы реализовывали выше: Master + per-owner Jetton Wallet, детерминированные адреса, internal_transfer между кошельками, provide_wallet_address для on-chain интеграций.
| Параметр USD₮ | Значение | Откуда |
|---|---|---|
| Стандарт | TEP-74 + TEP-89 | Совместим с любым кошельком и DEX TON |
| Decimals | 6 | Совпадает с USD₮ на других сетях, не дефолтные 9 |
| Эмитент | Tether (admin-роль в master) | Адрес в state мастера |
| Total supply | Динамический, обновляется при mint/burn | Хранится в master, ~$1.28B по Q1 2026 |
| Контроль | Admin может lock/unlock/force-transfer/burn | Регуляторное требование Tether |
Ключевое архитектурное отличие от “учебного” Jetton — это admin-функции мастера: Tether как эмитент должен уметь блокировать кошельки санкционных лиц и насильно сжигать токены при потере приватных ключей легитимного держателя. Это делается через дополнительные opcode в master, которые в обычном Jetton отсутствуют. Сам перевод между обычными пользователями работает по стандартному пути TEP-74 — никакой transfer_hook, никакого whitelisting.
Centralized stablecoin — не “anti-crypto”
Регуляторика заставляет любого фиатно-обеспеченного эмитента иметь возможность блокировать средства (FATF требования, OFAC). USD₮ на TON в этом смысле ничем не отличается от USDC на Ethereum или USDT на Tron. Open-source контракт прозрачно показывает, что может делать admin — это лучше, чем непрозрачный enclave. Для приложений это значит: проектируйте интеграцию так, чтобы пользовательский баланс мог быть заморожен извне, и предусматривайте корректный UX для такого случая.
Газовая оптимизация: почему USD₮ дешевле
Tether вместе с core-командой TON оптимизировал контракт настолько, что перевод между двумя существующими держателями USD₮ стоит фиксированно ~0.0145 TON — примерно в 3 раза дешевле обычного Jetton. Источник экономии:
- Точная оценка газа. Контракт выверен под актуальные TVM-цены (Jetton 2.0 паттерн), не оставляет лишнего forward на ответных сообщениях.
- Минимальный код Wallet. Wallet-контракт сжат до необходимого минимума, что снижает stateInit-стоимость при первом mint.
- Шардирование. Wallet-контракт деплоится в шард владельца (ставится правильный workchain/префикс), что устраняет cross-shard transfer на самом частом пути.
Эта оптимизация — эмпирический ориентир: если ваш самописный Jetton требует >0.05 TON на простой transfer, значит вы либо не на Jetton 2.0, либо переплачиваете forward. Сравните с USD₮ как с baseline.
Fee-burning: где здесь Toncoin
Каждый перевод USD₮ платит газ в Toncoin. С учётом 150M+ пользователей и миллионов транзакций в месяц, накопительная нагрузка на Toncoin как gas-token заметна — часть этого Toncoin сжигается через стандартный механизм TON elections и переходит к валидаторам. Для экосистемы это создаёт рефлексивный эффект: чем активнее USD₮ обращается в TON, тем выше “сжигающее” давление на Toncoin. Это не специальный механизм USD₮ (никакого fee-burning хука в jetton-коде нет), а свойство всей TVM-модели — но именно USD₮ сделал его статистически значимым.
USD₮ Earn: yield через DeFi-vaults
В феврале 2026 TON Wallet добавил USD₮ Earn — возможность положить USD₮ в on-chain vaults и получать APY до 18%. Архитектурно это надстройка поверх стандартного Jetton: пользовательский USD₮ переводится через обычный TEP-74 transfer в адрес vault-контракта (Morpho-стиль lending + стратегии Re7), вместо USD₮ пользователь получает share-токен (тоже Jetton), который представляет долю в vault. Сам USD₮-контракт никак не знает о существовании Earn — vault это отдельный смарт-контракт, использующий стандартный интерфейс. Хороший пример того, что стандарт TEP-74 достаточен для построения сложных DeFi-продуктов поверх простого fungible-токена.
Чему учиться у USD₮
Когда будете деплоить собственный Jetton для серьёзного продукта: (1) сравнивайте газовую стоимость transfer с USD₮ — это honest baseline; (2) если нужны admin-возможности (заморозка, force-burn), смотрите на open-source stablecoin-contract от ton-blockchain как на reference; (3) decimals выбирайте исходя из совместимости с другими сетями, а не дефолта 9; (4) обязательно реализуйте TEP-89, иначе DeFi-протоколы не смогут вас интегрировать on-chain.
Чеклист деплоя Jetton
Перед деплоем в mainnet убедитесь:
- Metadata (TEP-64) загружена и доступна по URI
- Контракт протестирован в Sandbox (mint, transfer, burn)
- Bounce-обработка работает корректно
- Owner-only функции защищены проверкой
ctx.sender == self.owner - Достаточно TON на адресе для деплоя (~0.1 TON)
- Тест в testnet пройден успешно
Частые ошибки
- Не устанавливают metadata в формате off-chain (URL) или on-chain (Cell), и без метаданных токен не будет отображаться в кошельках и на маркетплейсах.
- Забывают передать достаточно TON для деплоя всех дочерних Jetton Wallet контрактов: каждый wallet требует баланс для оплаты хранения.
- Не ограничивают функцию mint: без проверки прав доступа любой может выпустить произвольное количество токенов.
- Используют жёстко закодированные decimals вместо стандартного значения 9, что приводит к несовместимости с DEX и кошельками.