Skip to content

Providing liquidity

Read how to mint, add to, move, split and withdraw a position through the v4 PositionManager, the way the Hookr app sends each one.

On this page

Introduction

Hookr pools take liquidity through the ordinary Uniswap v4 PositionManager. There is no Hookr-specific liquidity contract and no Hookr position NFT.

Minting a Position

Use PositionManager exactly as you would on any v4 pool.

Actions.MINT_POSITION   (poolKey, tickLower, tickUpper, liquidity, amount0Max, amount1Max, owner, hookData)
Actions.SETTLE_PAIR     (currency0, currency1)

Pass empty hookData.

For an ERC-20 currency, approve the token to Permit2, then approve PositionManager as a Permit2 spender with an amount and an expiry. PositionManager pulls through Permit2 and a plain ERC-20 approval to it is not read.

What the Hookr App Sends

The app's mint is one modifyLiquidities call carrying three actions:

Actions.MINT_POSITION   (poolKey, tickLower, tickUpper, liquidity, amount0Max, amount1Max, owner, hookData)
Actions.SETTLE_PAIR     (currency0, currency1)
Actions.SWEEP           (currency, owner)

The two maxima carry a 1% allowance above the debts quoted at review, because the block that mines the mint can sit at a slightly different price and PositionManager reverts MaximumAmountExceeded the moment a debt exceeds its maximum. msg.value is amount0Max for a native leg, and SWEEP returns in the same transaction whatever SETTLE_PAIR did not take, so the allowance is never stranded in the PositionManager. An ERC-20 leg pulls only the exact debt through Permit2, so nothing is left to sweep there.

Managing a Position

Every action the app offers is one modifyLiquidities call. The actions, in the order they are encoded:

In the appActionsNative value sent
Add liquidityMINT_POSITION · SETTLE_PAIR · SWEEPamount0Max
SplitMINT_POSITION × n · SETTLE_PAIR · SWEEPthe summed amount0Max
Increase a positionINCREASE_LIQUIDITY · SETTLE_PAIR · SWEEPamount0Max
Withdraw partDECREASE_LIQUIDITY · TAKE_PAIRnone
Collect feesDECREASE_LIQUIDITY with zero liquidity · TAKE_PAIRnone
Move the rangeBURN_POSITION · MINT_POSITION · CLOSE_CURRENCY × 2 · SWEEPthe net native debt
Rebalance into another poolas a move, with the mint in the other PoolKeythe net native debt
Withdraw allBURN_POSITION · TAKE_PAIRnone

SWEEP appears only when currency0 is native; an ERC-20 pair has nothing to return. An increase keeps the position's range and token id; its share is of the liquidity the position already holds, and it is funded and swept like a mint. Every action is encoded from the Actions bytes in src/lib/lp-position-manager.ts, and the mint and burn sequences reproduce, byte for byte, the calldata a retained fork rehearsal broadcast.

Moving a Range

A move closes the old position and opens the new one inside one PoolManager unlock. BURN_POSITION credits the position's principal and its uncollected fees to the transient delta ledger, MINT_POSITION debits the new range's deposit against the same ledger, and CLOSE_CURRENCY settles each currency's net once: a negative net is paid, a positive net is taken to the owner. Nothing crosses as a transfer for the part that funds the new range in place.

A range move as one unlock with one settlementThe wallet sends one modifyLiquidities call. Inside the PoolManager unlock, BURN_POSITION credits the old position's two currencies and MINT_POSITION debits the new range's; the transient delta ledger nets each currency, CLOSE_CURRENCY settles the net, SWEEP returns unused native value, and the lock checks every delta is zero.Your walletmodifyLiquidities(actions, params, deadline)msg.value = the net native debt:mint amount0Max− burn amount0Minone signature, one transaction1PositionManageractions, in orderBURN_POSITION 0x03MINT_POSITION 0x02CLOSE_CURRENCY 0x12 ×2SWEEP 0x142unlockUniswap v4 PoolManagerunlock, then unlockCallbackeach action moves a delta, not a tokenhook: beforeAddLiquidity on the mint only(hookData length; the guard window)the burn is not hookedlock: every delta must read zeroTransient delta ledger, per currency3actioncurrency0 (ETH)currency1 (token)BURN_POSITION+ withdrawn₀ + fees₀+ withdrawn₁ + fees₁MINT_POSITION− deposit₀− deposit₁net, what settleswithdrawn₀ − deposit₀withdrawn₁ − deposit₁a negative net is paid, a positive net is taken; nothing else is transferredSettlementCLOSE_CURRENCY ×2pays a negative net throughPermit2 or msg.value; takesa positive net to the ownerSWEEPreturns the msg.value the pooldid not take, same transaction4What crosses the wireTwo transfers at most, one per currency, for the net alone.The old position's principal and fees never leave the PoolManager to come back: they fund the new range in place.A native leg is funded up front by msg.value; the SWEEP returns the unused part in the same transaction.Sending the mint's gross amount0Max as value over-funds the call by the burn's credit. The SWEEP would return it,but a wallet that cannot cover the gross cannot re-range at all. The app sends the net.The same ledger lets a split mint n positions with one SETTLE_PAIR, and a rebalance across two pools of one pair net in one call.
A range move as one unlock: the burn's credit and the mint's debit net per currency and only the net settles.

The native value sent is the net debt, the mint's amount0Max less the burn's amount0Min, and never the mint's gross maximum. SWEEP returns what the pool does not take, so the value is never stranded; but a call funded with the gross asks the wallet to hold ETH that is already inside the position, and a wallet that cannot cover it cannot re-range. A rebalance is the same sequence with the mint pointed at another pool of the same pair, in the same currency order, which is the condition under which the two legs net inside one call.

The new position is a new token id. The receipt is accepted only when it carries the burn of the reviewed id and exactly one mint to the connected wallet.

Splitting a Deposit

The Split tile divides one deposit across three of the fixed plans: Full range, Sit still and Dip, then climb, opening on 10%, 30% and 60% of the currency0 amount. The shares are whole percentages that add up to 100; a share of zero drops its range rather than minting an empty position, and a split with one range left is refused. Each range is sized at the current price the way a single plan is and re-priced at submit time, so every position takes both currencies.

The call is MINT_POSITION once per range, one SETTLE_PAIR and one SWEEP, funded with the summed amount0Max. Each position keeps its own range, its own fees and its own token id. The receipt is accepted only when it carries exactly the reviewed number of mints to the connected wallet.