V3 Locker
V3 Locker 托管由白名单 INonfungiblePositionManager 合约发行的 ERC721 流动性仓位。锁仓期间无法更改 NFT 价格区间;获授权账户可以收取手续费,任何用户都可以增加流动性。
ETERNAL_LOCK 为 type(uint256).max。永久锁仓无法提取或减少流动性,但可以转移、收取手续费和增加流动性。
写入函数
lock
function lock(LockParams calldata params)
external
payable
returns (uint256 lockId)
将仓位 NFT 转入托管,收取锁仓前已累积的手续费,应用所选锁仓费用并创建锁仓。
| 字段 | 类型 | 说明 |
|---|---|---|
params.nftPositionManager | INonfungiblePositionManager | 已列入白名单的 Position Manager。 |
params.nft_id | uint256 | 由调用方持有并已授权的 NFT token ID。 |
params.dustRecipient | address | 锁仓前已累积手续费的接收方。 |
params.owner | address | 初始锁仓 Owner;必须非零。 |
params.additionalCollector | address | 可选的 Additional Collector。 |
params.collectAddress | address | Owner 自动收取净收益的接收方;必须非零。 |
params.unlockDate | uint256 | 以秒为单位的未来 Unix 时间戳,或 ETERNAL_LOCK。 |
params.countryCode | uint16 | COUNTRY_LIST 接受的代码。 |
params.feeName | string | 命名费用选项,通常为 DEFAULT;r 非空时忽略。 |
params.r | bytes[] | 使用命名费用时为空;使用动态费用时为 FeeResolver 载荷。 |
| 返回值 | 类型 | 说明 |
|---|---|---|
lockId | uint256 | 新创建的锁仓 ID。 |
访问权限 / payable: NFT Owner/调用方;payable;nonReentrant。
Position Manager、Owner、收取地址、日期和国家/地区必须有效。调用方必须授权 NFT。在扣除 LP 费用前,仓位原有手续费会先收取至 dustRecipient,避免将这些手续费作为新增锁仓流动性计费。使用原生代币支付固定费用时,msg.value 必须精确匹配;使用 ERC20 支付固定费用时需要 allowance。存储的 ucf 是所选手续费收取费率。函数递增 NONCE 并触发 onLock。
当 params.r 非空时,FEE_RESOLVER.useFee(params.r, msg.sender) 提供动态费用。签名费用载荷会绑定 nonce、用户、链和 Resolver。
collect
function collect(
uint256 _lockId,
address _recipient,
uint128 _amount0Max,
uint128 _amount1Max
) external returns (
uint256 amount0,
uint256 amount1,
uint256 fee0,
uint256 fee1
)
从已锁仓仓位中收取 token0 和 token1 手续费。
| 名称 | 类型 | 说明 |
|---|---|---|
_lockId | uint256 | 要收取手续费的锁仓。 |
_recipient | address | 普通调用的净收益接收方;Auto Collector 调用时的协议费用接收方。 |
_amount0Max | uint128 | token0 最大收取数量。 |
_amount1Max | uint128 | token1 最大收取数量。 |
| 返回值 | 类型 | 说明 |
|---|---|---|
amount0 | uint256 | 交付给用户侧接收方的 token0 净数量。 |
amount1 | uint256 | 交付给用户侧接收方的 token1 净数量。 |
fee0 | uint256 | 协议 token0 费用。 |
fee1 | uint256 | 协议 token1 费用。 |
访问权限 / payable: 锁仓 Owner、additionalCollector 或 AUTO_COLLECT_ACCOUNT;nonpayable;nonReentrant。
对于普通调用方,协议费用发送至 FEE_ADDR_COLLECT,净收益发送至 _recipient。对于 AUTO_COLLECT_ACCOUNT,费用发送至 _recipient,净收益发送至锁仓的 collectAddress。如果 ucf == 0,Position Manager 会将所有已收取代币直接发送至 _recipient,Auto Collector 调用时亦如此。应将 AUTO_COLLECT_ACCOUNT 视为受信任账户,并避免使用零地址接收方。
collectBatchPreview
function collectBatchPreview(
uint256[] calldata lockIds,
address[] calldata recipients,
uint128[] calldata amount0Maxs,
uint128[] calldata amount1Maxs
) external
模拟多次 collect 调用,并通过主动 revert 返回精确结果。
| 名称 | 类型 | 说明 |
|---|---|---|
lockIds | uint256[] | 要预览的锁仓。 |
recipients | address[] | 每次 collect 的接收方参数。 |
amount0Maxs | uint128[] | 各锁仓的 token0 最大值。 |
amount1Maxs | uint128[] | 各锁仓的 token1 最大值。 |
访问权限 / payable: 每一项均采用与 collect 相同的授权;nonpayable;nonReentrant。
所有数组长度必须相等,否则函数以 LengthMismatch() revert。只能通过 eth_call 调用;预览成功时会按设计以以下错误 revert:
error CollectBatchPreviewResult(CollectPreviewItem[] previews);
每一项都包含 Lock ID、接收方、代币地址、净数量和费用。revert 会原子化回滚 Position Manager、代币和 Locker 的状态。任何其他 revert 都表示真实的验证或执行失败。不要将该函数作为交易广播。
increaseLiquidity
function increaseLiquidity(
uint256 _lockId,
INonfungiblePositionManager.IncreaseLiquidityParams calldata params
) external payable returns (
uint128 liquidity,
uint256 amount0,
uint256 amount1
)
向已锁仓仓位增加 token0 和 token1 流动性。
| 名称 | 类型 | 说明 |
|---|---|---|
_lockId | uint256 | 接收流动性的锁仓。 |
params.tokenId | uint256 | 必须等于锁仓的 NFT ID。 |
params.amount0Desired | uint256 | 从调用方转入的 token0 最大数量。 |
params.amount1Desired | uint256 | 从调用方转入的 token1 最大数量。 |
params.amount0Min | uint256 | token0 最小支出数量。 |
params.amount1Min | uint256 | token1 最小支出数量。 |
params.deadline | uint256 | Position Manager 截止时间。 |
| 返回值 | 类型 | 说明 |
|---|---|---|
liquidity | uint128 | 增加的流动性。 |
amount0 | uint256 | 使用的 token0 数量。 |
amount1 | uint256 | 使用的 token1 数量。 |
访问权限 / payable: 任意调用方;payable;nonReentrant。
调用方必须授权两种代币。未使用的代币会退还给调用方,并触发 onIncreaseLiquidity。该实现不会转发 msg.value;集成方应发送零原生代币。在兼容的情况下,直接与 Position Manager 交互可能更节省 Gas。
decreaseLiquidity
function decreaseLiquidity(
uint256 _lockId,
INonfungiblePositionManager.DecreaseLiquidityParams calldata params
) external payable returns (uint256 amount0, uint256 amount1)
定时锁仓到期后移除流动性,并将所得代币发送给 Owner。
| 名称 | 类型 | 说明 |
|---|---|---|
_lockId | uint256 | 要减少流动性的锁仓。 |
params.tokenId | uint256 | 必须等于锁仓的 NFT ID。 |
params.liquidity | uint128 | 要移除的流动性。 |
params.amount0Min | uint256 | 计入的 token0 最小数量。 |
params.amount1Min | uint256 | 计入的 token1 最小数量。 |
params.deadline | uint256 | Position Manager 截止时间。 |
| 返回值 | 类型 | 说明 |
|---|---|---|
amount0 | uint256 | decreaseLiquidity 计入的 token0 数量。 |
amount1 | uint256 | decreaseLiquidity 计入的 token1 数量。 |
访问权限 / payable: 仅锁仓 Owner;payable;nonReentrant。
锁仓必须已到期且不是永久锁仓。减少流动性前,当前全部应计手续费会通过 Locker 的费用路径收取;之后 NFT 应付的全部代币都会收取给调用方。该实现不会转发 msg.value;请发送零值。触发 onDecreaseLiquidity。
relock
function relock(uint256 _lockId, uint256 _unlockDate) external
延长锁仓期限或将其设为永久锁仓。
| 名称 | 类型 | 说明 |
|---|---|---|
_lockId | uint256 | 要延长的锁仓。 |
_unlockDate | uint256 | 以秒为单位的新时间戳,或 ETERNAL_LOCK。 |
访问权限 / payable: 仅锁仓 Owner;nonpayable;nonReentrant。
日期必须同时大于当前 unlockDate 和 block.timestamp,且除永久锁仓外必须小于 10_000_000_000。锁仓期限永远不能缩短。触发 onRelock。
withdraw
function withdraw(uint256 _lockId, address _receiver) external
从已到期的定时锁仓中转出完整 NFT。
| 名称 | 类型 | 说明 |
|---|---|---|
_lockId | uint256 | 要提取的锁仓。 |
_receiver | address | NFT 接收方。 |
访问权限 / payable: 仅锁仓 Owner;nonpayable;nonReentrant。
锁仓必须已到期且不是永久锁仓。如果 ucf > 0,会先以 _receiver 作为普通接收方收取全部手续费。随后转移 NFT,删除用户索引项和 LOCKS 状态,并触发 onWithdraw。应使用能够接收 safe ERC721 转账的接收方。
setAdditionalCollector
function setAdditionalCollector(
uint256 _lockId,
address _additionalCollector
) external
设置可调用 collect 的可选账户。
| 名称 | 类型 | 说明 |
|---|---|---|
_lockId | uint256 | 要配置的锁仓。 |
_additionalCollector | address | Collector 地址,或使用零地址清除。 |
访问权限 / payable: 仅锁仓 Owner;nonpayable;nonReentrant。更新锁仓并触发 onSetAdditionalCollector。
setCollectAddress
function setCollectAddress(
uint256 _lockId,
address _collectAddress
) external
设置自动收取净收益的接收方。
| 名称 | 类型 | 说明 |
|---|---|---|
_lockId | uint256 | 要配置的锁仓。 |
_collectAddress | address | 非零的自动收取接收方。 |
访问权限 / payable: 仅锁仓 Owner;nonpayable;nonReentrant。触发 onSetCollectAddress。
transferLockOwnership
function transferLockOwnership(
uint256 _lockId,
address _newOwner
) external
发起两步式锁仓所有权转移。
| 名称 | 类型 | 说明 |
|---|---|---|
_lockId | uint256 | 要转移的锁仓。 |
_newOwner | address | 拟议 Owner。 |
访问权限 / payable: 仅锁仓 Owner;nonpayable;nonReentrant。
新 Owner 必须与调用方不同。合约不会拒绝零地址,但零地址 pending Owner 无法接受转移。已有待定所有权会被覆盖。触发 onLockOwnershipTransferStarted。
acceptLockOwnership
function acceptLockOwnership(
uint256 _lockId,
address _collectAddress
) external
接受待定所有权转移,并选择新的自动收取地址。
| 名称 | 类型 | 说明 |
|---|---|---|
_lockId | uint256 | 要接受的锁仓。 |
_collectAddress | address | 新的存储收取地址。 |
访问权限 / payable: 仅 pending Owner;nonpayable;nonReentrant。
用户索引会被移动,pendingOwner 和 additionalCollector 会被清除,并存储 _collectAddress。与 setCollectAddress 不同,该函数不会拒绝零地址;客户端应拒绝。触发 onTransferLockOwnership。
查询函数
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
);
两者返回相同的锁仓字段;getLock 返回 struct,而 public mapping Getter 返回 ABI tuple。已提取或未知 ID 返回全零锁仓。
| 参数 | 说明 |
|---|---|
_lockId / lockId | 要读取的锁仓 ID。 |
getLocksLength / NONCE
function getLocksLength() external view returns (uint256);
function NONCE() external view returns (uint256);
两者都返回下一个 Lock ID,其中包括已提取锁仓的历史记录;两者都不是有效锁仓数量。
用户枚举
function getNumUserLocks(address _user)
external view returns (uint256);
function getUserLockAtIndex(address _user, uint256 _index)
external view returns (Lock memory);
| 参数 | 说明 |
|---|---|
_user | 要查询有效锁仓集合的 Owner。 |
_index | 从零开始的有效集合索引;越界访问会 revert。 |
第一个函数返回有效锁仓数量;第二个函数解析索引对应的 ID 并返回其锁仓。
费用查询
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);
| 参数 | 说明 |
|---|---|
_name | 精确的费用选项名称;未知名称会 revert。 |
_index | 从零开始的费用集合索引;无效索引会 revert。 |
FeeStruct 包含 name、lpFee、collectFee、flatFee 和 flatFeeToken。由于选项可以立即变更,应在锁仓前即时读取。
nftPositionManagerIsAllowed
function nftPositionManagerIsAllowed(address _nftPositionManager)
external view returns (bool);
返回所提供 Position Manager 是否可供 lock 使用。
公共配置 Getter
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);
这些由编译器生成或继承的 Getter 会公开永久锁仓和费用常量、外部依赖、特权地址以及两步式合约所有权。
事件
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);
lock 创建锁仓时触发。事件包含 ID、相关账户、解锁条款、存储的 ucf 费率、Pool 地址和完整 Position 快照。
| 参数 | 说明 |
|---|---|
lock_id | 新锁仓 ID。 |
nftPositionManager | 定义 NFT 的 Position Manager。 |
nft_id | 仓位 NFT ID。 |
owner | 初始锁仓 Owner。 |
additionalCollector | 可选 Collector。 |
collectAddress | 自动收取净收益接收方。 |
unlockDate | 解锁时间戳或 ETERNAL_LOCK。 |
countryCode | 已验证的国家/地区代码。 |
collectFee | 存储的 ucf 费率。 |
poolAddress | 该仓位对应的 Factory 池。 |
position | 完整的 Position Manager Position 快照。 |
onWithdraw
event onWithdraw(uint256 lock_id, address owner, address receiver);
在删除已提取锁仓状态前触发。
| 参数 | 说明 |
|---|---|
lock_id | 已提取的锁仓。 |
owner | 锁仓 Owner。 |
receiver | NFT 接收方。 |
onLockOwnershipTransferStarted
event onLockOwnershipTransferStarted(uint256 lockId, address currentOwner, address pendingOwner);
当前 Owner 提议新 Owner 时触发。
| 参数 | 说明 |
|---|---|
lockId | 正在转移的锁仓。 |
currentOwner | 当前 Owner。 |
pendingOwner | 拟议 Owner。 |
onTransferLockOwnership
event onTransferLockOwnership(uint256 lockId, address oldOwner, address newOwner, address newCollectAddress);
pending Owner 接受转移时触发。
| 参数 | 说明 |
|---|---|
lockId | 已转移的锁仓。 |
oldOwner | 原 Owner。 |
newOwner | 接受转移的新 Owner。 |
newCollectAddress | 接受转移时提供的收取地址。 |
onSetAdditionalCollector
event onSetAdditionalCollector(uint256 lockId, address additionalCollector);
Owner 变更 Additional Collector 时触发,该地址可以为零。
| 参数 | 说明 |
|---|---|
lockId | 已更新的锁仓。 |
additionalCollector | 新 Collector,可以为零地址。 |
onSetCollectAddress
event onSetCollectAddress(uint256 lockId, address collectAddress);
Owner 变更非零自动收取地址时触发。
| 参数 | 说明 |
|---|---|
lockId | 已更新的锁仓。 |
collectAddress | 新的非零接收方。 |
onRelock
event onRelock(uint256 lockId, uint256 unlockDate);
延长锁仓后触发。
| 参数 | 说明 |
|---|---|
lockId | 已延长的锁仓。 |
unlockDate | 新解锁时间戳。 |
onIncreaseLiquidity
event onIncreaseLiquidity(uint256 lockId);
通过 Locker Wrapper 增加流动性后触发。
| 参数 | 说明 |
|---|---|
lockId | 已增加流动性的锁仓。 |
onDecreaseLiquidity
event onDecreaseLiquidity(uint256 lockId);
通过 Locker 减少已到期锁仓的流动性后触发。
| 参数 | 说明 |
|---|---|
lockId | 已减少流动性的锁仓。 |
错误
Locker 业务错误
| Error / Revert | 相关函数 | 触发条件 |
|---|---|---|
OWNER CANNOT = address(0) | lock | params.owner 为零地址。 |
COLLECT_ADDR | lock、setCollectAddress | collectAddress 为零地址。注意 acceptLockOwnership 当前不会执行该检查。 |
MILLISECONDS | lock、relock | 普通解锁时间戳大于等于 10_000_000_000,疑似误传毫秒时间戳。 |
DATE PASSED | lock、relock | 解锁时间不晚于当前区块时间。 |
COUNTRY | lock | countryCode 未通过 COUNTRY_LIST 校验。 |
INVALID NFT POSITION MANAGER | lock | Position Manager 未加入白名单。 |
NOT FOUND | lock、getFee | 指定的命名费用不存在。 |
FLAT FEE | lock | 原生代币 msg.value 与命名费用要求不完全相等。 |
Gas token transfer failed | lock、adminRefundEth | 原生代币向费用地址或退款接收方转账失败。 |
OWNER | collect、collectBatchPreview、锁仓管理函数 | 调用方不是允许的 Owner、Additional Collector、Auto Collector 或 pending Owner。 |
NFT ID | increaseLiquidity、decreaseLiquidity | params.tokenId 与锁仓记录中的 nft_id 不一致。 |
ETERNAL_LOCK | decreaseLiquidity、withdraw | 尝试从永久锁仓减少流动性或提取 NFT。 |
NOT YET | decreaseLiquidity、withdraw | 普通锁仓尚未到期。 |
DATE | relock | 新解锁时间没有严格大于当前 unlockDate。 |
SAME OWNER | transferLockOwnership | _newOwner 与当前 Owner 相同。 |
L | setUCF | 新 _ucf 没有严格小于锁仓当前费率。 |
DEFAULT | removeFee | 尝试删除内置 DEFAULT 费用选项。 |
Fee not exists | removeFee | 要删除的费用选项不存在。 |
批量预览自定义错误
error CollectBatchPreviewResult(CollectPreviewItem[] previews);
error LengthMismatch();
| Custom Error | 含义 |
|---|---|
CollectBatchPreviewResult(...) | collectBatchPreview 的预期成功载荷。客户端应将解码后的 previews 作为结果。 |
LengthMismatch() | lockIds、recipients、amount0Maxs、amount1Maxs 的长度不一致。 |
外部依赖和继承错误
| Error / Revert | 触发条件 |
|---|---|
STF | TransferHelper.safeTransferFrom 失败,通常由余额或 allowance 不足导致。 |
ST | TransferHelper.safeTransfer 失败。 |
SA | TransferHelper.safeApprove 失败。 |
Ownable: caller is not the owner | 非合约 Owner 调用管理函数。 |
ReentrancyGuard: reentrant call | 对 nonReentrant 函数发起重入调用。 |
Panic(0x11) | Solidity 算术发生下溢或上溢。 |