Архитектура Jetton 2.0
Jetton — это стандарт взаимозаменяемых токенов на TON, аналог ERC-20 в Ethereum, но с принципиально другой архитектурой. В отличие от ERC-20, где все балансы хранятся в одном контракте, в Jetton каждый держатель имеет свой отдельный контракт (Jetton Wallet). Это обеспечивает бесконечную масштабируемость, но требует понимания двухуровневой архитектуры для корректной интеграции.
В модулях M03 и M04 мы научились писать смарт-контракты на Tact и разобрали модель акторов. Теперь применим эти знания к токенам — одному из главных применений блокчейна. TON использует уникальную шардированную архитектуру токенов, принципиально отличающуюся от Ethereum.
ERC-20 vs Jetton: два разных подхода
В Ethereum токен стандарта ERC-20 — это один контракт с маппингом balances[address] => amount. Все балансы хранятся в одном месте:
В TON такой подход невозможен по архитектуре. Поскольку контракты распределены по шардчейнам и не имеют синхронного доступа к чужому состоянию, хранить все балансы в одном контракте — значит создать узкое место (bottleneck), нарушающее принцип параллелизма.
ERC-20 vs Jetton 2.0
ERC-20 хранит все балансы в одном mapping внутри одного контракта. Это работает на Ethereum, потому что EVM обеспечивает синхронный доступ к любому состоянию. В TON каждый контракт изолирован в своём шардчейне — поэтому каждый баланс живёт в отдельном контракте.
Шардированная модель Jetton 2.0
Стандарт TEP-74 определяет архитектуру Jetton 2.0, состоящую из двух типов контрактов:
Jetton Master (мастер-контракт)
Один на весь токен. Хранит:
- Metadata (TEP-64): имя токена, символ, описание, иконка
- total_supply: общее количество выпущенных токенов
- owner: адрес владельца (может минтить новые токены)
- mintable: разрешено ли создание новых токенов
Jetton Master не хранит балансы пользователей. Его задача — управлять метаданными и создавать новые Jetton Wallet при минтинге.
Jetton Wallet (контракт баланса)
Один на каждого пользователя, владеющего токеном. Хранит:
- balance: количество токенов пользователя
- owner: адрес TON-кошелька владельца
- jetton_master: адрес мастер-контракта
Jetton Wallet развёрнут в шардчейне своего владельца. Это ключевое свойство Jetton 2.0 — контракт баланса находится рядом с кошельком пользователя, обеспечивая максимальную скорость обработки.
Jetton Wallet — это НЕ TON Wallet
Не путайте Jetton Wallet (контракт баланса токенов) и TON Wallet (кошелёк пользователя типа v4/v5). Jetton Wallet — это отдельный смарт-контракт, который хранит баланс одного конкретного токена для одного пользователя. У пользователя может быть десятки Jetton Wallet — по одному на каждый токен.
Почему шардирование?
Шардированная архитектура Jetton 2.0 решает три ключевые проблемы:
1. Параллелизм
Каждый Jetton Wallet обрабатывается в шардчейне своего владельца. Когда Alice и Bob одновременно переводят токены разным получателям, их транзакции обрабатываются параллельно в разных шардах. Нет общего контракта, который стал бы узким местом.
2. Масштабируемость
Токен с миллионом держателей = миллион отдельных контрактов, распределённых по шардчейнам. Нагрузка распределяется равномерно по всей сети, а не концентрируется на одном контракте.
3. Локальность данных
Jetton Wallet пользователя находится в том же шардчейне, что и его TON Wallet. Это минимизирует межшардовые сообщения при инициации транзакций.
Детерминистическое обнаружение адресов
Как Jetton Master “знает” адрес Jetton Wallet конкретного пользователя? Адрес вычисляется детерминистически из init-данных:
jetton_wallet_address = hash(
code: jetton_wallet_code,
data: init(owner_address, jetton_master_address)
)
Это означает, что:
- Любой может вычислить адрес Jetton Wallet, зная адрес владельца и мастер-контракта
- Jetton Wallet не нужно регистрировать — его адрес предсказуем
- При первом переводе Jetton Wallet может быть развёрнут автоматически
Именно так кошельки и DEX определяют, где находится баланс пользователя: они вычисляют адрес Jetton Wallet и запрашивают его состояние.
TEP-89: Discoverable Jettons Wallets
Детерминистическая формула адреса Jetton Wallet элегантна, но создаёт проблему интеграции: чтобы вычислить адрес, dApp должен знать точный код Jetton Wallet мастер-контракта (он может отличаться от референсной реализации) и формат init-данных. Любое расхождение версий — и адрес получится “не тот”, сообщения уйдут в пустоту.
TEP-89 (Discoverable Jettons Wallets) — расширение TEP-74, которое решает проблему стандартизованным on-chain RPC: вместо ручного вычисления dApp просто спрашивает Jetton Master о адресе кошелька конкретного владельца.
Протокол: provide / take
TEP-89 определяет два opcode-сообщения:
| Opcode | Имя | Направление | Назначение |
|---|---|---|---|
0x2c76b973 | provide_wallet_address | dApp -> Jetton Master | ”Дай адрес Jetton Wallet для этого владельца” |
0xd1735400 | take_wallet_address | Jetton Master -> dApp | ”Вот адрес Jetton Wallet” (ответ) |
Поток выглядит так:
dApp (контракт) -> Jetton Master:
op: 0x2c76b973 (provide_wallet_address)
query_id: uint64
owner_address: Address
include_address: Bool // включать ли owner_address в ответ?
Jetton Master -> dApp:
op: 0xd1735400 (take_wallet_address)
query_id: uint64
jetton_wallet_address: Address
owner_address: Address? // если include_address = true
Зачем это нужно
Без TEP-89 dApp обязан сам реализовать вычисление адреса: хранить копию jetton_wallet_code, корректно собирать init-Cell, вызывать contractAddress(). Это:
- Хрупко: при апгрейде кошелька (изменение кода) формула ломается.
- Сложно для on-chain интеграции: смарт-контракту неудобно тащить с собой код всех Jetton Wallet, с которыми он работает.
- Несовместимо с кастомными мастерами, у которых нестандартный init.
С TEP-89 dApp передаёт ответственность мастеру: тот знает свой код и сам вычисляет адрес. Это особенно важно для on-chain DEX, multisig и эскроу-контрактов, которые принимают произвольные Jetton.
TEP-89 vs off-chain SDK
Off-chain SDK (например, @ton/ton, tonweb) обычно вычисляют адрес локально по жёстко зашитому коду референсного Jetton Wallet. Это работает для стандартных Jetton, но для on-chain контракта (где нет JS-runtime) TEP-89 — единственный способ получить адрес безопасно.
Реализация в Tact
Стандартный trait Jetton в @stdlib/jetton уже включает обработчик provide_wallet_address. Минимальный пример обработки на стороне Master:
message(0x2c76b973) ProvideWalletAddress {
queryId: Int as uint64;
ownerAddress: Address;
includeAddress: Bool;
}
message(0xd1735400) TakeWalletAddress {
queryId: Int as uint64;
walletAddress: Address;
ownerAddress: Slice as remaining; // either<None, Address>
}
contract MyJettonMaster {
// ... остальные поля и receivers
receive(msg: ProvideWalletAddress) {
let walletInit: StateInit = initOf MyJettonWallet(msg.ownerAddress, myAddress());
let walletAddress: Address = contractAddress(walletInit);
let ownerSlice: Builder = beginCell();
if (msg.includeAddress) {
ownerSlice = ownerSlice.storeUint(0x400, 11).storeAddress(msg.ownerAddress);
} else {
ownerSlice = ownerSlice.storeUint(0, 2); // None tag
}
send(SendParameters{
to: sender(),
value: 0,
mode: SendRemainingValue,
body: TakeWalletAddress{
queryId: msg.queryId,
walletAddress: walletAddress,
ownerAddress: ownerSlice.endCell().asSlice(),
}.toCell(),
});
}
}
DEX, агрегаторы и payment-процессоры обязаны реализовывать TEP-89 — иначе их Jetton не получится использовать в on-chain интеграциях. При написании собственного Jetton Master добавляйте этот receiver сразу.
Metadata по стандарту TEP-64
Jetton Master хранит метаданные токена в формате TEP-64. Существуют два подхода:
On-chain metadata — все данные в Cell контракта:
content_layout: 0x00
metadata:
name: "My Token"
symbol: "MTK"
decimals: "9"
description: "A sample Jetton 2.0 token"
image: "https://example.com/token.png"
Off-chain metadata — ссылка на JSON-файл:
content_layout: 0x01
uri: "https://example.com/token-metadata.json"
Большинство токенов используют off-chain metadata для экономии газа. JSON-файл размещают на IPFS или HTTPS-сервере. Кошельки и эксплореры (Tonviewer, TonScan) автоматически загружают метаданные по URI.
Интерактивная визуализация
В следующем уроке мы детально разберём поток перевода Jetton и используем интерактивную диаграмму JettonFlowDiagram, которая показывает:
- Архитектуру — связь Master и Wallet контрактов
- Перевод — 4-шаговый поток сообщений между контрактами
- Минтинг — создание новых токенов через Master
Итоги
| Свойство | ERC-20 (Ethereum) | Jetton 2.0 (TON) |
|---|---|---|
| Хранение балансов | Один mapping в одном контракте | Отдельный контракт на каждого пользователя |
| Количество контрактов | 1 | 1 + N (master + wallet на каждого держателя) |
| Параллелизм | Ограничен одним контрактом | Полный — каждый wallet в своём шарде |
| Обнаружение баланса | balanceOf(address) | Вычислить адрес Jetton Wallet -> запросить состояние |
| Стандарт | ERC-20 (EIP-20) | TEP-74 |
| Метаданные | ERC-20 (name, symbol, decimals) | TEP-64 (on-chain / off-chain JSON) |
Частые ошибки
- Пытаются прочитать баланс пользователя из Jetton Master, хотя балансы хранятся в индивидуальных Jetton Wallet контрактах, а не централизованно.
- Путают Jetton Master (minter) и Jetton Wallet (balance holder): это два разных типа контрактов с разными интерфейсами.
- Не верифицируют, что Jetton Wallet действительно принадлежит заявленному Jetton Master, что позволяет фишинговым токенам подделывать метаданные.
- Забывают, что total_supply в Jetton Master обновляется асинхронно: в момент чтения он может не отражать все pending-операции mint/burn.
Jetton — частный случай более широкого паттерна “system design токена”: от выбора структуры (один master + N wallet vs альтернативы) до экономики и upgrade-стратегии. Системный взгляд на проектирование токенов на TON есть в курсе по System Design.
System Design: проектирование токена на TON