V2 Locker
V2 Locker 托管由已配置 BDEX V2 Factory 创建的 ERC20 LP 代币。ETERNAL_LOCK 为 type(uint256).max;永久锁仓无法提取或缩短期限,但可以增加锁仓量、拆分和转移。
写入函数
lockLPToken
function lockLPToken(
address _lpToken,
uint256 _amount,
uint256 _unlock_date,
address payable _referral,
bool _fee_in_eth,
address payable _withdrawer,
uint16 _countryCode
) external payable
从调用方转入 LP 并扣除当前费用后创建锁仓。
| 名称 | 类型 | 说明 |
|---|---|---|
_lpToken | address | BDEX V2 交易对代币。 |
_amount | uint256 | 扣费前转入的 LP 数量。 |
_unlock_date | uint256 | 以秒为单位的 Unix 时间戳,或 ETERNAL_LOCK。 |
_referral | address payable | 符合条件的推荐人,或零地址。 |
_fee_in_eth | bool | true 表示使用原生代币支付固定费用;false 表示使用次级代币。 |
_withdrawer | address payable | 初始锁仓 Owner。 |
_countryCode | uint16 | COUNTRY_LIST 接受的代码。 |
访问权限 / payable: 任意调用方;payable;nonReentrant。
_amount 必须非零。除 ETERNAL_LOCK 外,解锁日期必须是未来时间且小于 10_000_000_000;国家/地区和 Factory 交易对必须有效。调用方需要提供 LP allowance;若选择次级代币付费,还需提供次级代币 allowance。未列入白名单的原生代币费用支付方必须发送与计算结果完全一致的费用。合约不会拒绝零地址 _withdrawer,因此客户端应拒绝。amount 和 initialAmount 存储的是扣除 LP 费用后的数量;随后 NONCE 递增并触发 onNewLock。
relock
function relock(uint256 _lockID, uint256 _unlock_date) external
延长锁仓期限,或将其转换为永久锁仓。
| 名称 | 类型 | 说明 |
|---|---|---|
_lockID | uint256 | 要延长的锁仓。 |
_unlock_date | uint256 | 以秒为单位的新时间戳,或 ETERNAL_LOCK。 |
访问权限 / payable: 仅锁仓 Owner;nonpayable;nonReentrant。
新值必须大于当前 unlockDate,并通过秒级格式检查。合约不会另外要求它大于 block.timestamp;客户端应要求 _unlock_date > max(current unlockDate, current block time)。合约会从全部剩余数量中扣除当前 LP 费用并触发 onRelock。该函数没有最小接收数量参数。
withdraw
function withdraw(uint256 _lockID, uint256 _amount) external
从已到期的定时锁仓中提取部分或全部资产。
| 名称 | 类型 | 说明 |
|---|---|---|
_lockID | uint256 | 来源锁仓。 |
_amount | uint256 | 要提取的数量;type(uint256).max 表示全部剩余 LP。 |
访问权限 / payable: 仅锁仓 Owner;nonpayable;nonReentrant。
数量必须非零,锁仓不能是永久锁仓,且必须满足 unlockDate < block.timestamp。全部提取会从 Owner 的有效索引中删除该 ID,但保留 amount 为零的历史 LOCKS 记录。触发 onWithdraw。
incrementLock
function incrementLock(uint256 _lockID, uint256 _amount) external
向已有锁仓增加 LP。
| 名称 | 类型 | 说明 |
|---|---|---|
_lockID | uint256 | 接收增资的锁仓。 |
_amount | uint256 | 扣费前提供的 LP 数量。 |
访问权限 / payable: 任意调用方;nonpayable;nonReentrant。
该函数特意不检查 Owner:增资的 LP 归已有锁仓 Owner 所有。数量必须非零且已授权。客户端应验证 LOCKS(_lockID).lpToken != address(0)。扣除当前 LP 费用后,净数量计入 amount,但不计入 initialAmount,并触发 onIncrementLock。
splitLock
function splitLock(uint256 _lockID, uint256 _amount) external payable
将锁仓的一部分转入具有相同 Owner 和条款的新锁仓。
| 名称 | 类型 | 说明 |
|---|---|---|
_lockID | uint256 | 来源锁仓。 |
_amount | uint256 | 分配给子锁仓的数量。 |
访问权限 / payable: 仅锁仓 Owner;payable;nonReentrant。
数量必须非零且不得大于来源锁仓余额。即使是白名单账户,msg.value 也必须等于 gFees.ethFee。子锁仓继承 lpToken、lockDate、unlockDate、Owner 和国家/地区代码;其 amount 和 initialAmount 均等于 _amount。触发 onSplitLock 和 onNewLock。
transferLockOwnership
function transferLockOwnership(
uint256 _lockID,
address payable _newOwner
) external
立即转移锁仓及其有效索引归属。
| 名称 | 类型 | 说明 |
|---|---|---|
_lockID | uint256 | 要转移的锁仓。 |
_newOwner | address payable | 新锁仓 Owner。 |
访问权限 / payable: 仅锁仓 Owner;nonpayable。
新 Owner 必须与调用方不同。这是无需接受调用的单步转移。合约不会拒绝零地址,因此客户端应拒绝。触发 onTransferLockOwnership。
查询函数
LOCKS
function LOCKS(uint256 _lockID) external view returns (
address lpToken,
uint256 lockDate,
uint256 amount,
uint256 initialAmount,
uint256 unlockDate,
uint256 lockID,
address owner,
uint16 countryCode
)
锁仓的 public mapping Getter。amount 是剩余 LP 数量,initialAmount 是创建时的净数量。未知 ID 返回零值。
TOKEN_LOCKS
function TOKEN_LOCKS(address lpToken, uint256 index)
external view returns (uint256 lockID)
返回 LP 代币在 index 位置的历史锁仓 ID。
代币枚举
function getNumLocksForToken(address _lpToken)
external view returns (uint256);
function getNumLockedTokens() external view returns (uint256);
function getLockedTokenAtIndex(uint256 _index)
external view returns (address);
| 参数 | 说明 |
|---|---|
_lpToken | 要返回其历史锁仓数量的 LP 代币。 |
_index | 从零开始的全局 LP 代币索引;越界访问会 revert。 |
全局代币集合不会裁剪,而 TOKEN_LOCKS 包含历史锁仓 ID。
用户枚举
function getUserNumLockedTokens(address _user)
external view returns (uint256);
function getUserLockedTokenAtIndex(address _user, uint256 _index)
external view returns (address);
function getUserNumLocksForToken(address _user, address _lpToken)
external view returns (uint256);
function getUserLockForTokenAtIndex(
address _user,
address _lpToken,
uint256 _index
) external view returns (TokenLock memory);
| 参数 | 说明 |
|---|---|
_user | 要查询有效索引的 Owner。 |
_lpToken | 该 Owner 索引中的 LP 代币。 |
_index | 从零开始的索引;越界访问会 revert。 |
这些函数分别返回有效代币组数量、按索引查询的代币、某代币的有效锁仓数量以及完整锁仓。已全部提取的锁仓会被移除。
费用白名单查询
function getWhitelistedUsersLength() external view returns (uint256);
function getWhitelistedUserAtIndex(uint256 _index)
external view returns (address);
function getUserWhitelistStatus(address _user)
external view returns (bool);
这些函数分别返回白名单长度、按索引查询的地址以及成员状态。无效索引会 revert。
公共配置 Getter
function NONCE() external view returns (uint256);
function ETERNAL_LOCK() external view returns (uint256);
function uniswapFactory() external view returns (address);
function COUNTRY_LIST() external view returns (address);
function owner() external view returns (address);
function gFees() external view returns (
uint256 ethFee,
address secondaryFeeToken,
uint256 secondaryTokenFee,
uint256 secondaryTokenDiscount,
uint256 liquidityFee,
uint256 referralPercent,
address referralToken,
uint256 referralHold,
uint256 referralDiscount
);
NONCE 是下一个锁仓 ID,并非有效锁仓数量。百分比字段使用分母 1000(10 = 1%)。费用可以立即变更;提交写入交易前应重新读取并模拟。
事件
onNewLock
event onNewLock(uint256 lockID, address lpToken, address owner, uint256 amount, uint256 lockDate, uint256 unlockDate, uint16 countryCode);
由 lockLPToken 以及 splitLock 创建子锁仓时触发。
| 参数 | 说明 |
|---|---|
lockID | 新锁仓 ID。 |
lpToken | 已锁仓交易对代币。 |
owner | 锁仓 Owner。 |
amount | 存储的净数量。 |
lockDate | 创建时间戳。 |
unlockDate | 解锁时间戳或 ETERNAL_LOCK。 |
countryCode | 已验证的国家/地区代码。 |
onRelock
event onRelock(uint256 lockID, address lpToken, address owner, uint256 amountRemainingInLock, uint256 liquidityFee, uint256 unlockDate);
延长锁仓并扣费后触发。
| 参数 | 说明 |
|---|---|
lockID | 已延长的锁仓。 |
lpToken | 交易对代币。 |
owner | 锁仓 Owner。 |
amountRemainingInLock | 净剩余数量。 |
liquidityFee | 已扣除的 LP 费用。 |
unlockDate | 新解锁时间。 |
onWithdraw
event onWithdraw(uint256 lockID, address lpToken, address owner, uint256 amountRemainingInLock, uint256 amountRemoved);
部分或全部提取后触发。
| 参数 | 说明 |
|---|---|
lockID | 已提取的锁仓。 |
lpToken | 交易对代币。 |
owner | Owner 和接收方。 |
amountRemainingInLock | 剩余 LP。 |
amountRemoved | 已转出的 LP。 |
onIncrementLock
event onIncrementLock(uint256 lockID, address lpToken, address owner, address payer, uint256 amountRemainingInLock, uint256 amountAdded, uint256 liquidityFee);
向锁仓增资 LP 时触发。
| 参数 | 说明 |
|---|---|
lockID | 已增加数量的锁仓。 |
lpToken | 交易对代币。 |
owner | 受益 Owner。 |
payer | LP 提供方。 |
amountRemainingInLock | 新的剩余总量。 |
amountAdded | 增加的净 LP 数量。 |
liquidityFee | 已扣除的 LP 费用。 |
onSplitLock
event onSplitLock(uint256 lockID, address lpToken, address owner, uint256 amountRemainingInLock, uint256 amountRemoved);
创建子锁仓时,针对来源锁仓触发。
| 参数 | 说明 |
|---|---|
lockID | 来源锁仓。 |
lpToken | 交易对代币。 |
owner | 两个锁仓的 Owner。 |
amountRemainingInLock | 拆分后来源锁仓的数量。 |
amountRemoved | 子锁仓数量。 |
onTransferLockOwnership
event onTransferLockOwnership(uint256 lockID, address lpToken, address oldOwner, address newOwner);
单步所有权转移完成时触发。
| 参数 | 说明 |
|---|---|
lockID | 已转移的锁仓。 |
lpToken | 交易对代币。 |
oldOwner | 原 Owner。 |
newOwner | 新 Owner。 |
错误
以下是 V2 Locker 业务路径中可能出现的主要 revert。错误字符串必须按原文匹配,尤其注意合约中的 ZERO WITHDRAWL 使用了该拼写。
| Error / Revert | 相关函数 | 触发条件 |
|---|---|---|
TIMESTAMP INVALID | lockLPToken、relock | 普通解锁时间戳大于等于 10_000_000_000,疑似误传毫秒时间戳。 |
DATE PASSED | lockLPToken | 普通解锁时间不晚于当前区块时间。 |
INSUFFICIENT | lockLPToken | _amount == 0。 |
COUNTRY | lockLPToken | _countryCode 未通过 COUNTRY_LIST 校验。 |
NOT UNIV2 | lockLPToken | _lpToken 不是已配置 BDEX V2 Factory 创建的交易对。 |
INADEQUATE BALANCE | lockLPToken | 推荐人没有持有满足 referralHold 要求的推荐代币。 |
FEE NOT MET | lockLPToken、splitLock | msg.value 与当前所需原生代币费用不完全相等。 |
NOT OWNER | relock、withdraw、splitLock、transferLockOwnership | 调用方不是锁仓 Owner。 |
UNLOCK BEFORE | relock | 新解锁时间没有严格大于当前 unlockDate。 |
ZERO WITHDRAWL | withdraw | _amount == 0,或解析后的实际提取数量为零。 |
ETERNAL_LOCK | withdraw | 尝试提取永久锁仓。 |
NOT YET | withdraw | 普通锁仓尚未到期。 |
ZERO AMOUNT | incrementLock、splitLock | 增加或拆分数量为零。 |
OWNER | transferLockOwnership | _newOwner 与当前调用方相同。 |
TransferHelper: APPROVE_FAILED | Token 授权路径 | 目标 ERC20 的 approve 调用失败或返回 false。 |
TransferHelper: TRANSFER_FAILED | Token 转出路径 | 目标 ERC20 的 transfer 调用失败或返回 false。 |
TransferHelper: TRANSFER_FROM_FAILED | lockLPToken、incrementLock | 余额、allowance 不足,或 ERC20 transferFrom 调用失败。 |
Panic(0x11) | withdraw、splitLock 等数量运算 | 请求数量超过锁仓剩余数量,或费率导致 Solidity 算术下溢/上溢。 |
管理函数和重入保护还可能返回 OpenZeppelin 的继承错误:
| Error / Revert | 触发条件 |
|---|---|
Ownable: caller is not the owner | 非合约 Owner 调用管理函数。 |
ReentrancyGuard: reentrant call | 对 nonReentrant 函数发起重入调用。 |
底层 Factory、LP Token、推荐代币或费用代币产生的 revert 会原样向上传播,前端应保留原始 revert data。