Skip to content

Merge Logic — Alien Token Consolidation ​

What is Merge ​

Merge is a mechanism for consolidating multiple alien tokens (representations of the same token from different source networks) into a single canon token (canonical representation) in the destination network.

Problem It Solves ​

When the same token (e.g., USDT) is bridged from different networks to one TVM network, different alien tokens are created:

  • USDT from network A → USDT-A (derive token 1)
  • USDT from network B → USDT-B (derive token 2)

Without merge, users receive different tokens that are not interchangeable. With merge, all these representations are consolidated into one USDT-canon that can be used in DeFi applications.

Merge Logic

When Merge is Needed ​

Conditions for Triggering on Incoming Transfer ​

ConditionDescription
MergeRouter deployedRouter exists for derive token
MergePool specified in Routerpool != address(0)
Canon token enabledcanonToken.enabled == true
Amount after conversion > 0canon_amount > 0 after decimals

When Merge Does NOT Happen ​

  • MergeRouter not deployed → user receives derive token
  • MergePool not specified in Router (pool == 0) → user receives derive token
  • Canon token disabled (enabled == false) → fallback to derive token
  • Amount too small (canon_amount == 0 after decimals) → fallback to derive token

Scenario 1: Merge on Incoming Transfer ​

Participants ​

ParticipantRole
IncomingEventContractEvent contract processing incoming transfer
AlienProxyProxy contract for alien tokens
MergeRouterLinks derive token to MergePool
MergePoolStores derive → canon mapping, performs conversion
Token (derive)Alien token created for specific source network
Token (canon)Canonical token representation

Step-by-step Description ​

  1. Getting derive token address — Event contract requests token address from Proxy
  2. Checking token deployment — if token not deployed (bounce), deployed via Proxy
  3. Requesting MergeRouter — Event contract requests router address for this token
  4. Requesting MergePool — Router returns pool address (or 0 if not configured)
  5. Requesting canon token — if pool is set, canon token is requested
  6. Decimals conversion and finalization — canon_amount calculated with decimals conversion
  7. Token minting — Proxy mints target_token in amount target_amount

Scenario 2: Swap Between Tokens in MergePool ​

User can exchange one token from MergePool to another at 1:1 rate (with decimals conversion).

Sequence ​



Example Payload for Swap ​

For building payload and sending transaction, the following objects are needed:

ObjectDescription
cellEncoderHelper contract for encoding payload in TVM Cell format. Stateless, used only for encode/decode operations
userTokenWalletUser's Jetton Wallet for source token (derive or other token in pool)
typescript
// cellEncoder — instance of CellEncoder contract
// CellEncoder address can be obtained from configuration or deploy your own
const cellEncoder = new ever.Contract(CellEncoderAbi, cellEncoderAddress);

// userTokenWallet — User's Jetton Wallet for token they want to exchange
// Address obtained via Jetton Minter: jettonMinter.getWalletAddress(userAddress)
const userTokenWallet = new ever.Contract(JettonWalletAbi, userJettonWalletAddress);

// Building payload for swap
const burnPayload = await cellEncoder.methods
  .encodeMergePoolBurnJettonSwapPayload({
    _targetToken: targetTokenAddress,  // Target token Jetton Minter address
    _remainingGasTo: userAddress,      // Where to return remaining gas
  })
  .call();

// Burning tokens with sending to MergePool
await userTokenWallet.methods.burn({
  amount: amount,
  remainingGasTo: userAddress,
  callbackTo: mergePool.address,
  payload: burnPayload.value0,
}).send({
  from: userAddress,
  amount: toNano(2),
});

Scenario 3: Withdraw via MergePool ​

User burns canon token via MergePool for withdrawal to another network.

Withdraw Variants ​

Destination NetworkMethod
EVMwithdrawTokensToEvmByMergePool
SVM (Solana)withdrawTokensToSvmByMergePool
TVMwithdrawTokensToTvmByMergePool

Sequence ​



Example Payload for Withdraw to TVM ​

For withdraw, user burns canon token and specifies derive token to determine destination network:

ObjectDescription
cellEncoderHelper contract for encoding payload in TVM Cell format
userCanonWalletUser's Jetton Wallet for canon token (canonical representation)
typescript
// cellEncoder — instance of CellEncoder contract
const cellEncoder = new ever.Contract(CellEncoderAbi, cellEncoderAddress);

// userCanonWallet — User's Jetton Wallet for CANON token
// This is canonical representation wallet, not derive token
// Address: canonJettonMinter.getWalletAddress(userAddress)
const userCanonWallet = new ever.Contract(JettonWalletAbi, userCanonJettonWalletAddress);

// Building payload for withdraw to TVM network
const burnPayload = await cellEncoder.methods
  .encodeMergePoolBurnJettonWithdrawPayloadTvm({
    _targetToken: deriveTokenAddress,  // Derive Jetton Minter address — determines destination network
    _recipient: recipientAddress,       // Recipient address in destination network
    _expectedGas: 0,                    // Expected gas (0 = default)
    _payload: '',                       // Additional payload (optional)
    _remainingGasTo: senderAddress,     // Where to return remaining gas
  })
  .call();

// Burning canon tokens with sending to MergePool
await userCanonWallet.methods.burn({
  amount: amount,
  remainingGasTo: senderAddress,
  callbackTo: mergePool.address,
  payload: burnPayload.value0,
}).send({
  from: senderAddress,
  amount: toNano(10),  // More gas for cross-chain operation
});

Technical Details ​

Data Structures ​

IMergePool.Token:

solidity
struct Token {
    uint8 decimals;  // token decimals
    bool enabled;    // is token enabled in pool
}

IMergePool.BurnType:

solidity
enum BurnType { Withdraw, Swap }

Decimals Conversion Algorithm ​

When swapping or merging between tokens with different decimals, amount conversion is performed:

Three scenarios:

ScenarioConditionFormulaExample
Decimals equalfrom_decimals == to_decimalsresult = amount18 → 18: 1000 → 1000
Decreasing precisionfrom_decimals > to_decimalsresult = amount / 10^(from - to)18 → 6: divide by 10^12
Increasing precisionfrom_decimals < to_decimalsresult = amount * 10^(to - from)6 → 18: multiply by 10^12

Loss on Precision Decrease

When converting from token with higher decimals to token with lower decimals, lower digits may be lost. For example:

  • Source amount: 1_000_000_000_001 (18 decimals)
  • Target token: 6 decimals
  • Result: 1_000_000_000_001 / 10^12 = 1 (lower 12 digits lost)

If conversion result equals 0, operation falls back to derive token (on merge) or is rejected (on swap).

Key Functions ​

FunctionPurpose
deployMergePoolDeploy new MergePool
deployMergeRouterDeploy MergeRouter
onAcceptTokensBurnProcessing token burning
mintTokensByMergePoolMinting via MergePool
receiveMergePoolCanonReceiving canon on incoming

Errors and Edge Cases ​

Error Codes ​

CodeNameCause
2903TOKEN_NOT_EXISTSToken not added to MergePool
2904TOKEN_IS_CANONAttempt to remove canon token
2905TOKEN_ALREADY_EXISTSToken already in pool
2907TOKEN_NOT_ENABLEDToken disabled
2908TOKEN_DECIMALS_IS_ZEROAttempt to enable token before getting decimals
2709WRONG_MERGE_POOL_NONCECall from unauthorized pool
2906MERGE_POOL_IS_ZERO_ADDRESSZero pool address in Router

Edge Cases ​

Case 1: Amount too small

  • Situation: amount = 1, from_decimals = 18, to_decimals = 6
  • Result: canon_amount = 1 / 10^12 = 0
  • Behavior: Fallback to derive token

Case 2: MergeRouter deployed but pool not set

  • Result: User receives derive token
  • Behavior: Normal, not an error

Case 3: Canon token disabled during transfer

  • Result: User receives derive token
  • Behavior: Fallback, no data loss

Case 4: Daily limit exceeded on withdraw

  • Result: Tokens minted back to user
  • Behavior: OutgoingLimitReached event emitted

ChainConnect Bridge Documentation