Migration V1 → V2: Swaps
- Scope
- TL;DR
- The plain swap payload is unchanged
- Opcode constants were renamed
- The multihop cell layout changed
- Reading the result (PAY_TO)
- Referral cell
- Migration checklist
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:
- V2 payload the client builds — POOL_SWAP
- V2 internal router → pool message — POOL_SWAP
- V2 payout back to the client — ROUTER_PAY_TO
- V1 equivalents — POOLV3_SWAP, Payload 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:
- Reference the opcode by its constant name (
POOLV3_SWAP→POOL_SWAP). - Use multihop / forward-payloads — the
multihop_celllayout changed. - 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 constant | V2 constant | Value |
|---|---|---|
| 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)withstoreMaybeRef(payload | null)— you may now passnull(an empty maybe-ref = one0bit) when there is no payload. - Append
excess_addressat the end (useaddr_noneif 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:
| Code | Constant | Meaning |
|---|---|---|
| 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_cellwithreferral_code (uint32)and a conditionalreferral_address. - V2 keeps only
code (uint32)and treats the wholereferral_cellas 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:
storeRef→storeMaybeReffor forward payloads; appendexcess_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.