Разработка индексатора TON blockchain
TON — одна из сложнейших блокчейн-платформ для индексации. Причина в архитектуре: TON использует infinite sharding — количество шардов динамически меняется в зависимости от нагрузки. Транзакции внутри одного шарда финализируются быстро, но транзакция между аккаунтами в разных шардах это цепочка сообщений, которую нужно отслеживать через несколько блоков и шардов. Стандартный подход "слушаем блоки по одному" здесь не работает.
Архитектура 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 ...
TON API vs запуск своего узла
Hosted API (быстрый старт)
- TonAPI (tonapi.io) — богатый REST + WebSocket, поддерживает events, transactions, traces; free tier с rate limits
-
TON Center (
toncenter.com/api/v2) — официальный публичный API, есть платный тариф без rate limits - GetBlock.io, Chainbase — enterprise RPC
Hosted API — правильный выбор для MVP и средних нагрузок.
Собственный узел
Полный TON архив (~2 TB, растёт ~500 GB/год) нужен при:
- Необходимости в трассировке транзакций (
runGetMethodпо историческим блокам) - Rate limits hosted API становятся bottleneck
- Требования к SLA
# TON node (C++ реализация) — сложная сборка из исходников
git clone --recurse-submodules https://github.com/ton-blockchain/ton
cd ton && mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
make -j$(nproc) validator-engine
# Или tonutils-go / tonutils-go для Go-инструментов
Более простой путь — mytonctrl (скрипт Ton Foundation для установки ноды):
wget https://raw.githubusercontent.com/ton-blockchain/mytonctrl/master/scripts/install.sh
sudo bash install.sh -m full
Реализация индексера: основной цикл
import { TonClient4, Cell, loadTransaction } from '@ton/ton';
class TonIndexer {
private client: TonClient4;
private db: Pool;
private lastMasterBlock: number;
async indexLoop(): Promise<void> {
while (true) {
try {
// Получаем актуальный masterchain блок
const masterInfo = await this.client.getLastBlock();
const currentSeqno = masterInfo.last.seqno;
if (currentSeqno <= this.lastMasterBlock) {
await this.sleep(2000); // Ждём новый блок (~5 сек в TON)
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, // workchain -1 = masterchain
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);
}
}
}
Транзакции TON: структура и парсинг
Транзакция в TON имеет:
-
in_msg— входящее сообщение (причина выполнения) -
out_msgs— исходящие сообщения (результат выполнения) -
compute_ph— фаза вычислений (gas, exit code) -
action_ph— фаза действий (отправка исходящих сообщений)
interface IndexedTransaction {
hash: string;
lt: bigint; // logical time
account: string;
inMsgHash?: string;
value?: bigint; // nanoTON
opcode?: number; // первые 4 байта in_msg body
exitCode: number;
computeFee: bigint;
timestamp: number;
blockSeqno: number;
}
function parseTransaction(rawTx: RawTransaction): IndexedTransaction {
const inMsg = rawTx.inMessage;
let opcode: number | undefined;
if (inMsg?.body) {
// Первые 4 байта body — это op code для Jetton/NFT/DEX протоколов
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,
};
}
Op-коды Jetton: индексация переводов токенов
Jetton Transfer имеет стандартный op-код 0x0f8a7ea5. Для индексации всех Jetton переводов нужно мониторить транзакции на JettonWallet контрактах с этим op-кодом:
const JETTON_TRANSFER_OP = 0x0f8a7ea5;
const JETTON_TRANSFER_NOTIFICATION_OP = 0x7362d09c;
function isJettonTransfer(tx: IndexedTransaction): boolean {
return tx.opcode === JETTON_TRANSFER_OP;
}
function parseJettonTransferNotification(body: Cell): {
amount: bigint;
sender: Address;
forwardPayload: Cell | null;
} {
const slice = body.beginParse();
slice.loadUint(32); // op
slice.loadUint(64); // query_id
const amount = slice.loadCoins();
const sender = slice.loadAddress();
const hasPayload = slice.loadBit();
const forwardPayload = hasPayload ? slice.loadRef() : null;
return { amount, sender, forwardPayload };
}
Трассировка транзакций (Trace)
Для DEX аналитики, бот-детекции, и аудита нужна полная трассировка: одна входящая транзакция порождает дерево вызовов. TonAPI предоставляет /v2/traces/{hash} — граф всех связанных транзакций.
async function getTransactionTrace(txHash: string) {
const response = await fetch(
`https://tonapi.io/v2/traces/${txHash}`,
{ headers: { 'Authorization': `Bearer ${TONAPI_KEY}` } }
);
const trace = await response.json();
// trace.transaction + trace.children[] — рекурсивное дерево
return trace;
}
База данных: схема для 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), -- nanoTON
opcode INTEGER,
exit_code SMALLINT NOT NULL,
compute_fee NUMERIC(38,0),
raw_data BYTESEA -- оригинальный BOC для повторного парсинга
);
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;
-- Для Jetton индексации
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 —
current_seqno - indexed_seqnoдолжно быть < 5 - Количество транзакций в очереди — если растёт, обработчик не успевает
- Ошибки парсинга — некорректный BOC в raw_data
- RPC latency — время ответа TonAPI/TonCenter
Реорганизации в TON редки, но возможны для свежих блоков. Храним raw_data (BOC) каждой транзакции — при обнаружении расхождения перепарсим из сырых данных без повторного обращения к сети.







