Skip to content
ChainConnect

Отслеживание трансфера

Предварительные шаги

Перед отслеживанием трансфера необходимо его отправить. См. Отправка трансфера для получения 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Описание
TvmTvmNativeNativeТрансфер native токена (lock → mint)
TvmTvmAlienAlienТрансфер alien токена (burn → unlock)

Статусы трансфера

Два уровня статусов

В системе существует два уровня статусов:

УровеньГде хранитсяНазначение
TransferStatusBridge Aggregator APIАгрегированный статус для UI/интеграций
Event Contract StatusOn-chain (Event контракт)Детальное состояние верификации события

Связь: Bridge Aggregator API агрегирует on-chain статусы Event контракта в упрощённый TransferStatus:

Event Contract Status                    →    TransferStatus (API)
─────────────────────────────────────────────────────────────────────
Initializing, Pending, Verified          →    Pending
Confirmed, LiquidityProvided             →    Completed
Rejected, Cancelled                      →    Failed

TransferStatus (API level)

Используется в ответах Bridge Aggregator API (/v2/transfers/status, /v2/transfers/search).

СтатусКодОписание
Pending1Событие создано, ожидает подтверждения в сети назначения
Completed2Трансфер полностью завершён (токены доставлены)
Failed3Трансфер отклонён релеями

Event Contract Status (on-chain level)

Детальные статусы Event контракта в блокчейне. Используются для понимания на каком этапе верификации находится событие.

СтатусКодОписание→ TransferStatus
Initializing0Event контракт задеплоенPending
Pending1Ожидание верификацииPending
Confirmed2Подтверждён, proxy вызван, токены доставленыCompleted
Rejected3ОтклонёнFailed
Cancelled4Отменён пользователемFailed
LimitReached5Превышен дневной лимит, требуется ликвидностьPending
LiquidityRequested6Запрос ликвидности созданPending
LiquidityProvided7Ликвидность предоставлена, токены доставленыCompleted
Verified8Merkle proof проверен, ожидание деплоя токеновPending

Переход для trustless: PendingVerifiedConfirmed

Диаграмма переходов статусов

Диаграмма переходов статусов

Получение статуса по 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)

ПараметрТипОбязательныйОписание
outgoingTransactionHashstringTransfer ID (hex, 64 символа)
dappChainIdnumberChain ID сети отправления (TON = -239, Tycho = 2000)
timestampCreatedFromnumberUnix timestamp для фильтрации (опционально)

Пример запроса (TvmTvm)

bash
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
    }
  }'

Пример ответа

json
{
  "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 — данные трансфера

ПолеТипОписание
transferStatusstringСтатус: "Pending", "Completed", "Failed"
timestampCreatedAtnumberUnix timestamp создания трансфера

outgoing / incoming — данные сторон трансфера

ПолеТипОписание
tokenTypestring | null"Native" или "Alien"
contractAddressstring | nullАдрес event контракта (hex без 0:)
chainIdnumberChain ID сети
userAddressstringАдрес пользователя (отправитель/получатель)
tokenAddressstring | nullАдрес токена в этой сети
proxyAddressstring | nullАдрес proxy контракта
volumeExecstring | nullСумма в human-readable формате
volumeUsdtExecstring | nullСумма в USDT эквиваленте
feeVolumeExecstring | nullУдержанная комиссия
messageHashstring | nullHash сообщения события
transactionHashstring | nullHash транзакции (Transfer ID для outgoing)

proofPayload — данные для non-credit трансферов

ПолеТипОписание
txBlockProofstringProof блока (BOC base64)
txProofstringMerkle proof транзакции (BOC base64)
messageHashstringHash сообщения события
outMessageIndexnumberИндекс исходящего сообщения в транзакции
eventobjectДанные события (см. ниже)
feeAmountstring | nullКомиссия в nano-единицах

proofPayload.event — данные события

ПолеТипОписание
tokenTypestring"Native" или "Alien"
chainIdnumberChain ID сети назначения
tokenstringАдрес токена
amountstringСумма в nano-единицах
recipientstringАдрес получателя
valuestringПрикреплённое значение (газ)
expectedGasstringОжидаемый газ
remainingGasTostringАдрес для возврата газа
senderstringАдрес отправителя
payloadstringДополнительный payload (BOC base64)
namestringНазвание токена
symbolstringСимвол токена
decimalsnumberКоличество decimals

notInstantTransfer — данные liquidity request

Заполняется когда трансфер требует ликвидности (статус LimitReachedLiquidityRequested).

ПолеТипОписание
liquidityRequestAddressstringАдрес контракта запроса ликвидности
statusstringСтатус запроса
amountstringЗапрашиваемая сумма

Интерпретация ответа

ПолеЗначениеДействие
transferStatus: "Pending"ОжиданиеПродолжить polling
transferStatus: "Completed"ЗавершёнУспех!
transferStatus: "Failed"ОшибкаОбработать ошибку
proofPayload != nullProof готовМожно деплоить event (non-credit)
incoming.transactionHash != nullВторая часть выполненаТрансфер почти завершён

История трансферов

API Endpoint

POST https://tetra-history-api.chainconnect.com/v2/transfers/search
Content-Type: application/json

Параметры запроса

ПараметрТипОбязательныйОписание
userAddressesstring[]Фильтр по адресам пользователя (макс 7)
transferKindsstring[]Тип трансфера: ["TvmToTvm"]
statusesstring[]Фильтр по статусам: ["Pending", "Completed", "Failed"]
fromTvmChainIdnumberChain ID сети отправления
toTvmChainIdnumberChain ID сети назначения
createdAtGenumberUnix timestamp >= (от какой даты)
createdAtLenumberUnix timestamp <= (до какой даты)
orderingstringСортировка: "CreatedAtAscending" или "CreatedAtDescending"
limitnumberЛимит записей (макс количество в ответе)
offsetnumberСмещение (для пагинации)
isNeedTotalCountbooleanВозвращать ли общее количество трансферов

Пример запроса

bash
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
  }'

Пример ответа

json
{
  "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 != nullMerkle proof готов (для non-credit трансферов)

Credit vs Non-credit трансферы

ТипОписаниеДействия пользователя
CreditРелеи автоматически деплоят event и доставляют токеныТолько отслеживать статус
Non-creditПользователь сам деплоит event контрактДождаться proofPayload, задеплоить event

Для non-credit трансферов:

  1. Дождаться появления proofPayload в ответе
  2. Использовать proofPayload.txBlockProof и proofPayload.txProof для деплоя Event контракта в сети назначения
  3. После деплоя Event контракта трансфер завершится автоматически

Пример: TypeScript polling

typescript
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:

Чтение статуса из контракта

typescript
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:

typescript
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 контракта перед парсингом.

ChainConnect Bridge Documentation