Розробка Jetton-стандарту на TON

Проєктуємо та розробляємо блокчейн-рішення повного циклу: від архітектури смарт-контрактів до запуску DeFi-протоколів, NFT-маркетплейсів та криптобірж. Аудит безпеки, токеноміка, інтеграція з наявною інфраструктурою.
Показано 1 з 1Усі 1306 послуг
Розробка Jetton-стандарту на TON
Середній
~3-5 днів
Часті запитання

Напрямки блокчейн-розробки

Етапи блокчейн-розробки

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1286
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1198
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    902
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1122
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    589
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    859

Розроблення Jetton-стандарту на TON

Якщо ви розробляли токени на EVM — TON потребує перестройки мислення. Не косметичної, а принципової. Модель аккаунтів тут протилежна: замість центрального контракту з mapping(address → balance) кожен власник токена має свій власний смарт-контракт — Jetton Wallet. Це асинхронна, шардирована архітектура з message-passing замість прямих вызовів. І у неї є конкретні наслідки для розроблення.

Архітектура Jetton: два контракти

Jetton Master — центральний контракт, зберігає метадані токена (name, symbol, decimals, total_supply) та може мінтити нові Jetton Wallets.

Jetton Wallet — один екземпляр на кожного власника. Зберігає баланс конкретної адреси. При transfer — Jetton Wallet відправника шле повідомлення Jetton Wallet одержувача.

Transfer flow (TEP-74):
Користувач A → [внутрішнє повідомлення] → Jetton Wallet A → [внутрішнє повідомлення] → Jetton Wallet B → Користувач B повідомлений

Це не один атомарний виклик функції як в EVM — це ланцюг асинхронних повідомлень. Якщо Jetton Wallet одержувача ще не існує — він створюється при першому отриманні токена, відправник платить за деплой (~0.04 TON storage deposit).

Стандарти TEP-74 та TEP-64

TEP-74 (Fungible tokens) — основний стандарт Jetton. Визначає структуру повідомлень:

;; Jetton Wallet: обробка transfer повідомлення
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
    ;; op::transfer = 0xf8a7ea5
    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();
        
        ;; Відправляємо повідомлення на Jetton Wallet одержувача
        var msg_body = begin_cell()
            .store_uint(op::internal_transfer(), 32)
            .store_uint(query_id, 64)
            .store_coins(jetton_amount)
            .store_slice(my_address())   ;; from_address
            .store_slice(response_address)
            .store_coins(forward_ton_amount)
            .store_slice(forward_payload)
            .end_cell();
        
        ;; Адреса Jetton Wallet одержувача вичисляється детерміністично
        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  ;; carry remaining gas
        );
    }
}

TEP-64 (Token Data Standard) — стандарт метаданих. Метадані можуть зберігатися on-chain (у cell структурах) або off-chain (snake-encoded URI):

;; Jetton Master: get-метод для метаданих
cell get_jetton_data() method_id {
    return begin_cell()
        .store_coins(total_supply)
        .store_int(mintable, 1)
        .store_slice(admin_address)
        .store_ref(jetton_content)    ;; cell з метаданими
        .store_ref(jetton_wallet_code)
    .end_cell();
}

Метадані в jetton_content — або off-chain URI (0x01 prefix), або on-chain snake-encoded dictionary:

# Структура on-chain metadata cell:
{
  "name": "My Token",
  "description": "Token description", 
  "symbol": "MTK",
  "decimals": "9",
  "image": "https://example.com/logo.png"
}

Tact vs FunC: вибір мови

FunC — нативна мова TON. Низькорівнева, схожа на C. Повний контроль над gas та структурою ячейок. Максимальна продуктивність, мінімальна читаність коду.

Tact — высокорівнева мова, компілюється в FunC. Синтаксис ближче до TypeScript. Значно простіше для розробників, що приходять з EVM. На сьогодні (2024–2025) — рекомендований вибір для нових проектів:

// Jetton Master на 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;
        
        // Деплой Jetton Wallet для одержувача
        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,
        });
    }
}

Gas та storage: специфіка TON

На TON gas працює інакше ніж на EVM. Ключові відмінності:

Storage fee — аккаунти платять оренду за зберігання даних. Якщо баланс TON на Jetton Wallet впаде до нуля — аккаунт замерзне, дані потеряються. Це реальна проблема для власників з дуже малими балансами. Стандартний мінімум для Jetton Wallet: ~0.05 TON.

Forward TON — при відправці Jetton з forward_ton_amount > 0 одержувач контракту отримує сповіщення з прикріпленими TON. Це паттерн для інтеграції з DeFi протоколами TON: замість approve + transferFrom ви робите transfer з payload в forward_payload, який цільовий контракт обробляє при отриманні Jetton.

// Transfer з payload для DeFi інтеграції
// Аналог ERC-20 approve+transferFrom, але в TON-стилі
message(0x7362d09c) TokenNotification {
    queryId: Int as uint64;
    amount: Int as coins;
    from: Address;
    forwardPayload: Slice as remaining;  // кастомні дані
}

// У цільовому контракті (наприклад, DEX)
receive(msg: TokenNotification) {
    // Токени отримані, msg.forwardPayload містить інструкції
    // наприклад: "swap на інший токен"
    let swapInstruction: SwapPayload = SwapPayload.fromSlice(msg.forwardPayload);
    self.executeSwap(msg.from, msg.amount, swapInstruction);
}

Тестування

Sandbox (Blueprint) — офіційний фреймворк для тестування TON контрактів в TypeScript:

import { Blockchain, SandboxContract, TreasuryContract } from '@ton/sandbox';
import { JettonMaster } from '../wrappers/JettonMaster';
import { JettonWallet } from '../wrappers/JettonWallet';
import '@ton/test-utils';

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,  // Jetton Wallet задеплоєн
            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

Кастомізація Jetton — це зміна контракту Jetton Wallet. Усі додаткові features (transfer tax, whitelist, vesting) реалізуються в recv_internal Jetton Wallet, а не в Master. Це нетипово для EVM розробників.

Transfer tax:

;; У Jetton Wallet, перед відправкою internal_transfer
if (has_transfer_tax()) {
    int tax_amount = (jetton_amount * tax_bps) / 10000;
    int send_amount = jetton_amount - tax_amount;
    
    ;; Відправляємо tax на treasury wallet
    send_jettons(treasury_wallet_address, tax_amount, query_id);
    ;; Відправляємо остаток одержувачу
    send_jettons(to_wallet_address, send_amount, query_id);
}

Що входить в роботу

Розроблення Jetton Master + Jetton Wallet на Tact (або FunC за потребою), тестування через Blueprint sandbox, деплой на TON mainnet, верифікація через tonviewer.com, wrapper-скрипти на TypeScript для інтеграції. Строк: 5–10 днів для стандартного Jetton без кастомної логіки, 2–4 тижні для Jetton з кастомними механіками (vesting, whitelist, tax).