Skip to content

Отправка трансфера

Обзор

Трансфер TVM↔TVM позволяет отправить токены между TVM сетями (например, TON ↔ Tetra L2). Процесс включает:

  1. Build Payload — получить данные для транзакции через API
  2. Отправка транзакции — отправить транзакцию в TVM (transfer через Proxy)
  3. Получение Transfer ID — найти hash транзакции с событием на Proxy контракте
  4. Запрос Proof (non-credit) — получить доказательство транзакции через API
  5. Деплой Event контракта (non-credit) — задеплоить Event контракт в сети назначения

Механизм верификации (Trustless)

Для TVM↔TVM верификация выполняется без relay-нод, через криптографическое доказательство транзакции:

  • Proxy контракт в сети отправления эмитит событие TvmTvmNative или TvmTvmAlien
  • API генерирует Merkle proof транзакции (поля txBlockProof и txProof)
  • Event контракт в сети назначения верифицирует proof через TransactionCheckerLiteClient
  • После успешной верификации Proxy в сети назначения mint'ит/unlock'ает токены

Credit vs Non-credit

Credit (useCredit: true)Non-credit (useCredit: false)
Кто деплоит EventGas Credit Backend автоматическиПользователь вручную (Шаг 4–5)
Нужен ли ProofНет (Backend получает сам)Да (через API)
Кол-во транзакций1 (отправка)2 (отправка + deployEvent)
Газ в сети назначенияОплачивает Gas Credit BackendОплачивает пользователь
СкоростьБыстрееЗависит от пользователя

Рекомендация

Для большинства случаев используйте useCredit: true (значение по умолчанию). Non-credit нужен, когда вы хотите контролировать процесс деплоя Event контракта самостоятельно.

Типы токенов

ТипОписание
NativeТокен, изначально созданный в данной сети (jetton / TIP-3). При трансфере блокируется (lock) в Proxy-контракте исходной сети, а в сети назначения создаётся соответствующий Alien токен.
AlienОбёрнутое (wrapped) представление токена из другой сети. Создаётся (mint) при входящем трансфере, обеспечен заблокированными Native токенами в исходной сети. При обратном трансфере сжигается (burn).

API Base URL

ОкружениеBase URL
Productionhttps://tetra-history-api.chainconnect.com/v2
Testnethttps://history-api-test.chainconnect.com/v2

Шаг 1: Подготовка трансфера (Build Payload)

Цель: Получить payload для отправки транзакции в блокчейн

API Endpoint

POST {BASE_URL}/payload/build
Content-Type: application/json

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

typescript
interface TransferPayloadRequest {
  fromChainId: number;        // Chain ID сети отправления
  toChainId: number;          // Chain ID сети назначения
  tokenAddress: string;       // Адрес токена в сети отправления (формат `0:...`)
  recipientAddress: string;   // Адрес получателя в сети назначения
  amount: string;             // Сумма в nano-единицах (integer string)
  senderAddress: string;      // Адрес отправителя в сети отправления

  useCredit?: boolean;        // `true` = Credit Backend оплачивает газ в сети назначения. Default: `true`
  remainingGasTo: string;     // Куда вернуть остаток газа (обязательный для TVM→TVM)
  payload?: string;           // Дополнительный payload
  callback?: CallbackRequest; // Callback
  tokenBalance?: {            // Для native currency (wrapped native token)
    nativeTokenAmount: string; // Баланс native currency (для wrapped native token трансферов)
    wrappedNativeTokenAmount: string; //  Баланс wrapped native (для wrapped native token трансферов)
  };
}
ПараметрТипОбязательныйОписание
fromChainIdnumberChain ID сети отправления
toChainIdnumberChain ID сети назначения
tokenAddressstringАдрес токена в сети отправления (формат 0:...)
recipientAddressstringАдрес получателя в сети назначения
amountstringСумма в nano-единицах (integer string, напр. "1000000" для 1 USDT с 6 decimals)
senderAddressstringАдрес отправителя в сети отправления
useCreditbooleantrue = Credit Backend оплачивает газ в сети назначения. Default: true
remainingGasTostringКуда вернуть остаток газа. Обязателен для TVM→TVM
tokenBalance.nativeTokenAmountstring⚠️Баланс газового токена (native currency). Обязателен при отправке газового токена
tokenBalance.wrappedNativeTokenAmountstring⚠️Баланс wrapped версии газового токена (например, wTON). Обязателен при отправке газового токена

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

bash
curl -X POST '{BASE_URL}/payload/build' \
  -H 'Content-Type: application/json' \
  -d '{
    "fromChainId": -239,
    "toChainId": 2000,
    "tokenAddress": "0:1111...aaaa",
    "recipientAddress": "0:2222...bbbb",
    "amount": "1000000",
    "senderAddress": "0:3333...cccc",
    "useCredit": false,
    "remainingGasTo": "0:3333...cccc"
  }'

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

json
{
  "transferKind": "TvmToTvm",
  "tokensMeta": {
    "targetToken": {
      "tokenType": "Alien",
      "isDeployed": true,
      "address": "0:4444...dddd"
    },
    "sourceTokenType": "Native"
  },
  "tokenAmount": "999000",
  "feesMeta": {
    "amount": "1000",
    "numerator": "1",
    "denominator": "1000"
  },
  "gasEstimateAmount": "155113636",
  "abiMeta": {
    "tx": "te6ccgEBBwEA6AABiw/X8c8...",
    "executionAddress": "0:5555...eeee",
    "attachedValue": "155113636",
    "abiMethod": "transfer",
    "abi": "...",
    "params": "{...}"
  },
  "trackingMeta": {
    "sourceProxy": "0:6666...ffff",
    "targetProxy": "0:7777...0000",
    "targetConfiguration": "0:8888...1111"
  },
  "payload": null
}

Поля ответа

ПолеОписаниеИспользование
transferKindТип трансфера (TvmToTvm)Подтверждение направления
tokenAmountИтоговая сумма после вычета комиссииСумма, которую получит получатель
feesMeta.amountРазмер комиссии мостаИнформация о комиссии
abiMeta.executionAddressАдрес контракта для транзакцииПолучатель TVM транзакции (Шаг 2)
abiMeta.txПолный BOC вызова (base64)Готовый body для TonConnect (Шаг 2)
abiMeta.abiABI контракта (JSON string)Для вызова через TVM SDK (Шаг 2)
abiMeta.abiMethodМетод контракта (transfer)Имя метода для вызова (Шаг 2)
abiMeta.paramsПараметры метода (JSON string)Параметры для вызова через TVM SDK (Шаг 2)
abiMeta.attachedValueКоличество nanoTON для отправкиГаз TVM транзакции (Шаг 2)
trackingMeta.sourceProxyАдрес Proxy контрактаДля получения Transfer ID (Шаг 3)
trackingMeta.targetConfigurationАдрес EventConfiguration в сети назначенияДля non-credit: используется в deployEvent (Шаг 5)

Шаг 2: Отправка транзакции в блокчейн

Цель: Выполнить транзакцию и получить Transaction Hash

Что потребуется из Шага 1

ПараметрОписаниеИспользование
abiMeta.txПолный BOC вызова transferДля TonConnect — готовый body сообщения
abiMeta.abiABI контракта (JSON)Для TVM SDK — создание Contract
abiMeta.abiMethod"transfer"Для TVM SDK — имя метода
abiMeta.paramsПараметры метода (JSON)Для TVM SDK — аргументы вызова
abiMeta.executionAddressАдрес контрактаПолучатель транзакции
abiMeta.attachedValueГаз в nanoTONAmount транзакции

Отправка транзакции

typescript
import TonConnectUI from '@tonconnect/ui';
import { Cell } from '@ton/core';

interface TransferPayloadResponse {
  abiMeta: {
    tx: string;              // BOC base64
    executionAddress: string; // Адрес контракта
    attachedValue: string;   // nanoTON
    abiMethod: string;
  };
  trackingMeta: {
    sourceProxy: string;
    targetProxy: string;
    targetConfiguration: string;
  };
}

async function sendTransfer(
  tonConnectUI: TonConnectUI,
  payloadResponse: TransferPayloadResponse
): Promise<string> {
  const { abiMeta } = payloadResponse;

  console.log(`Отправляем ${abiMeta.abiMethod} транзакцию...`);
  console.log('Адрес контракта:', abiMeta.executionAddress);
  console.log('Газ:', abiMeta.attachedValue, 'nanoTON');

  // Формируем транзакцию
  // abiMeta.tx — готовый BOC с закодированным вызовом transfer,
  // включая сумму токенов и адрес получателя.
  // abiMeta.attachedValue — газ (nanoTON), не сумма токенов.
  const transaction = {
    validUntil: Math.floor(Date.now() / 1000) + 600, // 10 минут
    messages: [
      {
        address: abiMeta.executionAddress,  // Адрес контракта
        amount: abiMeta.attachedValue,      // Газ (nanoTON)
        payload: abiMeta.tx,                // BOC base64 из API
      },
    ],
  };

  // Отправляем через TonConnect
  const result = await tonConnectUI.sendTransaction(transaction);

  // TonConnect возвращает BOC (сериализованное сообщение), а не transaction hash.
  // Извлекаем message hash из BOC — он используется в TonAPI для поиска trace.
  const messageHash = Cell.fromBase64(result.boc).hash().toString('hex');
  console.log('Транзакция отправлена, message hash:', messageHash);

  return messageHash;
}
typescript
import { Address, ProviderRpcClient } from 'everscale-inpage-provider';

interface TransferPayloadResponse {
  abiMeta: {
    tx: string;              // BOC base64 
    abi: string;             // ABI контракта (JSON string)
    abiMethod: string;       // Метод контракта
    params: string;          // Параметры метода (JSON string)
    executionAddress: string; // Адрес контракта
    attachedValue: string;   // nanoTON
  };
  trackingMeta: {
    sourceProxy: string;
    targetProxy: string;
    targetConfiguration: string;
  };
}

async function sendTransfer(
  tvmClient: ProviderRpcClient,
  senderAddress: string,
  payloadResponse: TransferPayloadResponse
): Promise<string> {
  const { abiMeta } = payloadResponse;

  console.log(`Отправляем ${abiMeta.abiMethod} транзакцию...`);
  console.log('Адрес контракта:', abiMeta.executionAddress);
  console.log('Газ:', abiMeta.attachedValue, 'nanoTON');

  // API возвращает ABI контракта, имя метода и параметры —
  // используем их напрямую для вызова через TVM SDK.
  const contract = new tvmClient.Contract(
    JSON.parse(abiMeta.abi),
    new Address(abiMeta.executionAddress)
  );

  const params = JSON.parse(abiMeta.params);
  const { transaction } = await contract.methods[abiMeta.abiMethod](params).send({
    from: new Address(senderAddress),
    amount: abiMeta.attachedValue,
    bounce: true,
  });

  console.log('Транзакция отправлена, hash:', transaction.id.hash);

  return transaction.id.hash;
}

Пример использования

typescript
import TonConnectUI from '@tonconnect/ui';

// TonConnect manifest — JSON-файл с описанием вашего dApp (название, иконка, URL).
// Хостится на вашем домене. Формат: { "url": "...", "name": "...", "iconUrl": "..." }
const tonConnectUI = new TonConnectUI({
  manifestUrl: 'https://your-app.com/tonconnect-manifest.json',
});

// payloadResponse получен на Шаге 1
const messageHash = await sendTransfer(tonConnectUI, payloadResponse);
console.log('Message hash получен:', messageHash);
typescript
import { EverscaleStandaloneClient } from 'everscale-standalone-client';
import { ProviderRpcClient } from 'everscale-inpage-provider';

const tvmClient = new ProviderRpcClient({
  fallback: () => EverscaleStandaloneClient.create({
    connection: { type: 'jrpc', data: { endpoint: 'https://jrpc-ton.broxus.com' } },
  }),
});
await tvmClient.ensureInitialized();

// payloadResponse получен на Шаге 1
const txHash = await sendTransfer(
  tvmClient,
  '0:3333...cccc',  // TVM адрес кошелька отправителя
  payloadResponse
);
console.log('Transaction hash получен:', txHash);

После успешной транзакции

  1. Транзакция от кошелька запускает цепочку внутренних вызовов в блокчейне
  2. В этой цепочке Proxy контракт (sourceProxy) эмитит событие TvmTvmNative или TvmTvmAlien
  3. Transfer ID = transaction hash той транзакции в цепочке, в которой эмитится событие (не hash исходной транзакции от кошелька)

Шаг 3: Получение Transfer ID

Зачем нужен Transfer ID

Transfer ID необходим для:

  • Отслеживания статуса трансфера через Bridge Aggregator API
  • Получения proof для non-credit режима
  • Диагностики проблем с трансфером

Transfer ID = transaction hash транзакции с событием TvmTvmNative или TvmTvmAlien на Proxy контракте.

Transaction на Proxy ← Transaction Hash = TRANSFER ID
    └── Out Messages
        └── External Out Message (событие TvmTvmNative/TvmTvmAlien)

Как получить Transfer ID

Важно

result.boc из TON Connect — это BOC подписанного сообщения от кошелька, а не Transfer ID. Transfer ID появляется только после того, как транзакция обработана в блокчейне и Proxy эмитит событие.

typescript
// npm install @ton/ton @ton/core

import { Address } from '@ton/ton';

// === КОНФИГУРАЦИЯ ===
const TONAPI_BASE = 'https://tetra.tonapi.io/v2'; // Blockchain Explorer API
// const TONAPI_BASE = 'https://testnet.tonapi.io/v2';  // Testnet

/**
 * Получает trace транзакции через TonAPI
 */
async function getTransactionTrace(txHash: string): Promise<any> {
  const response = await fetch(
    `${TONAPI_BASE}/traces/${txHash}`,
    {
      headers: { 'Accept': 'application/json' }
    }
  );

  if (!response.ok) {
    throw new Error(`Failed to get trace: ${response.status}`);
  }

  return response.json();
}

/**
 * Рекурсивно ищет транзакцию на Proxy контракте с событием TvmTvmNative/TvmTvmAlien
 */
function findProxyTransaction(
  node: any,
  proxyAddress: string
): string | null {
  const normalizedProxy = Address.parse(proxyAddress).toRawString().toLowerCase();
  const tx = node.transaction;

  if (tx?.account?.address) {
    const txAddress = tx.account.address.toLowerCase();
    const isProxy = txAddress === normalizedProxy;

    // Проверяем наличие External Out Message (событие)
    const hasEvent = tx.out_msgs?.some(
      (m: any) => !m.destination || m.destination?.address === ''
    );

    if (isProxy && hasEvent) {
      return tx.hash;
    }
  }

  for (const child of node.children || []) {
    const result = findProxyTransaction(child, proxyAddress);
    if (result) return result;
  }

  return null;
}

// === ИСПОЛЬЗОВАНИЕ ===

// messageHash получен на Шаге 2 из Cell.fromBase64(result.boc).hash()
const trace = await getTransactionTrace(messageHash);
const transferId = findProxyTransaction(trace, payloadResponse.trackingMeta.sourceProxy);

console.log('Transfer ID:', transferId);
typescript
// При использовании TVM SDK transaction hash доступен напрямую
// из результата вызова contract.methods[...].send()

// txHash получен на Шаге 2 из transaction.id.hash
// Для получения Transfer ID нужен trace той же транзакции

import { Address } from '@ton/ton';

const TONAPI_BASE = 'https://tetra.tonapi.io/v2';

async function getTransactionTrace(txHash: string): Promise<any> {
  const response = await fetch(
    `${TONAPI_BASE}/traces/${txHash}`,
    { headers: { 'Accept': 'application/json' } }
  );
  if (!response.ok) {
    throw new Error(`Failed to get trace: ${response.status}`);
  }
  return response.json();
}

function findProxyTransaction(
  node: any,
  proxyAddress: string
): string | null {
  const normalizedProxy = Address.parse(proxyAddress).toRawString().toLowerCase();
  const tx = node.transaction;

  if (tx?.account?.address) {
    const txAddress = tx.account.address.toLowerCase();
    const isProxy = txAddress === normalizedProxy;
    const hasEvent = tx.out_msgs?.some(
      (m: any) => !m.destination || m.destination?.address === ''
    );
    if (isProxy && hasEvent) {
      return tx.hash;
    }
  }

  for (const child of node.children || []) {
    const result = findProxyTransaction(child, proxyAddress);
    if (result) return result;
  }
  return null;
}

// txHash получен на Шаге 2
const trace = await getTransactionTrace(txHash);
const transferId = findProxyTransaction(trace, payloadResponse.trackingMeta.sourceProxy);

console.log('Transfer ID:', transferId);

Шаг 4: Запрос Proof через Bridge Aggregator API

Цель: Получить данные для завершения трансфера (если useCredit=false)

Условия

useCreditДействие
trueCredit Backend автоматически деплоит Event. Пропустите шаги 4 и 5.
falseНужно вручную получить proof и задеплоить Event контракт в сети назначения.

API Endpoint

POST {BASE_URL}/transfers/status
Content-Type: application/json

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

bash
curl -X POST '{BASE_URL}/transfers/status' \
  -H 'Content-Type: application/json' \
  -d '{
    "tvmTvm": {
      "outgoingTransactionHash": "abc123def456...",
      "dappChainId": -239,
      "timestampCreatedFrom": null
    }
  }'

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

ПараметрТипОписание
tvmTvm.outgoingTransactionHashstringTransfer ID, полученный на Шаге 3
tvmTvm.dappChainIdnumberChain ID сети отправления
tvmTvm.timestampCreatedFromnumber | nullФильтр по времени (опционально)

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

json
{
  "transfer": {
    "tvmTvm": {
      "transferStatus": "Pending",
      "timestampCreatedAt": 1767972717,
      "outgoing": {
        "tokenType": "Native",
        "chainId": -239,
        "userAddress": "0:3333...cccc",
        "tokenAddress": "0:1111...aaaa",
        "proxyAddress": "0:6666...ffff",
        "volumeExec": "0.1000",
        "messageHash": "abc123def456...",
        "transactionHash": "def789abc012..."
      },
      "incoming": {
        "tokenType": null,
        "chainId": 2000,
        "userAddress": "0:2222...bbbb"
      }
    }
  },
  "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
    },
    "abiTx": {
      "tx": "te6ccgEBBwEA6AABiw/X8c8...",
      "executionAddress": "0:8888...1111",
      "attachedValue": "2000000000",
      "abiMethod": "deployEvent",
      "abi": "...",
      "params": "{\"_eventVoteData\":{...}}"
    }
  }
}

Поля ответа

ПолеОписание
transfer.tvmTvm.transferStatusСтатус трансфера (Pending, Completed, Failed)
proofPayloadДанные для деплоя Event контракта. null если proof ещё не готов
proofPayload.txBlockProofMerkle proof блока с транзакцией
proofPayload.txProofMerkle proof самой транзакции
proofPayload.eventДанные события (токен, сумма, получатель)
proofPayload.abiTxГотовая транзакция для деплоя Event контракта (Шаг 5)

Шаг 5: Деплой Event контракта (non-credit)

Цель: Задеплоить Event контракт в сети назначения для завершения трансфера

Только для non-credit

Этот шаг нужен только при useCredit: false. При useCredit: true Credit Backend автоматически деплоит Event контракт.

Данные для транзакции

ПолеОписаниеИспользование
proofPayload.abiTx.executionAddressАдрес EventConfiguration контрактаАдрес получателя транзакции
proofPayload.abiTx.txГотовый payload (BOC base64)Body транзакции
proofPayload.abiTx.attachedValueТребуемый газ (в nano-единицах)Amount транзакции
proofPayload.abiTx.abiABI контракта (JSON string)Для TVM SDK
proofPayload.abiTx.abiMethodМетод контракта (deployEvent)Имя метода
proofPayload.abiTx.paramsПараметры метода (JSON string)Для TVM SDK

Отправка транзакции deployEvent

typescript
import TonConnectUI from '@tonconnect/ui';

interface ProofPayloadResponse {
  abiTx: {
    tx: string;              // BOC base64
    executionAddress: string; // EventConfiguration адрес
    attachedValue: string;   // nanoTON
    abiMethod: string;
    abi: string;
    params: string;
  } | null;
}

async function deployEventContract(
  tonConnectUI: TonConnectUI,
  proofPayload: ProofPayloadResponse
): Promise<string> {
  const { abiTx } = proofPayload;

  if (!abiTx) {
    throw new Error('abiTx not available in proofPayload');
  }

  console.log('Деплоим Event контракт...');
  console.log('EventConfiguration:', abiTx.executionAddress);
  console.log('Газ:', abiTx.attachedValue, 'nanoTON');

  // Формируем транзакцию используя готовые данные из API
  const transaction = {
    validUntil: Math.floor(Date.now() / 1000) + 600, // 10 минут
    messages: [
      {
        address: abiTx.executionAddress,  // EventConfiguration адрес
        amount: abiTx.attachedValue,      // Газ из API
        payload: abiTx.tx,                // BOC base64 из API
      },
    ],
  };

  // Отправляем транзакцию через TonConnect
  const result = await tonConnectUI.sendTransaction(transaction);

  console.log('deployEvent транзакция отправлена:', result.boc);

  return result.boc;
}
typescript
import { Address, ProviderRpcClient } from 'everscale-inpage-provider';

interface ProofPayloadResponse {
  abiTx: {
    tx: string;              // BOC base64
    executionAddress: string; // EventConfiguration адрес
    attachedValue: string;   // nanoTON
    abiMethod: string;       // deployEvent
    abi: string;             // ABI контракта (JSON string)
    params: string;          // Параметры метода (JSON string)
  } | null;
}

async function deployEventContract(
  tvmClient: ProviderRpcClient,
  senderAddress: string,
  proofPayload: ProofPayloadResponse
): Promise<string> {
  const { abiTx } = proofPayload;

  if (!abiTx) {
    throw new Error('abiTx not available in proofPayload');
  }

  console.log('Деплоим Event контракт...');
  console.log('EventConfiguration:', abiTx.executionAddress);
  console.log('Газ:', abiTx.attachedValue, 'nanoTON');

  // API возвращает ABI, метод и параметры для деплоя Event —
  // используем их напрямую через TVM SDK.
  const contract = new tvmClient.Contract(
    JSON.parse(abiTx.abi),
    new Address(abiTx.executionAddress)
  );

  const params = JSON.parse(abiTx.params);
  const { transaction } = await contract.methods[abiTx.abiMethod](params).send({
    from: new Address(senderAddress),
    amount: abiTx.attachedValue,
    bounce: true,
  });

  console.log('deployEvent транзакция отправлена, hash:', transaction.id.hash);

  return transaction.id.hash;
}

Что происходит после deployEvent

  1. EventConfiguration деплоит новый Event контракт с данными proof
  2. Event контракт верифицирует транзакцию через TransactionCheckerLiteClient
  3. После успешной верификации Event контракт вызывает Proxy в сети назначения
  4. Proxy mint'ит (для Alien) или unlock'ает (для Native) токены получателю

Полный пример

End-to-end пример non-credit трансфера TVM→TVM:

typescript
import TonConnectUI from '@tonconnect/ui';
import { Cell } from '@ton/core';
import { Address } from '@ton/ton';

const BASE_URL = 'https://tetra-history-api.chainconnect.com/v2';
const TONAPI_BASE = 'https://tetra.tonapi.io/v2';

const tonConnectUI = new TonConnectUI({
  manifestUrl: 'https://your-app.com/tonconnect-manifest.json',
});

// --- Шаг 1: Build Payload ---
const payloadResponse = await fetch(`${BASE_URL}/payload/build`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    fromChainId: -239,
    toChainId: 2000,
    tokenAddress: '0:1111...aaaa',
    recipientAddress: '0:2222...bbbb',
    amount: '1000000',
    senderAddress: '0:3333...cccc',
    useCredit: false,
    remainingGasTo: '0:3333...cccc',
  }),
}).then(r => r.json());

console.log('Transfer kind:', payloadResponse.transferKind);
console.log('Amount after fee:', payloadResponse.tokenAmount);

// --- Шаг 2: Отправка транзакции ---
const txResult = await tonConnectUI.sendTransaction({
  validUntil: Math.floor(Date.now() / 1000) + 600,
  messages: [{
    address: payloadResponse.abiMeta.executionAddress,
    amount: payloadResponse.abiMeta.attachedValue,
    payload: payloadResponse.abiMeta.tx,
  }],
});

const messageHash = Cell.fromBase64(txResult.boc).hash().toString('hex');
console.log('Message hash:', messageHash);

// --- Шаг 3: Получение Transfer ID ---
// Ожидание подтверждения транзакции (задержка для обработки блокчейном)
await new Promise(resolve => setTimeout(resolve, 15000));

const trace = await fetch(`${TONAPI_BASE}/traces/${messageHash}`, {
  headers: { 'Accept': 'application/json' },
}).then(r => r.json());

function findProxyTx(node: any, proxy: string): string | null {
  const normalized = Address.parse(proxy).toRawString().toLowerCase();
  const tx = node.transaction;
  if (tx?.account?.address?.toLowerCase() === normalized) {
    const hasEvent = tx.out_msgs?.some(
      (m: any) => !m.destination || m.destination?.address === ''
    );
    if (hasEvent) return tx.hash;
  }
  for (const child of node.children || []) {
    const r = findProxyTx(child, proxy);
    if (r) return r;
  }
  return null;
}

const transferId = findProxyTx(trace, payloadResponse.trackingMeta.sourceProxy);
console.log('Transfer ID:', transferId);

// --- Шаг 4: Запрос Proof ---
const statusResponse = await fetch(`${BASE_URL}/transfers/status`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    tvmTvm: {
      outgoingTransactionHash: transferId,
      dappChainId: -239,
    },
  }),
}).then(r => r.json());

console.log('Status:', statusResponse.transfer?.tvmTvm?.transferStatus);

// --- Шаг 5: Деплой Event контракта ---
if (statusResponse.proofPayload?.abiTx) {
  const deployResult = await tonConnectUI.sendTransaction({
    validUntil: Math.floor(Date.now() / 1000) + 600,
    messages: [{
      address: statusResponse.proofPayload.abiTx.executionAddress,
      amount: statusResponse.proofPayload.abiTx.attachedValue,
      payload: statusResponse.proofPayload.abiTx.tx,
    }],
  });
  console.log('Event deployed:', deployResult.boc);
}
typescript
import { EverscaleStandaloneClient } from 'everscale-standalone-client';
import { Address, ProviderRpcClient } from 'everscale-inpage-provider';

const BASE_URL = 'https://tetra-history-api.chainconnect.com/v2';
const TONAPI_BASE = 'https://tetra.tonapi.io/v2';

const tvmClient = new ProviderRpcClient({
  fallback: () => EverscaleStandaloneClient.create({
    connection: { type: 'jrpc', data: { endpoint: 'https://jrpc-ton.broxus.com' } },
  }),
});
await tvmClient.ensureInitialized();

const senderAddress = '0:3333...cccc';

// --- Шаг 1: Build Payload ---
const payloadResponse = await fetch(`${BASE_URL}/payload/build`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    fromChainId: -239,
    toChainId: 2000,
    tokenAddress: '0:1111...aaaa',
    recipientAddress: '0:2222...bbbb',
    amount: '1000000',
    senderAddress,
    useCredit: false,
    remainingGasTo: senderAddress,
  }),
}).then(r => r.json());

console.log('Transfer kind:', payloadResponse.transferKind);
console.log('Amount after fee:', payloadResponse.tokenAmount);

// --- Шаг 2: Отправка транзакции ---
const contract = new tvmClient.Contract(
  JSON.parse(payloadResponse.abiMeta.abi),
  new Address(payloadResponse.abiMeta.executionAddress)
);

const params = JSON.parse(payloadResponse.abiMeta.params);
const { transaction } = await contract.methods[payloadResponse.abiMeta.abiMethod](params).send({
  from: new Address(senderAddress),
  amount: payloadResponse.abiMeta.attachedValue,
  bounce: true,
});

const txHash = transaction.id.hash;
console.log('Transaction hash:', txHash);

// --- Шаг 3: Получение Transfer ID ---
// Ожидание подтверждения транзакции
await new Promise(resolve => setTimeout(resolve, 15000));

const trace = await fetch(`${TONAPI_BASE}/traces/${txHash}`, {
  headers: { 'Accept': 'application/json' },
}).then(r => r.json());

function findProxyTx(node: any, proxy: string): string | null {
  const normalized = Address.parse(proxy).toRawString().toLowerCase();
  const tx = node.transaction;
  if (tx?.account?.address?.toLowerCase() === normalized) {
    const hasEvent = tx.out_msgs?.some(
      (m: any) => !m.destination || m.destination?.address === ''
    );
    if (hasEvent) return tx.hash;
  }
  for (const child of node.children || []) {
    const r = findProxyTx(child, proxy);
    if (r) return r;
  }
  return null;
}

const transferId = findProxyTx(trace, payloadResponse.trackingMeta.sourceProxy);
console.log('Transfer ID:', transferId);

// --- Шаг 4: Запрос Proof ---
const statusResponse = await fetch(`${BASE_URL}/transfers/status`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    tvmTvm: {
      outgoingTransactionHash: transferId,
      dappChainId: -239,
    },
  }),
}).then(r => r.json());

console.log('Status:', statusResponse.transfer?.tvmTvm?.transferStatus);

// --- Шаг 5: Деплой Event контракта ---
if (statusResponse.proofPayload?.abiTx) {
  const { abiTx } = statusResponse.proofPayload;

  const eventConfig = new tvmClient.Contract(
    JSON.parse(abiTx.abi),
    new Address(abiTx.executionAddress)
  );

  const eventParams = JSON.parse(abiTx.params);
  const { transaction: deployTx } = await eventConfig.methods[abiTx.abiMethod](eventParams).send({
    from: new Address(senderAddress),
    amount: abiTx.attachedValue,
    bounce: true,
  });

  console.log('Event deployed, hash:', deployTx.id.hash);
}

Справочник ошибок

Ошибки Proxy контракта

КодКонстантаОписание
2701NOT_EVM_CONFIGОтправитель не является EVM EventConfiguration
2702PROXY_PAUSEDProxy контракт приостановлен
2703PROXY_TOKEN_ROOT_IS_EMPTYНе установлен Token Root
2704WRONG_TOKENS_AMOUNT_IN_PAYLOADНеверная сумма токенов в payload
2705WRONG_OWNER_IN_PAYLOADНеверный owner в payload
2708WRONG_TOKEN_ROOTНеверный Token Root
2710NOT_TVM_CONFIGОтправитель не является TVM EventConfiguration
2713LOW_MSG_VALUEНедостаточно газа (attached value слишком мал)

Ошибки Event Configuration

КодКонстантаОписание
2209SENDER_NOT_BRIDGEОтправитель не является Bridge контрактом
2210EVENT_BLOCK_NUMBER_LESS_THAN_STARTНомер блока события меньше начального
2211EVENT_TIMESTAMP_LESS_THAN_STARTTimestamp события раньше начального
2212SENDER_NOT_EVENT_CONTRACTОтправитель не является Event контрактом
2213TOO_LOW_DEPLOY_VALUEНедостаточно газа для деплоя Event
2220SENDER_IS_NOT_EVENT_EMITTERОтправитель не является источником события
2221WRONG_DISPATCH_CHAIN_IDНеверный chain ID отправления
2222WRONG_MESSAGE_HASHНеверный hash сообщения
2223WRONG_DESTINATION_CHAIN_IDНеверный chain ID назначения

Ошибки Event контракта

КодКонстантаОписание
2312EVENT_NOT_PENDINGСобытие не в статусе Pending
2316EVENT_NOT_CONFIRMEDСобытие не подтверждено
2317TOO_LOW_MSG_VALUEНедостаточно газа для обработки
2321EVENT_NOT_INITIALIZINGСобытие не в статусе инициализации
2329SENDER_NOT_TX_CHECKERОтправитель не является TransactionChecker
2330WRONG_BASE_NATIVE_PROXY_WALLETНеверный адрес Native Proxy Wallet

Ошибки Bridge

КодКонстантаОписание
2102BRIDGE_NOT_ACTIVEBridge не активен
2103EVENT_CONFIGURATION_NOT_ACTIVEEventConfiguration не активна
2108BRIDGE_PAUSEDBridge приостановлен
2112ZERO_ADDRESSПередан нулевой адрес

ChainConnect Bridge Documentation