Индексация jetton-трансферов через TonAPI

Получишь историю Jetton-переводов через TonAPI: список JettonTransfer-событий по аккаунту без ручного парсинга BOC.

Зачем TonAPI

On-chain transfer — цепочка внутренних сообщений. Парсить BOC с RPC неудобно для UI. TonAPI индексирует jetton-события и отдаёт REST: кто → кому, amount, tx hash, время.

Для отправки транзакций по-прежнему нужен кошелёк / @ton/ton; TonAPI — для чтения истории.


События JettonTransfer аккаунта

typescript
// TonAPI REST v2 — https://tonapi.io
// Header Authorization: Bearer <API_KEY> желателен (без ключа — жёсткий rate limit)

type JettonTransferAction = {
  amount: string;
  sender: { address: string };
  recipient: { address: string };
  jetton: {
    address: string;
    symbol?: string;
    decimals: number;
  };
};

type IndexedTransfer = JettonTransferAction & {
  eventId: string;
  timestamp: number;
};

async function fetchJettonTransfers(
  account: string,
  opts?: { apiKey?: string; limit?: number },
): Promise<IndexedTransfer[]> {
  const limit = opts?.limit ?? 30;
  const url =
    `https://tonapi.io/v2/accounts/${encodeURIComponent(account)}/events` +
    `?limit=${limit}`;

  const headers: Record<string, string> = { Accept: "application/json" };
  if (opts?.apiKey) headers.Authorization = `Bearer ${opts.apiKey}`;

  const res = await fetch(url, { headers });
  if (res.status === 429) {
    throw new Error("TonAPI rate limit — см. /docs/tonapi-rate-limit-429");
  }
  if (!res.ok) {
    throw new Error(`TonAPI ${res.status}: ${await res.text()}`);
  }

  const data = (await res.json()) as {
    events: Array<{
      event_id: string;
      timestamp: number;
      actions: Array<{
        type: string;
        JettonTransfer?: JettonTransferAction;
      }>;
    }>;
  };

  return data.events.flatMap((e) =>
    e.actions
      .filter((a) => a.type === "JettonTransfer" && a.JettonTransfer)
      .map((a) => ({
        eventId: e.event_id,
        timestamp: e.timestamp,
        ...a.JettonTransfer!,
      })),
  );
}

const transfers = await fetchJettonTransfers(
  "EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N",
);

for (const t of transfers) {
  const human = Number(t.amount) / 10 ** t.jetton.decimals;
  console.log(
    `${t.sender.address} → ${t.recipient.address}: ${human} ${t.jetton.symbol ?? ""}`,
  );
}

Testnet: https://testnet.tonapi.io. Фильтр по конкретному master — на клиенте по t.jetton.address или через узкие эндпоинты /jettons/... в актуальной OpenAPI TonAPI.

Как это работает

  • TonAPI индексирует transfer / transfer_notification → action JettonTransfer.
  • amount — в минимальных единицах; деление через decimals токена.
  • Без API-ключа — rate limits; для продакшена ключ + бэкенд-прокси.
  • Для «живого» баланса дублируй on-chain get_wallet_data — индекс может отставать на секунды.

Частые ошибки

Путаешь testnet/mainnet хост → пустая история или чужие адреса.

Показываешь amount без decimals → в UI «1000000» вместо «1».

Дергаешь TonAPI с клиента на каждый keystroke → 429. Кэш + debounce; см. rate limit.


Что дальше

Материалы gramdocs.tech носят образовательный характер и не являются финансовой, юридической или инвестиционной рекомендацией. Работа с блокчейном TON и токеном Gram связана с рисками потери средств. Правовая информация