Разработка dApp на TON blockchain
TON — это не «Ethereum с другим синтаксисом». Это принципиально другая архитектурная модель, в которой большинство паттернов EVM не работают или работают наоборот. Разработчик, впервые переходящий с EVM, потратит неделю только на то, чтобы перестать думать категориями Solidity. Асинхронные сообщения между контрактами, cell-based хранилище, Actor model, отсутствие атомарных транзакций через несколько контрактов — это не ограничения, это дизайн.
Архитектурные особенности TON
Actor model и асинхронные сообщения
В EVM транзакция может вызвать N контрактов синхронно и атомарно. В TON каждый смарт-контракт — это Actor, принимающий сообщения. Вызов другого контракта — это отправка асинхронного сообщения, которое будет обработано в следующем блоке (или позже).
Последствия для разработки:
- Нет понятия «atomic composability» между несколькими контрактами
- Паттерн «check → effect → interact» (CEI) из EVM здесь не применяется напрямую
- Ответ от другого контракта приходит через
recv_internalкак входящее сообщение - Ошибка во вложенном контракте не откатывает весь chain — нужно явно обрабатывать bounce messages
;; Отправка внутреннего сообщения с обработкой bounce
send_raw_message(
begin_cell()
.store_uint(0x18, 6) ;; bounce flag = 1
.store_slice(destination_address)
.store_coins(amount)
.store_uint(0, 107) ;; default message flags
.store_ref(message_body)
.end_cell(),
64 ;; mode: carry remaining gas
);
Bounce сообщения — это TON-специфичный механизм возврата: если целевой контракт не существует или вернул ошибку, отправителю приходит bounce с возвратом TON. Обрабатывать bounce обязательно, иначе TON просто «исчезнут».
FunC и Tact: выбор языка
FunC — нативный язык TON контрактов. Низкоуровневый, работает с cells и slices напрямую. Необходим для максимальной оптимизации газа или при написании нестандартных контрактов. Синтаксис непривычный — функции в стиле lisp, глобальный стек TVM.
Tact — высокоуровневый язык, компилируется в FunC. Появился в 2023, активно развивается. Для большинства dApp сегодня Tact — правильный выбор: знакомый синтаксис, типобезопасность, встроенные паттерны для jetton/NFT.
// Tact: jetton transfer с callback
message JettonTransfer {
queryId: Int as uint64;
amount: Int as coins;
destination: Address;
responseDestination: Address?;
forwardTonAmount: Int as coins;
forwardPayload: Slice as remaining;
}
contract JettonWallet {
receive(msg: JettonTransfer) {
// Проверка sender - только master контракт или другой wallet
require(sender() == self.master || sender() == self.owner, "Unauthorized");
// Отправка notify покупателю
if (msg.forwardTonAmount > 0) {
send(SendParameters{
to: msg.destination,
value: msg.forwardTonAmount,
body: JettonNotification{ amount: msg.amount }.toCell()
});
}
}
}
Cell-based хранилище
В EVM данные хранятся в storage slots (uint256 слоты). В TON — в cells: древовидная структура, где каждая cell содержит до 1023 бит данных и до 4 ссылок на другие cells. Это позволяет хранить произвольные структуры, но требует явной сериализации/десериализации.
Стандарт хранилища контракта — через load_data() / save_data():
;; FunC: загрузка состояния из cell
(slice owner, int balance, cell metadata) load_data() inline {
slice ds = get_data().begin_parse();
return (
ds~load_msg_addr(), ;; 267 бит - адрес
ds~load_coins(), ;; переменная длина - coins
ds~load_ref() ;; ссылка на cell с метаданными
);
}
Важное ограничение: максимальный размер state контракта — 65536 cells. Для коллекций NFT или jetton с тысячами держателей данные хранятся в отдельных контрактах для каждого пользователя — паттерн, где master контракт деплоит child контракты (wallet контракты для jetton, item контракты для NFT).
TEP стандарты: Jetton и NFT
Jetton (TEP-74/TEP-89)
Jetton — аналог ERC-20 в TON. Но архитектура принципиально другая. В EVM один ERC-20 контракт хранит mapping(address → balance). В TON:
- Jetton Master — хранит общие параметры (totalSupply, metadata, admin)
- Jetton Wallet — отдельный контракт для каждого держателя, хранит его баланс
При переводе: отправитель отправляет сообщение transfer своему Jetton Wallet → тот отправляет internal_transfer Jetton Wallet получателя → тот уведомляет получателя через transfer_notification.
Три контракта, три асинхронных сообщения, три блока минимум. Gas распределяется между ними — нужно отправлять достаточно TON для покрытия gas на всех уровнях.
NFT (TEP-62/TEP-64)
Аналогично Jetton: NFT Collection контракт + отдельный NFT Item контракт для каждого токена. Item хранит owner, individual_content, collection_address. Минт — это деплой нового Item контракта.
// NFT Collection: минт нового item
receive(msg: Mint) {
let itemAddress = contractAddress(initOf NftItem(
self.nextItemIndex,
myAddress(), // collection address
msg.owner,
msg.content
));
// Деплой через StateInit
send(SendParameters{
to: itemAddress,
value: ton("0.05"), // gas для деплоя и инициализации
code: codeOf NftItem,
data: NftItem.init(self.nextItemIndex, myAddress(), msg.owner, msg.content),
body: "Deploy".asComment()
});
self.nextItemIndex += 1;
}
TON Connect и фронтенд
TON Connect 2.0
Аналог WalletConnect для TON. Поддерживается Tonkeeper, MyTonWallet, OpenMask. Реализация на React:
import { TonConnectUIProvider, TonConnectButton, useTonConnectUI, useTonAddress } from '@tonconnect/ui-react';
function App() {
return (
<TonConnectUIProvider manifestUrl="https://your-dapp.com/tonconnect-manifest.json">
<DappContent />
</TonConnectUIProvider>
);
}
function DappContent() {
const userAddress = useTonAddress();
const [tonConnectUI] = useTonConnectUI();
async function sendTransaction() {
await tonConnectUI.sendTransaction({
messages: [{
address: CONTRACT_ADDRESS,
amount: toNano('0.1').toString(),
payload: beginCell()
.storeUint(0x5fcc3d14, 32) // op code transfer
.storeUint(queryId, 64)
.endCell()
.toBoc()
.toString('base64')
}]
});
}
// ...
}
tonconnect-manifest.json — обязательный файл, описывает dApp для кошелька. Должен быть доступен по публичному URL.
Telegram Mini Apps интеграция
Для TON экосистемы Telegram Mini Apps — первоклассный deployment target. Пользователи не устанавливают кошелёк отдельно — Tonkeeper встроен. Telegram Web App API предоставляет контекст пользователя:
import WebApp from '@twa-dev/sdk';
// Инициализация Telegram Mini App
WebApp.ready();
WebApp.expand(); // полноэкранный режим
// Данные пользователя из Telegram (не требуют wallet connection для базового UX)
const user = WebApp.initDataUnsafe.user;
// MainButton для primary action
WebApp.MainButton.setText('Подтвердить транзакцию');
WebApp.MainButton.onClick(() => sendTransaction());
WebApp.MainButton.show();
Архитектура Mini App: Vite + React + TON Connect UI React. Деплой на любой HTTPS хостинг. Bot создаётся через @BotFather, Mini App регистрируется через /newapp.
Тестирование и инфраструктура
Testnet и локальная среда
TON Testnet (testnet.toncenter.com) — для интеграционных тестов. Testnet TON получают через @testgiver_ton_bot. Важно: адреса mainnet и testnet несовместимы (разные workchain параметры).
Для unit тестов — @ton-community/sandbox (ton-core) позволяет запускать контракты локально без нод:
import { Blockchain, SandboxContract, TreasuryContract } from '@ton/sandbox';
import { JettonMaster } from '../wrappers/JettonMaster';
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, Cell.fromBase64(metadata))
);
await jettonMaster.send(deployer.getSender(), { value: toNano('0.5') }, 'Deploy');
});
it('should mint tokens', async () => {
const result = await jettonMaster.send(deployer.getSender(),
{ value: toNano('0.1') },
{ $$type: 'Mint', amount: toNano('1000'), receiver: deployer.address }
);
expect(result.transactions).toHaveTransaction({ success: true });
});
});
Blueprint framework
Blueprint — официальный инструмент разработки TON контрактов. Аналог Hardhat/Foundry для TON:
npm create ton@latest
# Создаёт структуру: contracts/, wrappers/, tests/, scripts/
Wrappers — TypeScript обёртки над контрактами, генерируются из Tact или пишутся вручную для FunC. Используются и в тестах, и во фронтенде.
Стек разработки
Контракты: Tact 1.x (preferred) или FunC. Blueprint для тестирования и деплоя.
Frontend: React + Vite (для Mini Apps) или Next.js (для web dApp). TON Connect UI React, @ton/ton, @ton/core для работы с блокчейном.
Backend (если нужен): TON API (toncenter.com) или TON HTTP API v2. Для production — собственная нода через mytonctrl или использование QuickNode/Tatum для TON.
| Компонент | Технология |
|---|---|
| Смарт-контракты | Tact + Blueprint |
| Wallet connection | TON Connect 2.0 |
| Web frontend | Next.js + TON Connect UI |
| Telegram Mini App | Vite React + @twa-dev/sdk |
| Тесты | @ton/sandbox + Jest/Vitest |
| Деплой контрактов | Blueprint deploy scripts |
Ориентиры по срокам
Простой dApp: один контракт (Tact) + web frontend с TON Connect — 2-3 недели (с учётом кривой обучения TON архитектуры если команда с EVM бэкграундом). Telegram Mini App с jetton интеграцией, стейкингом и leaderboard — 4-6 недель. Полноценный DeFi протокол (AMM или lending) на TON с аудитом — 2-4 месяца. Асинхронная природа TON требует значительно большего тестирования message chains по сравнению с EVM.







