Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Migration V1 → V2: Swaps

Scope

This page is for a developer who builds cells by hand to perform swaps and is porting an integration from V1 to V2 (the contract set formerly known as v1.6). It compares the swap request a client assembles and forwards to the Router.

Reference formats:

TL;DR

For a plain (single-hop) swap, nothing changes at the request level. The forward-payload body is byte-for-byte identical to V1 and the opcode keeps the same value (0xa7fb58f8). An existing integration that assembles a flat swap keeps working without changes.

You only need to touch code if you:

  1. Reference the opcode by its constant name (POOLV3_SWAPPOOL_SWAP).
  2. Use multihop / forward-payloads — the multihop_cell layout changed.
  3. Parse the swap result in PAY_TO — new result codes were added.

The plain swap payload is unchanged

Both versions build the same forward payload and attach it to a jetton transfer (or a pTON transfer) to the router:

const swapRequest = beginCell()
    .storeUint(POOL_SWAP, 32)         // V1: POOLV3_SWAP — same value 0xa7fb58f8
    .storeAddress(targetJettonWallet) // router's jetton wallet of the OTHER side (identifies pool + direction)
    .storeUint(sqrtPriceLimitX96, 160)// limit price
    .storeCoins(minOutAmount)         // slippage guard
    .storeAddress(ownerAddress)       // receiver of the swap result
    .storeUint(0, 1)                  // multihop maybe-ref = 0 (no multihop)
.endCell()

The leading, mandatory part of the payload — everything up to and including the multihop maybe-ref bit — is identical in V1 and V2. The opcode value, the field order, and the field sizes did not change. See POOL_SWAP for the full V2 field table.

Opcode constants were renamed

All POOLV3_* constants became POOL_*. Numeric values did not change, so the wire format is unaffected — only source that references the constant by name needs updating.

V1 constantV2 constantValue
POOLV3_SWAP POOL_SWAP 0xa7fb58f8
POOLV3_RESULT_SWAP_OK POOL_RESULT_SWAP_OK 200
POOLV3_RESULT_SWAP_OUTPUT_TOO_SMALL POOL_RESULT_SWAP_OUTPUT_TOO_SMALL 230

The multihop cell layout changed

The trailing maybe-ref of the swap payload is the multihop cell. It is parsed by the router only when the ROUTER_FLAG_PAYLOADS flag is enabled. Its layout changed in two ways.

V1 — forward payloads are mandatory refs, and there is no excess address:

_ 
    target_address:MsgAddress
    ok_forward_amount:(VarUInteger 16)
    ok_forward_payload:^Cell            // mandatory ref
    ret_forward_amount:(VarUInteger 16)
    ret_forward_payload:^Cell           // mandatory ref
= Multihop_cellType_V1;

V2 — forward payloads became Maybe refs, and a trailing excess_address was added:

_ 
    target_address:MsgAddress
    ok_forward_amount:(VarUInteger 16)
    ok_forward_payload:(Maybe ^Cell)    // now optional
    ret_forward_amount:(VarUInteger 16)
    ret_forward_payload:(Maybe ^Cell)   // now optional
    excess_address:MsgAddress           // NEW
= Multihop_cellType;

What to change when you build a multihop cell:

  • Replace mandatory storeRef(payload) with storeMaybeRef(payload | null) — you may now pass null (an empty maybe-ref = one 0 bit) when there is no payload.
  • Append excess_address at the end (use addr_none if you have none).

A V1-shaped multihop cell will not parse correctly under V2 — it must be rebuilt. See the full V2 field table under POOL_SWAP → multihop_cell.

Reading the result (PAY_TO)

The pool returns the swap result to the router with ROUTER_PAY_TO, which then pays the client out through its jetton wallet.

  • V1 encoded the result as a single exit_code : uint32.
  • V2 keeps the same 32 bits but treats them as four bytes (exit_code0..exit_code3). For a SWAP the code lands in the low byte (exit_code3), so reading the full 32-bit integer still yields the same value as V1 (e.g. 200). The per-byte split only matters for multi-order reforge.

New swap result codes were added in V2:

CodeConstantMeaning
200 RESULT_SWAP_OK Swap succeeded
230 RESULT_SWAP_OUTPUT_TOO_SMALL minOutAmount not reached, swap reverted, input returned
231 RESULT_SWAP_INTERNAL_ERROR New. Internal error (should not normally happen)
232 RESULT_SWAP_POOL_LOCKED New. Swap attempted on a locked pool
233 RESULT_SWAP_POOL_EMPTY New. Swap attempted on an empty pool

Off-chain code that classifies swap outcomes should keep reading the full 32-bit value (compatible for swaps) and add handling for 231 / 232 / 233.

Referral cell

  • V1 carried a referral_cell with referral_code (uint32) and a conditional referral_address.
  • V2 keeps only code (uint32) and treats the whole referral_cell as indexer-only — the contract does not parse it; partner rewards are computed off-chain by the indexer straight from the on-chain message.

Do not rely on on-chain referral processing; attach the marker as an indexer-only field. See POOL_SWAP → referral_cell.

Migration checklist

  • Rename opcode constants POOLV3_*POOL_* (values unchanged).
  • Plain swap — confirm it still assembles as before (no change required).
  • Multihop: storeRefstoreMaybeRef for forward payloads; append excess_address.
  • PAY_TO parsing: add codes 231/232/233; note the per-byte semantics (swap uses the low byte).
  • Referral: switch to an indexer-only marker; do not expect on-chain handling.