Отслеживание трансфера
Предварительные шаги
Перед отслеживанием трансфера необходимо его отправить. См. Отправка трансфера для получения Transfer ID.
Transfer ID
Что это технически
Transfer ID — это transaction hash события на Proxy контракте.
┌─────────────────────────────────────────────────────────────────┐
│ Proxy Contract │
│ │
│ 1. Получает токены через transferNotification() │
│ 2. Вызывает _emitTvmEvent() или _deployEvmEvent() │
│ 3. Эмитит событие TvmTvmNative или TvmTvmAlien │
│ 4. Transaction hash этого события = TRANSFER ID │
└─────────────────────────────────────────────────────────────────┘Где в блокчейне находится
Transaction (на Proxy контракте) ← Transaction Hash = TRANSFER ID
├── In Message (входящие токены)
├── Out Messages
│ ├── Internal Messages (internal transfers)
│ └── External Out Message ← СОБЫТИЕ (TvmTvmNative/TvmTvmAlien)
└── Account (обновлённое состояние)Типы событий
| Событие | TokenType | Описание |
|---|---|---|
TvmTvmNative | Native | Трансфер native токена (lock → mint) |
TvmTvmAlien | Alien | Трансфер alien токена (burn → unlock) |
Статусы трансфера
Два уровня статусов
В системе существует два уровня статусов:
| Уровень | Где хранится | Назначение |
|---|---|---|
| TransferStatus | Bridge Aggregator API | Агрегированный статус для UI/интеграций |
| Event Contract Status | On-chain (Event контракт) | Детальное состояние верификации события |
Связь: Bridge Aggregator API агрегирует on-chain статусы Event контракта в упрощённый TransferStatus:
Event Contract Status → TransferStatus (API)
─────────────────────────────────────────────────────────────────────
Initializing, Pending, Verified → Pending
Confirmed, LiquidityProvided → Completed
Rejected, Cancelled → FailedTransferStatus (API level)
Используется в ответах Bridge Aggregator API (/v2/transfers/status, /v2/transfers/search).
| Статус | Код | Описание |
|---|---|---|
Pending | 1 | Событие создано, ожидает подтверждения в сети назначения |
Completed | 2 | Трансфер полностью завершён (токены доставлены) |
Failed | 3 | Трансфер отклонён релеями |
Event Contract Status (on-chain level)
Детальные статусы Event контракта в блокчейне. Используются для понимания на каком этапе верификации находится событие.
| Статус | Код | Описание | → TransferStatus |
|---|---|---|---|
Initializing | 0 | Event контракт задеплоен | Pending |
Pending | 1 | Ожидание верификации | Pending |
Confirmed | 2 | Подтверждён, proxy вызван, токены доставлены | Completed |
Rejected | 3 | Отклонён | Failed |
Cancelled | 4 | Отменён пользователем | Failed |
LimitReached | 5 | Превышен дневной лимит, требуется ликвидность | Pending |
LiquidityRequested | 6 | Запрос ликвидности создан | Pending |
LiquidityProvided | 7 | Ликвидность предоставлена, токены доставлены | Completed |
Verified | 8 | Merkle proof проверен, ожидание деплоя токенов | Pending |
Переход для trustless: Pending → Verified → Confirmed
Диаграмма переходов статусов

Получение статуса по Transfer ID
API Endpoint
POST https://tetra-history-api.chainconnect.com/v2/transfers/status
Content-Type: application/jsonТестовое окружение
Для тестирования используйте https://history-api-test.chainconnect.com/v2/transfers/status
Параметры запроса
Важно
Согласно OpenAPI спецификации, поле называется outgoingTransactionHash (не outgoingMessageHash)
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
outgoingTransactionHash | string | ✅ | Transfer ID (hex, 64 символа) |
dappChainId | number | ✅ | Chain ID сети отправления (TON = -239, Tycho = 2000) |
timestampCreatedFrom | number | ❌ | Unix timestamp для фильтрации (опционально) |
Пример запроса (TvmTvm)
curl -X POST 'https://tetra-history-api.chainconnect.com/v2/transfers/status' \
-H 'Content-Type: application/json' \
-d '{
"tvmTvm": {
"outgoingTransactionHash": "abc123def456...",
"dappChainId": -239,
"timestampCreatedFrom": null
}
}'Пример ответа
{
"transfer": {
"tvmTvm": {
"transferStatus": "Pending",
"timestampCreatedAt": 1767972717,
"outgoing": {
"tokenType": "Native",
"contractAddress": "aaaa...1111",
"chainId": -239,
"userAddress": "0:3333...cccc",
"tokenAddress": "0:1111...aaaa",
"proxyAddress": "0:6666...ffff",
"volumeExec": "0.1000",
"volumeUsdtExec": "0",
"feeVolumeExec": "0",
"messageHash": "abc123def456...",
"transactionHash": "def789abc012..."
},
"incoming": {
"tokenType": null,
"contractAddress": null,
"chainId": 2000,
"userAddress": "0:2222...bbbb",
"tokenAddress": null,
"proxyAddress": null,
"volumeExec": null,
"volumeUsdtExec": null,
"feeVolumeExec": null,
"messageHash": null,
"transactionHash": null
}
}
},
"notInstantTransfer": null,
"proofPayload": {
"txBlockProof": "te6ccgECCg...",
"txProof": "te6ccgEBBw...",
"messageHash": "abc123def456...",
"outMessageIndex": 0,
"event": {
"tokenType": "Native",
"chainId": 2000,
"token": "0:1111...aaaa",
"amount": "99000",
"recipient": "0:2222...bbbb",
"value": "150000000",
"expectedGas": "0",
"remainingGasTo": "0:3333...cccc",
"sender": "0:3333...cccc",
"payload": "te6ccgEBAQEAAgAAAA==",
"nativeProxyWallet": "0:9999...2222",
"name": "Tether USD",
"symbol": "USD₮",
"decimals": 6
},
"feeAmount": "1000"
}
}Описание полей ответа
transfer.tvmTvm — данные трансфера
| Поле | Тип | Описание |
|---|---|---|
transferStatus | string | Статус: "Pending", "Completed", "Failed" |
timestampCreatedAt | number | Unix timestamp создания трансфера |
outgoing / incoming — данные сторон трансфера
| Поле | Тип | Описание |
|---|---|---|
tokenType | string | null | "Native" или "Alien" |
contractAddress | string | null | Адрес event контракта (hex без 0:) |
chainId | number | Chain ID сети |
userAddress | string | Адрес пользователя (отправитель/получатель) |
tokenAddress | string | null | Адрес токена в этой сети |
proxyAddress | string | null | Адрес proxy контракта |
volumeExec | string | null | Сумма в human-readable формате |
volumeUsdtExec | string | null | Сумма в USDT эквиваленте |
feeVolumeExec | string | null | Удержанная комиссия |
messageHash | string | null | Hash сообщения события |
transactionHash | string | null | Hash транзакции (Transfer ID для outgoing) |
proofPayload — данные для non-credit трансферов
| Поле | Тип | Описание |
|---|---|---|
txBlockProof | string | Proof блока (BOC base64) |
txProof | string | Merkle proof транзакции (BOC base64) |
messageHash | string | Hash сообщения события |
outMessageIndex | number | Индекс исходящего сообщения в транзакции |
event | object | Данные события (см. ниже) |
feeAmount | string | null | Комиссия в nano-единицах |
proofPayload.event — данные события
| Поле | Тип | Описание |
|---|---|---|
tokenType | string | "Native" или "Alien" |
chainId | number | Chain ID сети назначения |
token | string | Адрес токена |
amount | string | Сумма в nano-единицах |
recipient | string | Адрес получателя |
value | string | Прикреплённое значение (газ) |
expectedGas | string | Ожидаемый газ |
remainingGasTo | string | Адрес для возврата газа |
sender | string | Адрес отправителя |
payload | string | Дополнительный payload (BOC base64) |
name | string | Название токена |
symbol | string | Символ токена |
decimals | number | Количество decimals |
notInstantTransfer — данные liquidity request
Заполняется когда трансфер требует ликвидности (статус LimitReached → LiquidityRequested).
| Поле | Тип | Описание |
|---|---|---|
liquidityRequestAddress | string | Адрес контракта запроса ликвидности |
status | string | Статус запроса |
amount | string | Запрашиваемая сумма |
Интерпретация ответа
| Поле | Значение | Действие |
|---|---|---|
transferStatus: "Pending" | Ожидание | Продолжить polling |
transferStatus: "Completed" | Завершён | Успех! |
transferStatus: "Failed" | Ошибка | Обработать ошибку |
proofPayload != null | Proof готов | Можно деплоить event (non-credit) |
incoming.transactionHash != null | Вторая часть выполнена | Трансфер почти завершён |
История трансферов
API Endpoint
POST https://tetra-history-api.chainconnect.com/v2/transfers/search
Content-Type: application/jsonПараметры запроса
| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
userAddresses | string[] | ❌ | Фильтр по адресам пользователя (макс 7) |
transferKinds | string[] | ❌ | Тип трансфера: ["TvmToTvm"] |
statuses | string[] | ❌ | Фильтр по статусам: ["Pending", "Completed", "Failed"] |
fromTvmChainId | number | ❌ | Chain ID сети отправления |
toTvmChainId | number | ❌ | Chain ID сети назначения |
createdAtGe | number | ❌ | Unix timestamp >= (от какой даты) |
createdAtLe | number | ❌ | Unix timestamp <= (до какой даты) |
ordering | string | ❌ | Сортировка: "CreatedAtAscending" или "CreatedAtDescending" |
limit | number | ✅ | Лимит записей (макс количество в ответе) |
offset | number | ✅ | Смещение (для пагинации) |
isNeedTotalCount | boolean | ✅ | Возвращать ли общее количество трансферов |
Пример запроса
curl -X POST 'https://tetra-history-api.chainconnect.com/v2/transfers/search' \
-H 'Content-Type: application/json' \
-d '{
"userAddresses": ["0:3333...cccc"],
"transferKinds": ["TvmToTvm"],
"statuses": ["Pending", "Completed"],
"fromTvmChainId": -239,
"toTvmChainId": 2000,
"ordering": "CreatedAtDescending",
"limit": 10,
"offset": 0,
"isNeedTotalCount": true
}'Пример ответа
{
"transfers": [
{
"tvmTvm": {
"transferStatus": "Completed",
"timestampCreatedAt": 1767972717,
"outgoing": {
"tokenType": "Native",
"contractAddress": "aaaa...1111",
"chainId": -239,
"userAddress": "0:3333...cccc",
"tokenAddress": "0:1111...aaaa",
"proxyAddress": "0:6666...ffff",
"volumeExec": "1.0000",
"transactionHash": "abc123..."
},
"incoming": {
"tokenType": "Alien",
"contractAddress": "bbbb...2222",
"chainId": 2000,
"userAddress": "0:2222...bbbb",
"tokenAddress": "0:4444...dddd",
"proxyAddress": "0:7777...0000",
"volumeExec": "0.9990",
"transactionHash": "def456..."
}
}
}
],
"totalCount": 42
}Transfer ID для каждого трансфера: transfers[i].tvmTvm.outgoing.transactionHash
Алгоритм отслеживания
┌─────────────────────────────────────────────────────────────────────────┐
│ 1. ПОЛУЧИТЬ TRANSFER ID │
│ После отправки токенов на Proxy контракт, сохранить transaction │
│ hash этой транзакции — это и есть Transfer ID │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ 2. ЗАПРОСИТЬ СТАТУС │
│ POST /v2/transfers/status с Transfer ID и Chain ID │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ 3. ПРОВЕРИТЬ transferStatus │
│ │
│ "Pending" → Трансфер в процессе, повторить запрос через 5-10 сек │
│ "Completed" → Успех! Токены доставлены получателю │
│ "Failed" → Ошибка, трансфер отклонён │
└─────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────┐
│ 4. ДЛЯ ЗАВЕРШЁННОГО ТРАНСФЕРА │
│ incoming.transactionHash — hash транзакции доставки токенов │
│ incoming.volumeExec — полученная сумма (после комиссий) │
└─────────────────────────────────────────────────────────────────────────┘Что проверять в ответе
| Поле | Что означает |
|---|---|
transferStatus: "Pending" | Трансфер в процессе — повторить запрос |
transferStatus: "Completed" | Трансфер завершён — токены доставлены |
transferStatus: "Failed" | Трансфер отклонён — обработать ошибку |
incoming.transactionHash != null | Транзакция доставки выполнена |
proofPayload != null | Merkle proof готов (для non-credit трансферов) |
Credit vs Non-credit трансферы
| Тип | Описание | Действия пользователя |
|---|---|---|
| Credit | Релеи автоматически деплоят event и доставляют токены | Только отслеживать статус |
| Non-credit | Пользователь сам деплоит event контракт | Дождаться proofPayload, задеплоить event |
Для non-credit трансферов:
- Дождаться появления
proofPayloadв ответе - Использовать
proofPayload.txBlockProofиproofPayload.txProofдля деплоя Event контракта в сети назначения - После деплоя Event контракта трансфер завершится автоматически
Пример: TypeScript polling
async function waitForTransferCompletion(
transferId: string,
chainId: number,
maxAttempts = 60,
initialDelayMs = 5000
): Promise<TransferStatusResponse> {
let delay = initialDelayMs;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
const response = await fetch('https://tetra-history-api.chainconnect.com/v2/transfers/status', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
tvmTvm: {
outgoingTransactionHash: transferId,
dappChainId: chainId,
},
}),
});
const status = await response.json();
if (status.transfer.tvmTvm?.transferStatus === 'Completed') {
console.log(`Transfer completed after ${attempt} attempts`);
return status;
}
if (status.transfer.tvmTvm?.transferStatus === 'Failed') {
throw new Error('Transfer failed');
}
console.log(`Attempt ${attempt}: status = ${status.transfer.tvmTvm?.transferStatus}`);
await new Promise(resolve => setTimeout(resolve, delay));
delay = Math.min(delay * 1.2, 30000); // Max 30 секунд
}
throw new Error('Transfer timeout');
}
// Использование
const transferId = 'abc123def456...'; // Transfer ID из Шага 3 отправки трансфера
const result = await waitForTransferCompletion(transferId, -239);
console.log('Transfer completed:', result.transfer.tvmTvm?.incoming);Получение статуса из контракта (on-chain)
Помимо Bridge Aggregator API, статус трансфера можно получить напрямую из Event контракта в блокчейне. Это полезно для:
- Верификации данных API
- Работы без зависимости от централизованного API
- Получения детального on-chain статуса
Адрес Event контракта
Адрес Event контракта можно получить из ответа API:
transfer.tvmTvm.outgoing.contractAddress— hex адрес без префикса0:
Чтение статуса из контракта
import { TonClient, Address } from '@ton/ton';
// ABI метода getDetails для Event контракта
const EVENT_CONTRACT_ABI = {
getDetails: {
name: 'getDetails',
inputs: [],
outputs: [
{ name: 'status', type: 'uint8' },
{ name: 'sender', type: 'address' },
{ name: 'recipient', type: 'address' },
{ name: 'amount', type: 'uint128' },
// ... другие поля
],
},
};
// Маппинг числовых статусов в строковые
const EVENT_STATUS = {
0: 'Initializing',
1: 'Pending',
2: 'Confirmed',
3: 'Rejected',
4: 'Cancelled',
5: 'LimitReached',
6: 'LiquidityRequested',
7: 'LiquidityProvided',
8: 'Verified',
} as const;
async function getEventContractStatus(
client: TonClient,
eventContractAddress: string
): Promise<{ status: string; statusCode: number }> {
const address = Address.parse(`0:${eventContractAddress}`);
// Вызываем get-метод контракта
const result = await client.runMethod(address, 'getDetails');
// Парсим результат
const statusCode = result.stack.readNumber();
const status = EVENT_STATUS[statusCode as keyof typeof EVENT_STATUS] || 'Unknown';
return { status, statusCode };
}
// Использование
const client = new TonClient({
endpoint: 'https://toncenter.com/api/v2/jsonRPC',
});
// contractAddress из ответа API (без 0:)
const eventContractAddress = 'aaaa...1111';
const { status, statusCode } = await getEventContractStatus(client, eventContractAddress);
console.log(`Event status: ${status} (code: ${statusCode})`);Полные данные Event контракта
Для получения всех данных события используйте метод getDetails:
interface EventDetails {
status: number;
sender: string;
recipient: string;
amount: bigint;
token: string;
chainId: number;
// ... другие поля зависят от типа события
}
async function getFullEventDetails(
client: TonClient,
eventContractAddress: string
): Promise<EventDetails> {
const address = Address.parse(`0:${eventContractAddress}`);
const result = await client.runMethod(address, 'getDetails');
return {
status: result.stack.readNumber(),
sender: result.stack.readAddress().toString(),
recipient: result.stack.readAddress().toString(),
amount: result.stack.readBigNumber(),
token: result.stack.readAddress().toString(),
chainId: result.stack.readNumber(),
};
}Важно
Структура ответа getDetails может отличаться для разных версий Event контрактов (Native vs Alien). Проверяйте ABI контракта перед парсингом.