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: Mint & Burn

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:

NB!

Info

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.

OperationOpcode (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

Info

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.

Mint flow (unchanged): two funded parts meet in the Account, which then triggers the mint
Mint flow (unchanged): two funded parts meet in the Account, which then triggers the mint

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

FieldNotes
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

  1. 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).
  2. For each position, set op (MINT_NOT_LESS for V1-equivalent behaviour) and receiver. Note MINT_AS_MUCH_AS_POSSIBLE can be overriden by the pool. You can check it beforehand using pool flags
  3. Fill the new needPos / passthrough / target_action fields (all 0 for an ordinary mint).
  4. enough0 / enough1 are in pool order. The order flips with the pool's internal jetton swap (swapIds): enough0 = !swapIds ? jetton0 : jetton1, and symmetrically for enough1. 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 ^Cell at 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 at tickUpper) makes the V2 parser overrun the cell and fail.

Minimum change for a plain burn: append .storeMaybeRef(null) — a single 0 bit.

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 a burn <amount> form is rejected with WRONG_COMMAND). This path always burns the full position.
  • Accepted only from the current NFT owner (INVALID_CALLER otherwise).
  • On the wire it is the standard TON text comment: op 0x00000000 followed by the UTF-8 bytes of burn, no query_id. At the TS level the same body is beginCell().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.

Burn via a plain wallet text comment
Burn via a plain wallet text comment

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:

Opcode constants were renamed

Values are unchanged; only the names changed (POOLV3_*POOL_*).

V1 constantV2 constantValue
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) and receiver.
  • Fill new fields needPos / passthrough / target_action (usually 0).
  • Reproduce the enough0 / enough1 swap by swapIds if you assemble by hand.

Burn

  • Append the trailing action maybe-ref — at minimum storeMaybeRef(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.