Skip to content
ChainConnect

Merge Logic — объединение alien токенов

Что такое Merge

Merge — механизм объединения нескольких alien токенов (представлений одного и того же токена из разных сетей отправления) в единый canon token (каноническое представление) в сети назначения.

Проблема которую решает

Когда один и тот же токен (например, USDT) бриджится из разных сетей в одну TVM сеть, создаются разные alien токены:

  • USDT из сети A → USDT-A (derive token 1)
  • USDT из сети B → USDT-B (derive token 2)

Без merge пользователь получает разные токены, которые не взаимозаменяемы. С merge все эти представления объединяются в один USDT-canon, который можно использовать в DeFi приложениях.

Merge Logic

Когда нужен Merge

Условия срабатывания при входящем трансфере

УсловиеОписание
MergeRouter задеплоенДля derive-токена существует роутер
MergePool указан в Routerpool != address(0)
Canon token включёнcanonToken.enabled == true
Сумма после конвертации > 0canon_amount > 0 после decimals

Когда Merge НЕ происходит

  • MergeRouter не задеплоен → пользователь получает derive token
  • MergePool не указан в Router (pool == 0) → пользователь получает derive token
  • Canon token выключен (enabled == false) → fallback на derive token
  • Сумма слишком мала (canon_amount == 0 после decimals) → fallback на derive token

Сценарий 1: Merge при входящем трансфере

Участники

УчастникРоль
IncomingEventContractEvent контракт, обрабатывающий входящий трансфер
AlienProxyProxy контракт для alien токенов
MergeRouterСвязывает derive-токен с MergePool
MergePoolХранит маппинг derive → canon, выполняет конвертацию
Token (derive)Alien токен, созданный для конкретной сети отправления
Token (canon)Каноническое представление токена

Пошаговое описание

  1. Получение адреса derive токена — Event контракт запрашивает адрес токена у Proxy
  2. Проверка деплоя токена — если токен не задеплоен (bounce), деплоится через Proxy
  3. Запрос MergeRouter — Event контракт запрашивает адрес роутера для данного токена
  4. Запрос MergePool — Роутер возвращает адрес пула (или 0 если не настроен)
  5. Запрос canon токена — если пул задан, запрашивается canon токен
  6. Конвертация decimals и финализация — вычисляется canon_amount с учетом decimals
  7. Минтинг токенов — Proxy минтит target_token в количестве target_amount

Сценарий 2: Swap между токенами в MergePool

Пользователь может обменять один токен из MergePool на другой по курсу 1:1 (с учётом decimals).

Последовательность

Swap между токенами в MergePool

Пример payload для swap

Для формирования payload и отправки транзакции необходимы следующие объекты:

ОбъектОписание
cellEncoderКонтракт-хелпер для кодирования payload в формате TVM Cell. Не хранит состояние, используется только для encode/decode операций
userTokenWalletJetton Wallet пользователя для исходного токена (derive или другой токен в пуле)
typescript
// cellEncoder — инстанс контракта CellEncoder
// Адрес CellEncoder можно получить из конфигурации или задеплоить свой
const cellEncoder = new ever.Contract(CellEncoderAbi, cellEncoderAddress);

// userTokenWallet — Jetton Wallet пользователя для токена, который он хочет обменять
// Адрес получается через Jetton Minter: jettonMinter.getWalletAddress(userAddress)
const userTokenWallet = new ever.Contract(JettonWalletAbi, userJettonWalletAddress);

// Формирование payload для swap
const burnPayload = await cellEncoder.methods
  .encodeMergePoolBurnJettonSwapPayload({
    _targetToken: targetTokenAddress,  // Адрес Jetton Minter целевого токена
    _remainingGasTo: userAddress,      // Куда вернуть остаток газа
  })
  .call();

// Сжигание токенов с отправкой в MergePool
await userTokenWallet.methods.burn({
  amount: amount,
  remainingGasTo: userAddress,
  callbackTo: mergePool.address,
  payload: burnPayload.value0,
}).send({
  from: userAddress,
  amount: toNano(2),
});

Сценарий 3: Withdraw через MergePool

Пользователь сжигает canon token через MergePool для вывода в другую сеть.

Варианты withdraw

Сеть назначенияМетод
EVMwithdrawTokensToEvmByMergePool
SVM (Solana)withdrawTokensToSvmByMergePool
TVMwithdrawTokensToTvmByMergePool

Последовательность

Withdraw через MergePool

Пример payload для withdraw в TVM

Для withdraw пользователь сжигает canon token и указывает derive token для определения сети назначения:

ОбъектОписание
cellEncoderКонтракт-хелпер для кодирования payload в формате TVM Cell
userCanonWalletJetton Wallet пользователя для canon token (каноническое представление токена)
typescript
// cellEncoder — инстанс контракта CellEncoder
const cellEncoder = new ever.Contract(CellEncoderAbi, cellEncoderAddress);

// userCanonWallet — Jetton Wallet пользователя для CANON токена
// Это кошелёк канонического представления, не derive токена
// Адрес: canonJettonMinter.getWalletAddress(userAddress)
const userCanonWallet = new ever.Contract(JettonWalletAbi, userCanonJettonWalletAddress);

// Формирование payload для withdraw в TVM сеть
const burnPayload = await cellEncoder.methods
  .encodeMergePoolBurnJettonWithdrawPayloadTvm({
    _targetToken: deriveTokenAddress,  // Адрес derive Jetton Minter — определяет сеть назначения
    _recipient: recipientAddress,       // Адрес получателя в сети назначения
    _expectedGas: 0,                    // Ожидаемый газ (0 = дефолт)
    _payload: '',                       // Дополнительный payload (опционально)
    _remainingGasTo: senderAddress,     // Куда вернуть остаток газа
  })
  .call();

// Сжигание canon токенов с отправкой в MergePool
await userCanonWallet.methods.burn({
  amount: amount,
  remainingGasTo: senderAddress,
  callbackTo: mergePool.address,
  payload: burnPayload.value0,
}).send({
  from: senderAddress,
  amount: toNano(10),  // Больше газа для cross-chain операции
});

Технические детали

Структуры данных

IMergePool.Token:

solidity
struct Token {
    uint8 decimals;  // decimals токена
    bool enabled;    // включён ли токен в пуле
}

IMergePool.BurnType:

solidity
enum BurnType { Withdraw, Swap }

Алгоритм конвертации decimals

При swap или merge между токенами с разным количеством decimals выполняется конвертация суммы:

Три сценария:

СценарийУсловиеФормулаПример
Decimals равныfrom_decimals == to_decimalsresult = amount18 → 18: 1000 → 1000
Уменьшение точностиfrom_decimals > to_decimalsresult = amount / 10^(from - to)18 → 6: делим на 10^12
Увеличение точностиfrom_decimals < to_decimalsresult = amount * 10^(to - from)6 → 18: умножаем на 10^12

Потеря при уменьшении точности

При конвертации из токена с большим decimals в токен с меньшим decimals возможна потеря младших разрядов. Например:

  • Исходная сумма: 1_000_000_000_001 (18 decimals)
  • Целевой токен: 6 decimals
  • Результат: 1_000_000_000_001 / 10^12 = 1 (младшие 12 разрядов потеряны)

Если результат конвертации равен 0, операция выполняет fallback на derive token (при merge) или отклоняется (при swap).

Ключевые функции

ФункцияНазначение
deployMergePoolДеплой нового MergePool
deployMergeRouterДеплой MergeRouter
onAcceptTokensBurnОбработка сжигания токенов
mintTokensByMergePoolМинтинг через MergePool
receiveMergePoolCanonПолучение canon при входящем

Ошибки и Edge Cases

Error Codes

КодНазваниеПричина
2903TOKEN_NOT_EXISTSТокен не добавлен в MergePool
2904TOKEN_IS_CANONПопытка удалить canon token
2905TOKEN_ALREADY_EXISTSТокен уже есть в пуле
2907TOKEN_NOT_ENABLEDТокен отключён
2908TOKEN_DECIMALS_IS_ZEROПопытка включить токен до получения decimals
2709WRONG_MERGE_POOL_NONCEВызов от неавторизованного пула
2906MERGE_POOL_IS_ZERO_ADDRESSНулевой адрес пула в Router

Edge Cases

Case 1: Слишком малая сумма

  • Ситуация: amount = 1, from_decimals = 18, to_decimals = 6
  • Результат: canon_amount = 1 / 10^12 = 0
  • Поведение: Fallback на derive token

Case 2: MergeRouter задеплоен, но pool не установлен

  • Результат: Пользователь получает derive token
  • Поведение: Штатное, не ошибка

Case 3: Canon token отключён во время трансфера

  • Результат: Пользователь получает derive token
  • Поведение: Fallback, данные не теряются

Case 4: Превышение дневного лимита при withdraw

  • Результат: Токены минтятся обратно пользователю
  • Поведение: Event OutgoingLimitReached эмитится

ChainConnect Bridge Documentation