Мосты: перенос активов между TON и другими сетями

Разберёшь lock-and-mint мостов TON↔EVM и встроишь quote/deposit/status через провайдера, без legacy protocol bridges.

Мосты и TON

Мост переносит ценность между TON и другой сетью (Ethereum, BNB Chain, Polygon и др.). Блокчейны не умеют «отправить токен напрямую» — мост блокирует актив на исходной сети и выпускает обёртку (или выплачивает из пула ликвидности) на целевой.

Официальные protocol-level мосты TON (config 71–73, 79, 81–82) считаются legacy и не рекомендуются: их могут отключить. Для продуктов используй актуальные bridge-провайдеры / агрегаторы из экосистемы (см. Bridge Dashboard в TON Docs).


Модель lock-and-mint

text
Ethereum                         TON
────────                         ───
lock(ERC-20)  ──oracles/relayers──►  mint(jetton)
burn(jetton)  ◄───────────────────  unlock(ERC-20)
  1. Пользователь лочит USDT в контракте моста на Ethereum, указывая TON-адрес получателя.
  2. Оракулы/релееры подтверждают событие.
  3. На TON минтится wrapped-jetton (или зачисляется из пула).
  4. Обратный путь: burn jetton → unlock ERC-20.

Liquidity-bridge вариант: на целевой сети сразу отдают токен из пула, а депозит на исходной пополняет пул — быстрее UX, другая модель риска (ликвидность пула, а не 1:1 mint).


Интеграция через провайдера (quote → deposit → status)

Типичный продуктовый путь — не писать свой мост, а вызвать API/SDK провайдера или агрегатора (AppKit on-ramp, LayerZero-based мосты, TAC для hybrid TON↔EVM и т.д.). Схема одна:

typescript
// Паттерн интеграции; конкретные имена методов — у выбранного SDK провайдера.
// Пример структуры близок к @ton/appkit crypto on-ramp.

type BridgeQuote = {
  id: string;
  fromChain: "ethereum" | "bsc" | "polygon";
  fromToken: string;
  toToken: string; // jetton master или native Gram в сети TON
  amountIn: string;
  amountOutEstimated: string;
  fee: string;
  estimatedMinutes: number;
};

type BridgeDeposit = {
  id: string;
  depositAddress: string; // куда слать на source-chain
  memo?: string;
  expiresAt: number;
};

type BridgeStatus = "pending" | "confirming" | "completed" | "failed" | "refunded";

/** 1. Котировка */
async function getQuote(input: {
  fromChain: BridgeQuote["fromChain"];
  fromToken: string;
  toToken: string;
  amount: string;
  destinationTonAddress: string;
}): Promise<BridgeQuote> {
  const res = await fetch("https://bridge-provider.example/v1/quote", {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify(input),
  });
  if (!res.ok) throw new Error(`quote failed: ${res.status}`);
  return res.json();
}

/** 2. Создание депозита на source-chain */
async function createDeposit(quoteId: string): Promise<BridgeDeposit> {
  const res = await fetch("https://bridge-provider.example/v1/deposits", {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({ quoteId }),
  });
  if (!res.ok) throw new Error(`deposit failed: ${res.status}`);
  return res.json();
}

/** 3. Статус — не смотри только баланс TON-кошелька */
async function getStatus(depositId: string): Promise<{ status: BridgeStatus }> {
  const res = await fetch(
    `https://bridge-provider.example/v1/deposits/${depositId}`,
  );
  if (!res.ok) throw new Error(`status failed: ${res.status}`);
  return res.json();
}

export async function bridgeUsdtToTon(destinationTonAddress: string) {
  const quote = await getQuote({
    fromChain: "ethereum",
    fromToken: "USDT",
    toToken: "USDT", // wrapped USDT jetton на TON
    amount: "100",
    destinationTonAddress,
  });

  const deposit = await createDeposit(quote.id);

  // Пользователь отправляет USDT на deposit.depositAddress в Ethereum
  // (через ethers / viem / кошелёк) с memo, если провайдер требует.

  let status: BridgeStatus = "pending";
  while (status === "pending" || status === "confirming") {
    await new Promise((r) => setTimeout(r, 15_000));
    status = (await getStatus(deposit.id)).status;
  }

  if (status !== "completed") {
    throw new Error(`bridge ended with status=${status}`);
  }
}

Подставь реальный base URL и поля из документации выбранного провайдера. Важно сохранить три фазы: quote → инструкции депозита → polling статуса.

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

Quote — оценка amountOut, комиссий и ETA. Это не гарантия курса: к моменту депозита котировка может истечь (expiresAt).

Deposit address — адрес контракта/кастодиала на source-chain. Ошибка в сети (Ethereum vs BSC) или в memo = потеря средств или долгий refund.

Status API — источник истины о завершении. Баланс jetton на TON может появиться с задержкой; наоборот, баланс ещё не обновился, а мост уже completed в индексере провайдера — ориентируйся на статус + explorer.

Legacy config bridges — не встраивай в новые продукты. Если встречаешь старые туториалы с bridge.ton.org protocol contracts — считай их историческими.

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

Считаешь bridged-jetton «тем же» USDT → это обёртка конкретного моста. Разные мосты = разные master-адреса. Для свопа на Ston.fi нужен тот jetton, который листит DEX.

Проверяешь только баланс кошелька → зависания на confirming остаются невидимыми. Полли статус провайдера и показывай ETA/failed/refunded.

Используешь legacy official bridge → риск внезапной депрекации. Бери провайдера из актуального списка TON Docs Bridge Dashboard.


Что дальше

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