Почему индексация TON требует иного подхода?
TON Blockchain — одна из сложнейших блокчейн-платформ для индексации. Причина в архитектуре: infinite sharding — количество шардов динамически меняется в зависимости от нагрузки, от 1 до 256. Транзакции внутри одного шарда финализируются быстро, но транзакция между аккаунтами в разных шардах — это цепочка сообщений, которую нужно отслеживать через несколько блоков и шардов. Каждое сообщение сериализовано в Cell и требует рекурсивного парсинга. Стандартный подход «слушаем блоки по одному» здесь не работает. Много лет мы работаем с блокчейнами, участвовали в аудитах смарт-контрактов TON, индексировали данные для крупного DeFi-проекта: более 2 млн транзакций в месяц. Индексация TON в 3–5 раз сложнее, чем аналогичная задача для EVM-цепей, и требует специализированного пайплайна.
Разработка индексатора для TON требует глубокого понимания TVM (TON Virtual Machine) и BOC-формата. Мы используем библиотеку @ton/ton версий 0.57+ для работы с блоками и транзакциями. В отличие от EVM, где каждая транзакция имеет один in-message, в TON одна транзакция может содержать до 255 out-messages, что делает традиционные SQL-модели неэффективными.
Как архитектура TON влияет на индексацию?
TON состоит из трёх уровней:
- Masterchain — главная цепь, финализирует состояние всех воркчейнов
- Basechain (workchain 0) — основная пользовательская цепь
- Shardchains — шарды workchain-а, их может быть от 1 до 256
Каждый masterchain блок ссылается на последние блоки всех шардов (ShardStateUnsplit). Для полного индексирования нужно:
- Получить masterchain блок
- Из него извлечь список актуальных шард-блоков
- Из каждого шард-блока извлечь транзакции
- Для каждой транзакции — отследить дочерние сообщения (out messages)
MasterBlock[N] └─ Shard(0:0..7fff)[blockX] ├─ tx1 → out_msg → [другой шард или аккаунт] └─ tx2 → out_msg → ... └─ Shard(0:8000..ffff)[blockY] └─ tx3 ... Hosted API vs собственный узел
| Критерий | Hosted API | Собственный узел |
|---|---|---|
| Время до старта | Минуты | Дни (сборка, синхронизация) |
| Нагрузка | Ограничена тарифом | Полный контроль |
| Исторические данные | Ограниченный буфер | Полный архив |
| Стоимость | От бесплатного уровня до корпоративных тарифов | Затраты на сервер и дисковое пространство: дешевле в 3-5 раз при больших объёмах |
| Сложность эксплуатации | Минимальная | DevOps-инженер в штате |
Hosted API (TonAPI, TON Center, GetBlock) — правильный выбор для MVP. Для high-load продакшена и исторических запросов нужен свой узел. Полный архивный узел TON занимает ~2 TB и растёт на ~500 GB в год. Мы помогаем выбрать архитектуру и развернуть ноду.
Сравнение СУБД для хранения TON-данных
| СУБД | Подходит для | Особенности |
|---|---|---|
| PostgreSQL | MVP, до 1 млн tx/день | ACID, легкая настройка, ограничена по скорости вставки |
| TimescaleDB | Высокая частота записи, временные ряды | Автоматическое партиционирование, непрерывные агрегаты |
| ClickHouse | Аналитика, миллиарды строк | Колоночное хранение, в 5-10 раз быстрее по агрегациям |
Что входит в работу?
- Проектирование схемы данных: под вашу бизнес-логику (транзакции, Jetton, NFT, DeFi)
- Реализация индексатора на TypeScript/Node.js с использованием
@ton/ton - Интеграция с TonAPI или собственным узлом
- Парсинг произвольных сообщений (op-коды, аргументы контрактов)
- Трассировка транзакций (trace chain) для аналитики
- База данных (PostgreSQL, TimescaleDB или ClickHouse для high-load)
- REST API и WebSocket для клиентов
- Мониторинг (отставание, ошибки, RPC latency)
- Документация и обучение команды
- Обработка форков: хранение сырых BOC для перепарсинга без потери данных
Как реализовать основной цикл индексатора?
class TonIndexer { private client: TonClient4; private db: Pool; private lastMasterBlock: number; async indexLoop(): Promise<void> { while (true) { try { const masterInfo = await this.client.getLastBlock(); const currentSeqno = masterInfo.last.seqno; if (currentSeqno <= this.lastMasterBlock) { await this.sleep(2000); // ~5 сек на блок continue; } for (let seqno = this.lastMasterBlock + 1; seqno <= currentSeqno; seqno++) { await this.processMasterBlock(seqno); } this.lastMasterBlock = currentSeqno; } catch (e) { console.error('Indexer error:', e); await this.sleep(5000); } } } private async processMasterBlock(seqno: number): Promise<void> { const masterBlock = await this.client.getBlock(seqno); await this.processShardTransactions(-1, masterBlock.shards .filter(s => s.workchain === -1) .flatMap(s => s.transactions)); for (const shard of masterBlock.shards.filter(s => s.workchain === 0)) { const transactions = await this.getShardTransactions(shard.workchain, shard.shard, shard.seqno); await this.processShardTransactions(shard.workchain, transactions); } } } Парсинг транзакций и Jetton-переводов
Транзакция TON содержит in_msg (причина) и out_msgs (результат). Для Jetton Transfer используется op-код 0x0f8a7ea5. Пример извлечения данных:
function parseTransaction(rawTx: RawTransaction): IndexedTransaction { const inMsg = rawTx.inMessage; let opcode: number | undefined; if (inMsg?.body) { const slice = inMsg.body.beginParse(); if (slice.remainingBits >= 32) opcode = slice.loadUint(32); } return { hash: rawTx.hash().toString('hex'), lt: rawTx.lt, account: rawTx.address.toString(), value: inMsg?.info.type === 'internal' ? inMsg.info.value.coins : undefined, opcode, exitCode: rawTx.description.computePhase?.exitCode ?? 0, computeFee: rawTx.totalFees.coins, timestamp: rawTx.now, blockSeqno: rawTx.blockSeqno, }; } Если нужна полная трассировка транзакций (например, для DEX-аналитики), используем TonAPI: вызов /v2/traces/{hash} возвращает дерево связанных транзакций. Это позволяет отследить весь путь от пользовательского вызова до финального события.
Схема базы данных для TON-индексатора
CREATE TABLE ton_transactions ( hash CHAR(64) PRIMARY KEY, lt BIGINT NOT NULL, account VARCHAR(66) NOT NULL, block_seqno INTEGER NOT NULL, timestamp TIMESTAMPTZ NOT NULL, in_msg_hash CHAR(64), value NUMERIC(38,0), opcode INTEGER, exit_code SMALLINT NOT NULL, compute_fee NUMERIC(38,0), raw_data BYTEA ); CREATE INDEX idx_ton_tx_account ON ton_transactions(account); CREATE INDEX idx_ton_tx_timestamp ON ton_transactions(timestamp DESC); CREATE INDEX idx_ton_tx_opcode ON ton_transactions(opcode) WHERE opcode IS NOT NULL; CREATE TABLE jetton_transfers ( id BIGSERIAL PRIMARY KEY, tx_hash CHAR(64) REFERENCES ton_transactions(hash), jetton_master VARCHAR(66) NOT NULL, from_address VARCHAR(66) NOT NULL, to_address VARCHAR(66) NOT NULL, amount NUMERIC(38,0) NOT NULL, timestamp TIMESTAMPTZ NOT NULL ); Мониторинг и надёжность
Ключевые метрики: отставание от masterchain (цель — менее 5 блоков), число ошибок парсинга, RPC latency. Реорганизации в TON редки, но возможны; мы храним сырые BOC для перепарсинга без повторных запросов.
Получите рабочий индексатор TON в сжатые сроки. Оценим нагрузку, предложим архитектуру и сроки при обращении. Гарантируем документацию, код и поддержку после запуска. Опыт: 5+ лет в DeFi, более 10 успешных индексаторов для разных сетей. Закажите разработку — получите готовое решение от 2 недель.







