Отправка трансфера
Обзор
Трансфер TVM↔TVM позволяет отправить токены между TVM сетями (например, TON ↔ Tetra L2). Процесс включает:
- Build Payload — получить данные для транзакции через API
- Отправка транзакции — отправить транзакцию в TVM (transfer через Proxy)
- Получение Transfer ID — найти hash транзакции с событием на Proxy контракте
- Запрос Proof (non-credit) — получить доказательство транзакции через API
- Деплой Event контракта (non-credit) — задеплоить Event контракт в сети назначения
Механизм верификации (Trustless)
Для TVM↔TVM верификация выполняется без relay-нод, через криптографическое доказательство транзакции:
- Proxy контракт в сети отправления эмитит событие
TvmTvmNativeилиTvmTvmAlien - API генерирует Merkle proof транзакции (поля
txBlockProofиtxProof) - Event контракт в сети назначения верифицирует proof через
TransactionChecker→LiteClient - После успешной верификации Proxy в сети назначения mint'ит/unlock'ает токены
Credit vs Non-credit
Credit (useCredit: true) | Non-credit (useCredit: false) | |
|---|---|---|
| Кто деплоит Event | Gas 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 |
|---|---|
| Production | https://tetra-history-api.chainconnect.com/v2 |
| Testnet | https://history-api-test.chainconnect.com/v2 |
Шаг 1: Подготовка трансфера (Build Payload)
Цель: Получить payload для отправки транзакции в блокчейн
API Endpoint
POST {BASE_URL}/payload/build
Content-Type: application/jsonПараметры запроса
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 трансферов)
};
}| Параметр | Тип | Обязательный | Описание |
|---|---|---|---|
fromChainId | number | ✅ | Chain ID сети отправления |
toChainId | number | ✅ | Chain ID сети назначения |
tokenAddress | string | ✅ | Адрес токена в сети отправления (формат 0:...) |
recipientAddress | string | ✅ | Адрес получателя в сети назначения |
amount | string | ✅ | Сумма в nano-единицах (integer string, напр. "1000000" для 1 USDT с 6 decimals) |
senderAddress | string | ✅ | Адрес отправителя в сети отправления |
useCredit | boolean | ❌ | true = Credit Backend оплачивает газ в сети назначения. Default: true |
remainingGasTo | string | ✅ | Куда вернуть остаток газа. Обязателен для TVM→TVM |
tokenBalance.nativeTokenAmount | string | ⚠️ | Баланс газового токена (native currency). Обязателен при отправке газового токена |
tokenBalance.wrappedNativeTokenAmount | string | ⚠️ | Баланс wrapped версии газового токена (например, wTON). Обязателен при отправке газового токена |
Пример запроса
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"
}'Пример ответа
{
"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.abi | ABI контракта (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.abi | ABI контракта (JSON) | Для TVM SDK — создание Contract |
abiMeta.abiMethod | "transfer" | Для TVM SDK — имя метода |
abiMeta.params | Параметры метода (JSON) | Для TVM SDK — аргументы вызова |
abiMeta.executionAddress | Адрес контракта | Получатель транзакции |
abiMeta.attachedValue | Газ в nanoTON | Amount транзакции |
Отправка транзакции
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;
}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;
}Пример использования
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);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);После успешной транзакции
- Транзакция от кошелька запускает цепочку внутренних вызовов в блокчейне
- В этой цепочке Proxy контракт (
sourceProxy) эмитит событиеTvmTvmNativeилиTvmTvmAlien - 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 эмитит событие.
// 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);// При использовании 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 | Действие |
|---|---|
true | Credit Backend автоматически деплоит Event. Пропустите шаги 4 и 5. |
false | Нужно вручную получить proof и задеплоить Event контракт в сети назначения. |
API Endpoint
POST {BASE_URL}/transfers/status
Content-Type: application/jsonПример запроса
curl -X POST '{BASE_URL}/transfers/status' \
-H 'Content-Type: application/json' \
-d '{
"tvmTvm": {
"outgoingTransactionHash": "abc123def456...",
"dappChainId": -239,
"timestampCreatedFrom": null
}
}'Параметры запроса
| Параметр | Тип | Описание |
|---|---|---|
tvmTvm.outgoingTransactionHash | string | Transfer ID, полученный на Шаге 3 |
tvmTvm.dappChainId | number | Chain ID сети отправления |
tvmTvm.timestampCreatedFrom | number | null | Фильтр по времени (опционально) |
Пример ответа с Proof
{
"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.txBlockProof | Merkle proof блока с транзакцией |
proofPayload.txProof | Merkle 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.abi | ABI контракта (JSON string) | Для TVM SDK |
proofPayload.abiTx.abiMethod | Метод контракта (deployEvent) | Имя метода |
proofPayload.abiTx.params | Параметры метода (JSON string) | Для TVM SDK |
Отправка транзакции deployEvent
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;
}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
- EventConfiguration деплоит новый Event контракт с данными proof
- Event контракт верифицирует транзакцию через
TransactionChecker→LiteClient - После успешной верификации Event контракт вызывает Proxy в сети назначения
- Proxy mint'ит (для Alien) или unlock'ает (для Native) токены получателю
Полный пример
End-to-end пример non-credit трансфера TVM→TVM:
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);
}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 контракта
| Код | Константа | Описание |
|---|---|---|
| 2701 | NOT_EVM_CONFIG | Отправитель не является EVM EventConfiguration |
| 2702 | PROXY_PAUSED | Proxy контракт приостановлен |
| 2703 | PROXY_TOKEN_ROOT_IS_EMPTY | Не установлен Token Root |
| 2704 | WRONG_TOKENS_AMOUNT_IN_PAYLOAD | Неверная сумма токенов в payload |
| 2705 | WRONG_OWNER_IN_PAYLOAD | Неверный owner в payload |
| 2708 | WRONG_TOKEN_ROOT | Неверный Token Root |
| 2710 | NOT_TVM_CONFIG | Отправитель не является TVM EventConfiguration |
| 2713 | LOW_MSG_VALUE | Недостаточно газа (attached value слишком мал) |
Ошибки Event Configuration
| Код | Константа | Описание |
|---|---|---|
| 2209 | SENDER_NOT_BRIDGE | Отправитель не является Bridge контрактом |
| 2210 | EVENT_BLOCK_NUMBER_LESS_THAN_START | Номер блока события меньше начального |
| 2211 | EVENT_TIMESTAMP_LESS_THAN_START | Timestamp события раньше начального |
| 2212 | SENDER_NOT_EVENT_CONTRACT | Отправитель не является Event контрактом |
| 2213 | TOO_LOW_DEPLOY_VALUE | Недостаточно газа для деплоя Event |
| 2220 | SENDER_IS_NOT_EVENT_EMITTER | Отправитель не является источником события |
| 2221 | WRONG_DISPATCH_CHAIN_ID | Неверный chain ID отправления |
| 2222 | WRONG_MESSAGE_HASH | Неверный hash сообщения |
| 2223 | WRONG_DESTINATION_CHAIN_ID | Неверный chain ID назначения |
Ошибки Event контракта
| Код | Константа | Описание |
|---|---|---|
| 2312 | EVENT_NOT_PENDING | Событие не в статусе Pending |
| 2316 | EVENT_NOT_CONFIRMED | Событие не подтверждено |
| 2317 | TOO_LOW_MSG_VALUE | Недостаточно газа для обработки |
| 2321 | EVENT_NOT_INITIALIZING | Событие не в статусе инициализации |
| 2329 | SENDER_NOT_TX_CHECKER | Отправитель не является TransactionChecker |
| 2330 | WRONG_BASE_NATIVE_PROXY_WALLET | Неверный адрес Native Proxy Wallet |
Ошибки Bridge
| Код | Константа | Описание |
|---|---|---|
| 2102 | BRIDGE_NOT_ACTIVE | Bridge не активен |
| 2103 | EVENT_CONFIGURATION_NOT_ACTIVE | EventConfiguration не активна |
| 2108 | BRIDGE_PAUSED | Bridge приостановлен |
| 2112 | ZERO_ADDRESS | Передан нулевой адрес |