Skip to main content

V3 Locker

V3 Locker holds ERC721 liquidity positions issued by allowlisted INonfungiblePositionManager contracts. The NFT price range cannot change while locked. Authorized accounts can collect fees, and any user can increase liquidity.

ETERNAL_LOCK is type(uint256).max. A permanent lock cannot be withdrawn or have liquidity removed, but it can be transferred, collect fees, and receive more liquidity.

Write Functions

lock

function lock(LockParams calldata params)
external
payable
returns (uint256 lockId)

Transfers the position NFT into custody, collects fees accrued before locking, applies the selected lock fee, and creates a lock.

FieldTypeDescription
params.nftPositionManagerINonfungiblePositionManagerAllowlisted Position Manager.
params.nft_iduint256NFT token ID owned and approved by the caller.
params.dustRecipientaddressRecipient of fees accrued before locking.
params.owneraddressInitial lock owner; must be nonzero.
params.additionalCollectoraddressOptional additional collector.
params.collectAddressaddressRecipient of net proceeds from automatic collection; must be nonzero.
params.unlockDateuint256Future Unix timestamp in seconds, or ETERNAL_LOCK.
params.countryCodeuint16Code accepted by COUNTRY_LIST.
params.feeNamestringNamed fee option, usually DEFAULT; ignored when r is nonempty.
params.rbytes[]Empty for a named fee; FeeResolver payload for a dynamic fee.
ReturnTypeDescription
lockIduint256Newly created lock ID.

Access / payable: NFT owner/caller; payable; nonReentrant.

The Position Manager, owner, collection address, date, and country/region must be valid. The caller must approve the NFT. Fees already accrued by the position are first collected to dustRecipient, so they are not charged as newly locked liquidity. For a native fixed fee, msg.value must match exactly; an ERC20 fixed fee requires allowance. The stored ucf is the selected collection-fee rate. The function increments NONCE and emits onLock.

When params.r is nonempty, FEE_RESOLVER.useFee(params.r, msg.sender) supplies a dynamic fee. The signed payload binds its nonce, user, chain, and Resolver.

collect

function collect(
uint256 _lockId,
address _recipient,
uint128 _amount0Max,
uint128 _amount1Max
) external returns (
uint256 amount0,
uint256 amount1,
uint256 fee0,
uint256 fee1
)

Collects token0 and token1 fees from a locked position.

NameTypeDescription
_lockIduint256Lock whose fees are collected.
_recipientaddressNet-proceeds recipient for normal callers; protocol-fee recipient for the Auto Collector.
_amount0Maxuint128Maximum token0 amount to collect.
_amount1Maxuint128Maximum token1 amount to collect.
ReturnTypeDescription
amount0uint256Net token0 delivered to the user-side recipient.
amount1uint256Net token1 delivered to the user-side recipient.
fee0uint256Protocol token0 fee.
fee1uint256Protocol token1 fee.

Access / payable: lock owner, additionalCollector, or AUTO_COLLECT_ACCOUNT; nonpayable; nonReentrant.

For a normal caller, protocol fees go to FEE_ADDR_COLLECT and net proceeds go to _recipient. For AUTO_COLLECT_ACCOUNT, fees go to _recipient and net proceeds go to the lock's collectAddress. If ucf == 0, the Position Manager sends all collected tokens directly to _recipient, including an Auto Collector call. Treat AUTO_COLLECT_ACCOUNT as trusted and avoid zero recipients.

collectBatchPreview

function collectBatchPreview(
uint256[] calldata lockIds,
address[] calldata recipients,
uint128[] calldata amount0Maxs,
uint128[] calldata amount1Maxs
) external

Simulates multiple collect calls and returns exact results through an intentional revert.

NameTypeDescription
lockIdsuint256[]Locks to preview.
recipientsaddress[]Recipient parameter for each collect call.
amount0Maxsuint128[]Maximum token0 amount for each lock.
amount1Maxsuint128[]Maximum token1 amount for each lock.

Access / payable: each item uses the same authorization as collect; nonpayable; nonReentrant.

All arrays must have equal length or the function reverts with LengthMismatch(). Call it only through eth_call. A successful preview intentionally reverts with:

error CollectBatchPreviewResult(CollectPreviewItem[] previews);

Each item contains the lock ID, recipient, token addresses, net amounts, and fees. The revert atomically rolls back Position Manager, token, and Locker state. Any other revert indicates a real validation or execution failure. Do not broadcast this function as a transaction.

increaseLiquidity

function increaseLiquidity(
uint256 _lockId,
INonfungiblePositionManager.IncreaseLiquidityParams calldata params
) external payable returns (
uint128 liquidity,
uint256 amount0,
uint256 amount1
)

Adds token0 and token1 liquidity to a locked position.

NameTypeDescription
_lockIduint256Lock receiving liquidity.
params.tokenIduint256Must equal the lock's NFT ID.
params.amount0Desireduint256Maximum token0 transferred from the caller.
params.amount1Desireduint256Maximum token1 transferred from the caller.
params.amount0Minuint256Minimum token0 spent.
params.amount1Minuint256Minimum token1 spent.
params.deadlineuint256Position Manager deadline.
ReturnTypeDescription
liquidityuint128Liquidity added.
amount0uint256Token0 amount used.
amount1uint256Token1 amount used.

Access / payable: any caller; payable; nonReentrant.

The caller must approve both tokens. Unused tokens are refunded to the caller and onIncreaseLiquidity is emitted. This implementation does not forward msg.value; integrations should send zero native tokens. Direct Position Manager interaction may cost less gas when compatible.

decreaseLiquidity

function decreaseLiquidity(
uint256 _lockId,
INonfungiblePositionManager.DecreaseLiquidityParams calldata params
) external payable returns (uint256 amount0, uint256 amount1)

Removes liquidity after a timed lock expires and sends the resulting tokens to the owner.

NameTypeDescription
_lockIduint256Lock whose liquidity is reduced.
params.tokenIduint256Must equal the lock's NFT ID.
params.liquidityuint128Liquidity to remove.
params.amount0Minuint256Minimum token0 amount credited.
params.amount1Minuint256Minimum token1 amount credited.
params.deadlineuint256Position Manager deadline.
ReturnTypeDescription
amount0uint256Token0 amount credited by decreaseLiquidity.
amount1uint256Token1 amount credited by decreaseLiquidity.

Access / payable: lock owner only; payable; nonReentrant.

The lock must be expired and non-permanent. All currently accrued fees are first collected through the Locker fee path; then all tokens owed by the NFT are collected to the caller. This implementation does not forward msg.value; send zero. Emits onDecreaseLiquidity.

relock

function relock(uint256 _lockId, uint256 _unlockDate) external

Extends a lock or makes it permanent.

NameTypeDescription
_lockIduint256Lock to extend.
_unlockDateuint256New timestamp in seconds, or ETERNAL_LOCK.

Access / payable: lock owner only; nonpayable; nonReentrant.

The date must exceed both the current unlockDate and block.timestamp; except for a permanent lock it must be below 10_000_000_000. A lock can never be shortened. Emits onRelock.

withdraw

function withdraw(uint256 _lockId, address _receiver) external

Transfers the complete NFT out of an expired timed lock.

NameTypeDescription
_lockIduint256Lock to withdraw.
_receiveraddressNFT recipient.

Access / payable: lock owner only; nonpayable; nonReentrant.

The lock must be expired and non-permanent. If ucf > 0, all fees are first collected with _receiver as the normal recipient. The NFT is then transferred, user-index and LOCKS state are deleted, and onWithdraw is emitted. Use a receiver capable of accepting safe ERC721 transfers.

setAdditionalCollector

function setAdditionalCollector(
uint256 _lockId,
address _additionalCollector
) external

Sets the optional account allowed to call collect.

NameTypeDescription
_lockIduint256Lock to configure.
_additionalCollectoraddressCollector address, or the zero address to clear it.

Access / payable: lock owner only; nonpayable; nonReentrant. Updates the lock and emits onSetAdditionalCollector.

setCollectAddress

function setCollectAddress(
uint256 _lockId,
address _collectAddress
) external

Sets the recipient of net proceeds from automatic collection.

NameTypeDescription
_lockIduint256Lock to configure.
_collectAddressaddressNonzero automatic-collection recipient.

Access / payable: lock owner only; nonpayable; nonReentrant. Emits onSetCollectAddress.

transferLockOwnership

function transferLockOwnership(
uint256 _lockId,
address _newOwner
) external

Starts a two-step lock-ownership transfer.

NameTypeDescription
_lockIduint256Lock to transfer.
_newOwneraddressProposed owner.

Access / payable: lock owner only; nonpayable; nonReentrant.

The new owner must differ from the caller. The contract accepts a zero address, but a zero pending owner cannot accept. A new proposal overwrites an existing pending owner. Emits onLockOwnershipTransferStarted.

acceptLockOwnership

function acceptLockOwnership(
uint256 _lockId,
address _collectAddress
) external

Accepts a pending transfer and chooses the new automatic-collection address.

NameTypeDescription
_lockIduint256Lock to accept.
_collectAddressaddressNew stored collection address.

Access / payable: pending owner only; nonpayable; nonReentrant.

User indexes are moved, pendingOwner and additionalCollector are cleared, and _collectAddress is stored. Unlike setCollectAddress, this function accepts a zero address, so clients must reject it. Emits onTransferLockOwnership.

Read Functions

getLock / LOCKS

function getLock(uint256 _lockId)
external view returns (Lock memory _lock);
function LOCKS(uint256 lockId) external view returns (
uint256 lock_id,
INonfungiblePositionManager nftPositionManager,
address pool,
uint256 nft_id,
address owner,
address pendingOwner,
address additionalCollector,
address collectAddress,
uint256 unlockDate,
uint16 countryCode,
uint256 ucf
);

Both return the same fields: getLock returns a struct and the public mapping getter returns an ABI tuple. Withdrawn or unknown IDs return an all-zero lock.

ParameterDescription
_lockId / lockIdLock ID to read.

getLocksLength / NONCE

function getLocksLength() external view returns (uint256);
function NONCE() external view returns (uint256);

Both return the next lock ID, including historical withdrawn locks. Neither is the active-lock count.

User Enumeration

function getNumUserLocks(address _user)
external view returns (uint256);
function getUserLockAtIndex(address _user, uint256 _index)
external view returns (Lock memory);
ParameterDescription
_userOwner whose active lock set is queried.
_indexZero-based active-set index; an out-of-range access reverts.

The first returns the active-lock count. The second resolves the indexed ID and returns its lock.

Fee Queries

function getFee(string memory _name)
public view returns (FeeStruct memory);
function getFeeOptionAtIndex(uint256 _index)
external view returns (FeeStruct memory);
function getFeeOptionLength() external view returns (uint256);
ParameterDescription
_nameExact fee-option name; an unknown name reverts.
_indexZero-based fee-set index; an invalid index reverts.

FeeStruct contains name, lpFee, collectFee, flatFee, and flatFeeToken. Options can change immediately, so read them just before locking.

nftPositionManagerIsAllowed

function nftPositionManagerIsAllowed(address _nftPositionManager)
external view returns (bool);

Returns whether the Position Manager may be used by lock.

Public Configuration Getters

function ETERNAL_LOCK() external view returns (uint256);
function FEE_DENOMINATOR() external view returns (uint256);
function COUNTRY_LIST() external view returns (address);
function FEE_RESOLVER() external view returns (address);
function AUTO_COLLECT_ACCOUNT() external view returns (address);
function FEE_ADDR_LP() external view returns (address);
function FEE_ADDR_COLLECT() external view returns (address);
function owner() external view returns (address);

These compiler-generated or inherited getters expose permanent-lock and fee constants, external dependencies, privileged addresses, and two-step contract ownership.

Events

onLock

event onLock(uint256 lock_id, address nftPositionManager, uint256 nft_id, address owner, address additionalCollector, address collectAddress, uint256 unlockDate, uint16 countryCode, uint256 collectFee, address poolAddress, INonfungiblePositionManager.Position position);

Emitted when lock creates a lock. It includes the IDs and accounts, unlock terms, stored ucf rate, pool address, and a full Position snapshot.

ParameterDescription
lock_idNew lock ID.
nftPositionManagerPosition Manager that defines the NFT.
nft_idPosition NFT ID.
ownerInitial lock owner.
additionalCollectorOptional collector.
collectAddressRecipient of net automatic-collection proceeds.
unlockDateUnlock timestamp or ETERNAL_LOCK.
countryCodeValidated country/region code.
collectFeeStored ucf rate.
poolAddressFactory pool for the position.
positionFull Position Manager Position snapshot.

onWithdraw

event onWithdraw(uint256 lock_id, address owner, address receiver);

Emitted before the withdrawn lock state is deleted.

ParameterDescription
lock_idWithdrawn lock.
ownerLock owner.
receiverNFT recipient.

onLockOwnershipTransferStarted

event onLockOwnershipTransferStarted(uint256 lockId, address currentOwner, address pendingOwner);

Emitted when the current owner proposes a new owner.

ParameterDescription
lockIdLock being transferred.
currentOwnerCurrent owner.
pendingOwnerProposed owner.

onTransferLockOwnership

event onTransferLockOwnership(uint256 lockId, address oldOwner, address newOwner, address newCollectAddress);

Emitted when the pending owner accepts the transfer.

ParameterDescription
lockIdTransferred lock.
oldOwnerPrevious owner.
newOwnerNew owner accepting the transfer.
newCollectAddressCollection address supplied during acceptance.

onSetAdditionalCollector

event onSetAdditionalCollector(uint256 lockId, address additionalCollector);

Emitted when the owner updates the additional collector, which may be zero.

ParameterDescription
lockIdUpdated lock.
additionalCollectorNew collector, which may be the zero address.

onSetCollectAddress

event onSetCollectAddress(uint256 lockId, address collectAddress);

Emitted when the owner updates the nonzero automatic-collection recipient.

ParameterDescription
lockIdUpdated lock.
collectAddressNew nonzero recipient.

onRelock

event onRelock(uint256 lockId, uint256 unlockDate);

Emitted after extending a lock.

ParameterDescription
lockIdExtended lock.
unlockDateNew unlock timestamp.

onIncreaseLiquidity

event onIncreaseLiquidity(uint256 lockId);

Emitted after adding liquidity through the Locker wrapper.

ParameterDescription
lockIdLock whose liquidity increased.

onDecreaseLiquidity

event onDecreaseLiquidity(uint256 lockId);

Emitted after reducing liquidity from an expired lock through the Locker.

ParameterDescription
lockIdLock whose liquidity decreased.

Errors

Locker Business Errors

Error / RevertRelated functionsTrigger
OWNER CANNOT = address(0)lockparams.owner is zero.
COLLECT_ADDRlock, setCollectAddresscollectAddress is zero. Note that acceptLockOwnership does not perform this check.
MILLISECONDSlock, relockA normal timestamp is at least 10_000_000_000, usually indicating milliseconds.
DATE PASSEDlock, relockUnlock time is not later than the current block time.
COUNTRYlockcountryCode fails COUNTRY_LIST.
INVALID NFT POSITION MANAGERlockPosition Manager is not allowlisted.
NOT FOUNDlock, getFeeNamed fee option does not exist.
FLAT FEElockNative msg.value does not exactly match the named fee.
Gas token transfer failedlock, adminRefundEthNative-token transfer to the fee or refund recipient fails.
OWNERcollect, collectBatchPreview, lock-management functionsCaller is not an allowed owner, additional collector, Auto Collector, or pending owner.
NFT IDincreaseLiquidity, decreaseLiquidityparams.tokenId differs from the stored nft_id.
ETERNAL_LOCKdecreaseLiquidity, withdrawLiquidity removal or NFT withdrawal is attempted on a permanent lock.
NOT YETdecreaseLiquidity, withdrawA timed lock has not expired.
DATErelockNew unlock time is not strictly greater than the current unlockDate.
SAME OWNERtransferLockOwnership_newOwner equals the current owner.
LsetUCFNew _ucf is not strictly below the lock's current rate.
DEFAULTremoveFeeRemoval of the built-in DEFAULT option is attempted.
Fee not existsremoveFeeFee option does not exist.

Batch Preview Custom Errors

error CollectBatchPreviewResult(CollectPreviewItem[] previews);
error LengthMismatch();
Custom ErrorMeaning
CollectBatchPreviewResult(...)Expected success payload for collectBatchPreview. Decode previews as a result.
LengthMismatch()The lengths of lockIds, recipients, amount0Maxs, and amount1Maxs differ.

External Dependency and Inherited Errors

Error / RevertTrigger
STFTransferHelper.safeTransferFrom fails, usually because balance or allowance is insufficient.
STTransferHelper.safeTransfer fails.
SATransferHelper.safeApprove fails.
Ownable: caller is not the ownerA non-owner calls an administrative function.
ReentrancyGuard: reentrant callA nonReentrant function is reentered.
Panic(0x11)Solidity arithmetic underflows or overflows.