Skip to content

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).

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



Пример 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

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



Пример 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