На прошлой неделе клиент попросил портировать AMM с Ethereum на TON за два месяца. EVM-рефлексы тут же сломались: синхронный вызов swap() на роутере — атомарная цепочка, а на TON каждый шаг — отдельное сообщение. Пять транзакций, разнесённых во времени, bounce-сообщения при ошибках, неочевидные race conditions. Пришлось перепроектировать архитектуру слёт на паттерне Jetton-to-Jetton под TON. Заказчик сэкономил месяц и 40% бюджета, потому что мы уже прошли эти грабли на пяти DEX-проектах. Разработка DEX на TON — это не портирование EVM-логики, а проектирование заново. Ниже рассказываем, как избежать переписывания дважды.
Разработка DEX на TON требует принципиально иного подхода к межконтрактному взаимодействию. — Документация TON
В чём главная сложность разработки DEX на TON?
На Ethereum вызов swap() на роутере — синхронная цепочка: роутер вызывает пул, пул обновляет резервы, возвращает результат — всё в одной транзакции. На TON каждый вызов между контрактами — отдельное сообщение. Роутер отправляет internal message в пул, пул обрабатывает его в отдельной транзакции, отправляет ответное сообщение роутеру. Это две транзакции, разнесённые во времени. Атомарности в EVM-смысле нет.
Для DEX это означает:
- Своп не атомарен: между отправкой входящих токенов и получением выходящих проходит 2-3 секунды
- Reentrancy в EVM-смысле невозможна, но race conditions между сообщениями реальны
- Откат всей цепочки при ошибке требует явной обработки: bounce-сообщения для возврата токенов
EVM-своп — одна транзакция, на TON — пять, что увеличивает время и требует управления bounce. Без корректной обработки bounced messages токены теряются навсегда. Ниже — обязательный паттерн для любого контракта DEX.
Обработка bounce-сообщения
() on_bounce(slice in_msg_body) impure {
int op = in_msg_body~load_uint(32);
if (op == op::transfer_notification) {
;; Получили bounce при transfer — возвращаем токены отправителю
send_tokens(original_sender, amount, jetton_wallet_addr);
}
}
Архитектура AMM на TON: от Jetton до свопа
TON не имеет native ERC-20. Вместо него — Jetton standard (TEP-74): каждый пользователь имеет отдельный jetton wallet контракт. Для свопа пользователь отправляет transfer на свой jetton wallet с payload, который содержит данные свопа. Jetton wallet отправляет transfer_notification в пул.
Архитектура пула для AMM:
User Jetton Wallet A
→ transfer(amount, pool_address, forward_payload=swap_data)
→ Pool Jetton Wallet A (transfer_notification)
→ Pool Contract (swap message)
→ Pool Jetton Wallet B (transfer)
→ User Jetton Wallet B
Пять контрактов, пять транзакций на один своп. Это нормально для TON, но требует аккуратного fee management: каждый шаг потребляет TON на gas. Пользователь должен приложить достаточно TON (обычно 0.1–0.3 TON) для оплаты всей цепочки. Экономия газа достигается за счёт оптимизации структуры сообщений — мы добиваемся снижения на 30% по сравнению с наивной реализацией.
Сравнение архитектур: Jetton vs Vault
| Параметр | Jetton (Ston.fi) | Vault (DeDust) |
|---|---|---|
| Транзакций на своп | 5 | 4 |
| Газ-затраты на своп (TON) | ~0.25 TON | ~0.18 TON |
| Совместимость со стандартами | Полная | Ограниченная (собственный flow) |
| Сложность реализации | Высокая | Средняя |
DeDust использует Vault и экономит 30% газа по сравнению с Ston.fi, но жертвует совместимостью с Jetton-потоком. Выбор архитектуры зависит от приоритетов проекта: если интеграция с другими Jetton-контрактами не критична, Vault — более эффективное решение.
Почему стоит выбрать Tact для новых проектов?
FunC — низкоуровневый язык, напоминающий C. Полный контроль над stack и cell операциями. Обязателен для понимания внутреннего устройства TON, но для коммерческой разработки DEX мы рекомендуем Tact. Tact — высокоуровневый язык с типизацией, struct-ами и более читаемым синтаксисом. Он компилируется в FunC, что даёт производительность низкого уровня без ручного управления ячейками.
contract LiquidityPool {
reserve0: Int as coins;
reserve1: Int as coins;
totalLpSupply: Int as uint128;
receive(msg: SwapRequest) {
let amountOut = self.calculateAmountOut(msg.tokenIn, msg.amountIn);
require(amountOut >= msg.minAmountOut, "Slippage exceeded");
self.updateReserves(msg.tokenIn, msg.amountIn, amountOut);
self.sendTokens(msg.recipient, amountOut, msg.tokenOut);
}
}
Контракт на Tact в 2 раза короче аналогичного на FunC, а риск ошибок при парсинге cell/slice снижается на порядок. Для новых DEX мы всегда начинаем с Tact, переходя на FunC только если требуется экстремальная оптимизация газа.
Сравнение FunC и Tact
| Характеристика | FunC | Tact |
|---|---|---|
| Уровень | Низкий | Высокий |
| Типизация | Нет | Строгая |
| Ошибки cell-парсинга | Частые | Редкие |
| Скорость разработки | Медленная | Быстрая |
| Популярность в сообществе | Устаревает | Растёт |
Как мы тестируем контракты DEX?
Blueprint — официальный фреймворк для разработки и тестирования TON контрактов (аналог Hardhat для TON). Поддерживает sandbox для локального тестирования без реальной ноды.
Sandbox (из @ton/sandbox) — in-process TON VM для unit тестов. Критично для тестирования bounce message handling и многошаговых транзакционных цепочек.
import { Blockchain } from '@ton/sandbox'
import { LiquidityPool } from '../build/LiquidityPool'
const blockchain = await Blockchain.create()
const pool = blockchain.openContract(await LiquidityPool.fromInit(token0, token1))
const swapResult = await pool.sendSwap(user.getSender(), {
tokenIn: token0Address,
amountIn: toNano('100'),
minAmountOut: toNano('95')
})
expect(swapResult.transactions).toHaveTransaction({
to: pool.address,
success: true
})
Мы используем fuzzing с Echidna и формальную верификацию для критичных контрактов — это позволяет выявить race conditions, которые не ловятся unit-тестами.
Шаги интеграции TON Connect в Telegram Mini App
- Подключите библиотеку
@tonconnect/ui-react. - Настройте манифест приложения с параметрами подключения.
- Реализуйте вызов
connector.connect(wallet)при клике на кнопку. - После подключения используйте
connector.accountдля получения адреса. - Для отправки транзакций создайте
Transactionобъект и вызовитеconnector.sendTransaction().
Что вы получите
- Архитектуру смарт-контрактов с детальной схемой сообщений и bounce-handling
- Полный репозиторий с контрактами на FunC/Tact и тестами в Blueprint
- Обучение вашей команды работе с TON, FunC/Tact и отладке
- Поддержку в тестнете и помощь при деплое в mainnet
- Код-ревью и опциональный аудит безопасности с формальной верификацией
Процесс работы и сроки
| Этап | Длительность | Результат |
|---|---|---|
| Аналитика | 2–3 дня | Тип AMM, экономическая модель, список пулов |
| Проектирование контрактов | 3–5 дней | Схема сообщений, bounce handling, fee accumulation |
| Разработка | 4–8 недель | Pool, Router, LP Jetton, тесты в Blueprint |
| Фронтенд и TON Connect | 2–3 недели | Swap UI, liquidity management, аналитика |
| Деплой и тестнет | 1 неделя | Testnet → mainnet |
Базовый AMM x*y=k с одним пулом и minimal UI — 6–8 недель. Полноценный DEX с роутером для мультихопов, аналитикой, Telegram Mini App — 3–4 месяца. Concentrated liquidity с менеджментом позиций — добавляет ещё 4–6 недель.
Мы — команда с 5+ годами опыта в блокчейн-разработке, на счету 15+ DeFi-проектов, включая один из первых DEX на TON. Всегда используем практики формальной верификации и fuzzing, чтобы минимизировать риски потери средств.
Для оценки вашего проекта свяжитесь с нами. Получите консультацию по архитектуре DEX на TON и расчёт сроков под ваши задачи. Оценим проект бесплатно и предложим оптимальное решение.







