Migration V1 → V2: Mint & Burn
- Scope
- TL;DR
- Mint: the FUND_ACCOUNT body was reworked
- Burn: a mandatory trailing maybe-ref was added
- Reforge
- Opcode constants were renamed
- Migration checklist
Scope
This page is for a developer who builds cells by hand or want to understand what happens in SDK to mint (add liquidity)
and burn positions, and is porting an integration from V1 to V2 (the contract set known as v1.6).
reference formats:
- V2 mint payload the client builds — POOL_FUND_ACCOUNT
- V2 burn message — POOL_START_BURN
- V2 order structure shared by mint & reforge — ACCOUNT_SET_ORDER
- V2 reforge engine (mint / burn / both at once) — POOL_REFORGE
- V1 equivalents — POOLV3_FUND_ACCOUNT, POOLV3_START_BURN
NB!
We introduce functions step by step. So we recommend to use SDK calls to make mints and burns.
So far **deposit** function is switched off in most pools, so you can't synchronously include your position in the mining of the new one
TL;DR
Unlike swaps, both mint and burn changed in an incompatible way, even though the opcode values stayed the same.
| Operation | Opcode (value unchanged) | Body compatible? | What happened |
|---|---|---|---|
| MINT | POOL_FUND_ACCOUNT = 0x4468de77 | No | Flat single position → "reforge order" (up to 4 mint orders) |
| BURN | POOL_START_BURN = 0x530b5f2c | No | A mandatory trailing action maybe-ref was added |
The change: the opcode is the same, so the message passes the initial op check, but the body can then parsed incorrectly (mint) or parsing overruns the end of the cell (burn). There is no silent backward compatibility — you must update how you assemble the cells.
Mint: the FUND_ACCOUNT body was reworked
Minting is still done in several parts (funds + positions + order), sent as several jetton transfers to the router. Delivery order is not guaranteed, so each part is self-sufficient. That mechanism is unchanged. The body of each part changed.
Here is the comparison of the structures
V1 — flat single position
The position (liquidity, tickLower, tickUpper) is stored directly in the
body:
POOLV3_FUND_ACCOUNT#4468de77
jetton_target_w:MsgAddress
enough0:(VarUInteger 16)
enough1:(VarUInteger 16)
liquidity:uint128 // position side and ticks stored right here
tickLower:int24
tickUpper:int24
= FundAccount_V1;
V2 — reforge order body (up to 4 mint orders)
The position is moved out into a separate MintOrder reference, and several new
fields appear:
POOL_FUND_ACCOUNT#4468de77
jetton_target_w:MsgAddress
enough0:(VarUInteger 16)
enough1:(VarUInteger 16)
needPos:uint64 // NEW
passthrough:uint4 // NEW
target_action:uint32 // NEW (0 = ordinary mint to self)
MintOrder0:(Maybe ^MintOrder)
MintOrder1:(Maybe ^MintOrder)
MintOrder2:(Maybe ^MintOrder)
MintOrder3:(Maybe ^MintOrder)
= FundAccount_V2;
_
op:uint32 // NEW: MINT_NOT_LESS(0) | MINT_AS_MUCH_AS_POSSIBLE(1)
liquidity:uint128
tickLower:int24
tickUpper:int24
receiver:MsgAddress // NEW: who receives the NFT (addr_none = sender)
= MintOrder;
See the full V2 field table under POOL_FUND_ACCOUNT.
New and changed fields
| Field | Notes |
|---|---|
| needPos | New. Number of positions that must be present for the order to execute. For an ordinary mint set 0. |
| passthrough | New. Bitmask of input positions left untouched. For an ordinary mint set 0. |
| target_action | New. Routing of the result (default vs ALM). For an ordinary mint to self set 0. |
| op (per order) | New. Mint mode: MINT_NOT_LESS (V1 behaviour — mint at least the requested liquidity or revert) or MINT_AS_MUCH_AS_POSSIBLE. |
| receiver (per order) | New. NFT receiver. In V1 it was always the sender; in V2 you can direct it. addr_none falls back to the sender. |
What to change in mint code
- Do not hand-assemble the old flat body. Build the body from the order structure (a single-position mint is an order array of length one).
- For each position, set
op(MINT_NOT_LESSfor V1-equivalent behaviour) andreceiver. NoteMINT_AS_MUCH_AS_POSSIBLEcan be overriden by the pool. You can check it beforehand using pool flags - Fill the new
needPos/passthrough/target_actionfields (all0for an ordinary mint). enough0/enough1are in pool order. The order flips with the pool's internal jetton swap (swapIds):enough0 = !swapIds ? jetton0 : jetton1, and symmetrically forenough1. Reproduce this if you assemble the body yourself.
Burn: a mandatory trailing maybe-ref was added
V1 — flat body, no tail
POOLV3_START_BURN#530b5f2c
query_id:uint64
burned_index:uint64
liquidity2Burn:uint128
tickLower:int24
tickUpper:int24
= StartBurn_V1; // body ends here
V2 — same body plus an action maybe-ref
POOL_START_BURN#530b5f2c
query_id:uint64
burned_index:uint64
liquidity2Burn:uint128
tickLower:int24
tickUpper:int24
action:(Maybe ^Cell) // NEW — always present on the wire
= StartBurn_V2;
⚠️ Important. The V2 pool reads a
Maybe ^Cellat the end of the body. That trailing maybe-ref bit is always required, even for a plain burn with no actions. A V1-shaped body (ending attickUpper) makes the V2 parser overrun the cell and fail.Minimum change for a plain burn: append
.storeMaybeRef(null)— a single0bit.
The optional action cell
When present, the action cell lets the burn immediately route its output:
_
target_address0:MsgAddress // receiver of jetton0 output
target_address1:MsgAddress // receiver of jetton1 output
payloads:(Maybe ^Payloads)
= Action;
_
ton_forward0:(VarUInteger 16)
forward_payload0:(Maybe ^Cell)
ton_forward1:(VarUInteger 16)
forward_payload1:(Maybe ^Cell)
excess_address:MsgAddress // excess gas
= Payloads;
In V1 there was no such option — burn output always went to the owner. See POOL_START_BURN.
Burn without a front-end (BURN_WITH_TEXT)
New in V2. The Position NFT now accepts a plain wallet transfer carrying the
text comment burn — a self-custodial escape hatch that needs no front-end, SDK
or knowledge of message layouts. The owner sends a small amount of GRAM to their
own position NFT address and types burn in the comment; the NFT then forwards
START_BURN to the pool for the whole position.
- The comment must be exactly
burn(compared against the literal — extra text or aburn <amount>form is rejected withWRONG_COMMAND). This path always burns the full position. - Accepted only from the current NFT owner (
INVALID_CALLERotherwise). - On the wire it is the standard TON text comment: op
0x00000000followed by the UTF-8 bytes ofburn, noquery_id. At the TS level the same body isbeginCell().storeUint(0, 32).storeBuffer(Buffer.from("burn")).endCell()— but a real user never assembles it; the wallet encodes the comment under the hood. - Mind the gas: the NFT enforces a minimum attached value (
BURN_GAS_AMOUNT, ~0.1 GRAM) and a burn fans out across NFT → pool → router → jetton wallets, so over-fund to be safe (the tests attach ~0.5 GRAM); unspent gas is returned.
V1 had no text-comment burn — this is a V2 addition. Full field table: BURN_WITH_TEXT.
Reforge
Reforge is the single engine behind minting, burning and doing both at once: in
one pool state transition it can burn up to 4 input positions, mint up to 4 new
ones, and route the freed / leftover jettons and positions to a chosen target.
Everything on this page is a special case of it — a plain mint is a reforge
with no burn orders, a plain burn is a reforge with no mint orders — which is
why the FUND_ACCOUNT body carries the reforge order fields.
You never craft POOL_REFORGE yourself: the pool accepts it only from trusted
contracts (a Position NFT, your per-pool Account, the ALM vault or the Arbiter),
which set the source field. As an integrator you drive one of these — in practice
your Account — and the reforge is emitted for you.
The full description — inputs (order + funds, order only, funding someone else's
account, depositing positions), the ReforgeMessage layout and all possible
outputs — lives with the pool contract:
- POOL_REFORGE — the reforge message the pool executes
- ACCOUNT_SET_ORDER — set / replace the order in your Account
- POOL_FUND_ACCOUNT — fund your Account + order (the mint path)
- POOL_FUND_SOMEONES_ACCOUNT — fund another user's Account
Opcode constants were renamed
Values are unchanged; only the names changed (POOLV3_* → POOL_*).
| V1 constant | V2 constant | Value |
|---|---|---|
| POOLV3_FUND_ACCOUNT | POOL_FUND_ACCOUNT | 0x4468de77 |
| POOLV3_START_BURN | POOL_START_BURN | 0x530b5f2c |
| POOLV3_BURN | POOL_BURN | 0xd73ac09d |
Migration checklist
Mint
-
Build the body from the order structure (
MintOrder), not the old flat layout. -
Per order: set
op(MINT_NOT_LESS/MINT_AS_MUCH_AS_POSSIBLE) andreceiver. -
Fill new fields
needPos/passthrough/target_action(usually0). -
Reproduce the
enough0/enough1swap byswapIdsif you assemble by hand.
Burn
-
Append the trailing
actionmaybe-ref — at minimumstoreMaybeRef(null)(otherwise V2 parsing fails). -
Optionally fill the action cell (receivers, forward payloads,
excess_address).
Both
-
Rename opcode constants
POOLV3_*→POOL_*(values unchanged). - Remember: same opcode ≠ compatible body — there is no silent backward compatibility.