Деплой 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;
}
Чеклист деплоя 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 и кошельками.
Проверка знанийЗачем при деплое Jetton Master указывается content: Cell с метаданными, если балансы хранятся в отдельных Jetton Wallet?
Check Your Understanding
Finished the lesson?
Mark it as complete to track your progress
Войдите чтобы оценить урок