Разработка индексатора TON blockchain

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1Все 1306 услуг
Разработка индексатора TON blockchain
Сложный
~1-2 недели
Часто задаваемые вопросы

Направления блокчейн-разработки

Этапы блокчейн-разработки

Последние работы

  • image_website-b2b-advance_0.webp
    Разработка сайта компании B2B ADVANCE
    1308
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1222
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    921
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1149
  • image_logo-advance_0.webp
    Разработка логотипа компании B2B Advance
    612
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    886

Разработка индексатора TON blockchain

TON — одна из сложнейших блокчейн-платформ для индексации. Причина в архитектуре: TON использует infinite sharding — количество шардов динамически меняется в зависимости от нагрузки. Транзакции внутри одного шарда финализируются быстро, но транзакция между аккаунтами в разных шардах это цепочка сообщений, которую нужно отслеживать через несколько блоков и шардов. Стандартный подход "слушаем блоки по одному" здесь не работает.

Архитектура TON: что нужно понять перед индексацией

TON состоит из трёх уровней:

  • Masterchain — главная цепь, финализирует состояние всех воркчейнов
  • Basechain (workchain 0) — основная пользовательская цепь
  • Shardchains — шарды workchain-а, их может быть от 1 до 256

Каждый masterchain блок ссылается на последние блоки всех шардов (ShardStateUnsplit). Для полного индексирования нужно:

  1. Получить masterchain блок
  2. Из него извлечь список актуальных шард-блоков
  3. Из каждого шард-блока извлечь транзакции
  4. Для каждой транзакции — отследить дочерние сообщения (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
);

Мониторинг индексера

Ключевые метрики:

  • Отставание от masterchaincurrent_seqno - indexed_seqno должно быть < 5
  • Количество транзакций в очереди — если растёт, обработчик не успевает
  • Ошибки парсинга — некорректный BOC в raw_data
  • RPC latency — время ответа TonAPI/TonCenter

Реорганизации в TON редки, но возможны для свежих блоков. Храним raw_data (BOC) каждой транзакции — при обнаружении расхождения перепарсим из сырых данных без повторного обращения к сети.