V2 Locker
Complete reference for the V2Locker contract, which handles locking of Uniswap V2 LP tokens (ERC-20).
Contract Address
Monad Testnet:
0xe373C89A77dE728D50c7372C8Fe139B292a7DFE2
Overview
The V2 Locker enables users to lock their Uniswap V2 LP tokens (ERC-20) for a specified duration. While locked, users can:
Add more tokens to existing locks
Extend lock duration
View total locked amounts per token
How V2 Fee Accrual Works
Important: V2 LP tokens work differently from V3 positions. There is no separate fee claiming function for V2, and this is by design:
Trading fees automatically accrue to the LP token's value
Fees aren't claimed separately; they're reflected in the increasing value of the LP tokens themselves
When you unlock your V2 LP tokens, you receive the full value including all accrued fees
No protocol fee is charged on V2 locks
Core Functions
lockLP
Locks LP tokens for a specified duration.
function lockLP(
address lpToken,
uint256 amount,
uint256 unlockTime,
bool permanent,
address condition
) external payableParameters:
lpToken: Address of the LP token contract to lockamount: Amount of LP tokens to lock (in token's decimals)unlockTime: Unix timestamp when tokens can be unlockedpermanent: Whether the lock is permanent (cannot be unlocked)condition: Optional condition contract address (useaddress(0)for time-based only)
Requirements:
Must send
creationFeeasmsg.valueMust have approved LP tokens to this contract
Amount must be > 0
If
conditionis provided, it must be a valid contract
Events Emitted:
event Locked(
address indexed owner,
uint256 indexed lockId,
address indexed lpToken,
uint256 amount,
uint256 unlockTime,
bool permanent,
address condition,
string lpTokenSymbol,
string lpTokenName,
uint8 lpTokenDecimals
);Example:
// Approve LP tokens
IERC20(lpTokenAddress).approve(v2LockerAddress, amount);
// Lock for 90 days
uint256 unlockTime = block.timestamp + 90 days;
v2Locker.lockLP{value: 0.0001 ether}(
lpTokenAddress,
1000 * 1e18, // 1000 LP tokens
unlockTime,
false, // Not permanent
address(0) // No custom condition
);unlock
Unlocks LP tokens after the unlock time.
function unlock(uint256 lockId) externalParameters:
lockId: The ID of the lock to unlock
Requirements:
Must be the lock owner (
msg.sender == lock.owner)Lock must be active
Cannot unlock permanent locks
Current timestamp must be >= unlock time
If condition is set, condition must return
true
Events Emitted:
event Unlocked(
address indexed owner,
uint256 indexed lockId,
address indexed lpToken,
uint256 amount
);Example:
// Unlock tokens
v2Locker.unlock(myLockId);addToLock
Adds more LP tokens to an existing lock.
function addToLock(
uint256 lockId,
uint256 amount
) externalParameters:
lockId: The ID of the lock to add tokens toamount: Amount of LP tokens to add
Requirements:
Must be the lock owner
Lock must be active
Amount must be > 0
Must have approved additional tokens
Events Emitted:
event LockIncreased(
address indexed owner,
uint256 indexed lockId,
uint256 addedAmount,
uint256 newTotalAmount
);Example:
// Add 500 more LP tokens to existing lock
uint256 additionalAmount = 500 * 1e18;
IERC20(lpToken).approve(v2LockerAddress, additionalAmount);
v2Locker.addToLock(myLockId, additionalAmount);extendLock
Extends the unlock time of an existing lock.
function extendLock(
uint256 lockId,
uint256 newUnlockTime
) externalParameters:
lockId: The ID of the lock to extendnewUnlockTime: New unlock timestamp (must be later than current)
Requirements:
Must be the lock owner
Lock must be active
Cannot extend permanent locks
New unlock time must be > current unlock time
Events Emitted:
event LockExtended(
address indexed owner,
uint256 indexed lockId,
uint256 oldUnlockTime,
uint256 newUnlockTime
);Example:
// Extend lock by 30 more days
uint256 currentUnlock = lock.unlockTime;
uint256 newUnlockTime = currentUnlock + 30 days;
v2Locker.extendLock(myLockId, newUnlockTime);View Functions
getOwnerLockIds
Returns all lock IDs owned by an address.
function getOwnerLockIds(address owner) external view returns (uint256[] memory)Example:
uint256[] memory myLocks = v2Locker.getOwnerLockIds(msg.sender);
console.log("I have", myLocks.length, "locks");getOwnerLockCount
Returns the number of locks owned by an address.
function getOwnerLockCount(address owner) external view returns (uint256)Example:
uint256 count = v2Locker.getOwnerLockCount(msg.sender);getOwnerLockId
Returns a specific lock ID for an owner by index.
function getOwnerLockId(
address owner,
uint256 index
) external view returns (uint256)Parameters:
owner: Address to queryindex: Index in the owner's lock array (0-based)
locks
Returns complete lock details.
function locks(uint256 lockId) external view returns (
address owner,
address lpToken,
uint256 amount,
uint256 unlockTime,
bool permanent,
bool active,
address condition
)Returns:
owner: Current owner of the locklpToken: Address of the locked LP tokenamount: Amount of LP tokens lockedunlockTime: Unlock timestamppermanent: Whether lock is permanentactive: Whether lock is activecondition: Condition contract (if any)
Example:
(
address owner,
address lpToken,
uint256 amount,
uint256 unlockTime,
bool permanent,
bool active,
address condition
) = v2Locker.locks(lockId);
console.log("Owner:", owner);
console.log("Amount:", amount);
console.log("Unlock:", unlockTime);canUnlock
Checks if a lock can be unlocked now.
function canUnlock(uint256 lockId) external view returns (bool)Example:
if (v2Locker.canUnlock(myLockId)) {
// Can unlock now!
v2Locker.unlock(myLockId);
}getTotalLocked
Returns the total amount of a specific LP token locked across all locks.
function getTotalLocked(address lpToken) external view returns (uint256)Example:
uint256 totalLocked = v2Locker.getTotalLocked(lpTokenAddress);
console.log("Total locked:", totalLocked);getOwnerLockedAmount
Returns the total amount of a specific LP token locked by a specific owner.
function getOwnerLockedAmount(
address owner,
address lpToken
) external view returns (uint256)Example:
uint256 myLockedAmount = v2Locker.getOwnerLockedAmount(
msg.sender,
lpTokenAddress
);State Variables
// Configuration
uint256 public creationFee; // Fee to create lock
address public treasury; // Treasury address
// State
uint256 public nextLockId; // Next available lock ID
mapping(address => uint256) public totalLocked; // Total locked per tokenEvents
event Locked(...); // LP tokens locked
event Unlocked(...); // LP tokens unlocked
event LockExtended(...); // Lock duration extended
event LockIncreased(...); // More tokens added to lockComplete Usage Example
Locking Workflow
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract V2LockerExample {
V2Locker public v2Locker;
IERC20 public lpToken;
function lockLPTokens() external payable {
// Step 1: Approve LP tokens
uint256 amount = 1000 * 1e18; // 1000 LP tokens
lpToken.approve(address(v2Locker), amount);
// Step 2: Get creation fee
uint256 creationFee = v2Locker.creationFee();
// Step 3: Lock for 90 days
uint256 unlockTime = block.timestamp + 90 days;
v2Locker.lockLP{value: creationFee}(
address(lpToken),
amount,
unlockTime,
false, // not permanent
address(0) // no condition
);
}
function addMoreToLock(uint256 lockId) external {
// Add 500 more tokens
uint256 additionalAmount = 500 * 1e18;
lpToken.approve(address(v2Locker), additionalAmount);
v2Locker.addToLock(lockId, additionalAmount);
}
function extendMyLock(uint256 lockId) external {
// Extend by 30 days
(
,
,
,
uint256 currentUnlockTime,
,
,
) = v2Locker.locks(lockId);
uint256 newUnlockTime = currentUnlockTime + 30 days;
v2Locker.extendLock(lockId, newUnlockTime);
}
function unlockTokens(uint256 lockId) external {
// Check if can unlock
require(v2Locker.canUnlock(lockId), "Cannot unlock yet");
// Unlock
v2Locker.unlock(lockId);
}
}Integration Examples
Web3.js
const Web3 = require('web3');
const web3 = new Web3('https://testnet-rpc.monad.xyz');
const V2_LOCKER_ABI = [...]; // Import ABI
const v2Locker = new web3.eth.Contract(
V2_LOCKER_ABI,
'0x527713F26D0769E5EaE5A77b530511025fB4FDe7'
);
// Lock LP tokens
async function lockLP(lpTokenAddress, amount, days) {
const unlockTime = Math.floor(Date.now() / 1000) + (days * 86400);
const creationFee = await v2Locker.methods.creationFee().call();
// First approve
const lpToken = new web3.eth.Contract(ERC20_ABI, lpTokenAddress);
await lpToken.methods.approve(v2Locker.options.address, amount).send({
from: userAddress
});
// Then lock
await v2Locker.methods.lockLP(
lpTokenAddress,
amount,
unlockTime,
false,
'0x0000000000000000000000000000000000000000'
).send({
from: userAddress,
value: creationFee
});
}
// Get all locks
async function getMyLocks(userAddress) {
const lockIds = await v2Locker.methods.getOwnerLockIds(userAddress).call();
const locks = await Promise.all(
lockIds.map(id => v2Locker.methods.locks(id).call())
);
return locks;
}
// Add to lock
async function addToLock(lockId, amount) {
const lock = await v2Locker.methods.locks(lockId).call();
// Approve additional tokens
const lpToken = new web3.eth.Contract(ERC20_ABI, lock.lpToken);
await lpToken.methods.approve(v2Locker.options.address, amount).send({
from: userAddress
});
// Add to lock
await v2Locker.methods.addToLock(lockId, amount).send({
from: userAddress
});
}Ethers.js
const { ethers } = require('ethers');
const provider = new ethers.providers.JsonRpcProvider('https://testnet-rpc.monad.xyz');
const wallet = new ethers.Wallet(privateKey, provider);
const v2Locker = new ethers.Contract(
'0x527713F26D0769E5EaE5A77b530511025fB4FDe7',
V2_LOCKER_ABI,
wallet
);
// Lock LP tokens
async function lockLP(lpTokenAddress, amount, days) {
const unlockTime = Math.floor(Date.now() / 1000) + (days * 86400);
const creationFee = await v2Locker.creationFee();
// Approve LP tokens
const lpToken = new ethers.Contract(lpTokenAddress, ERC20_ABI, wallet);
const approveTx = await lpToken.approve(v2Locker.address, amount);
await approveTx.wait();
// Lock tokens
const lockTx = await v2Locker.lockLP(
lpTokenAddress,
amount,
unlockTime,
false,
ethers.constants.AddressZero,
{ value: creationFee }
);
await lockTx.wait();
}
// Unlock tokens
async function unlockTokens(lockId) {
// Check if can unlock
const canUnlock = await v2Locker.canUnlock(lockId);
if (!canUnlock) {
throw new Error('Cannot unlock yet');
}
// Unlock
const tx = await v2Locker.unlock(lockId);
await tx.wait();
}Error Reference
error InvalidLock(); // Lock ID doesn't exist or invalid
error LockNotActive(); // Lock has been unlocked
error LockPermanent(); // Cannot unlock permanent locks
error LockNotUnlocked(); // Unlock conditions not met
error NotLockOwner(); // Caller is not the lock owner
error InsufficientCreationFee(); // Didn't send enough creation fee
error InvalidTreasury(); // Treasury address is zero
error TransferFailed(); // Token transfer failed
error InvalidCondition(); // Condition address is not a contract
error InvalidUnlockTime(); // Unlock time is invalid
error LockAlreadyPermanent(); // Cannot extend permanent lock
error ZeroAmount(); // Amount is zero
error InsufficientBalance(); // Not enough tokensGas Estimates
Approximate gas costs on Monad Testnet:
lockLP()
~180,000
~$0.54
unlock()
~120,000
~$0.36
addToLock()
~100,000
~$0.30
extendLock()
~50,000
~$0.15
Note: Actual costs may vary based on network congestion
Comparison: V2 vs V3
Token Type
ERC-20 LP tokens
ERC-721 NFTs
Fee Collection
No separate claim
Yes - claimFees()
How Fees Accrue
Increases LP token value
Separate from liquidity
When You Get Fees
When you unlock
Anytime via claimFees()
Protocol Fee
None
1% on claimed fees
Add to Lock
Yes
No
Concentrated Liquidity
No
Yes
Position Details
Limited
Full position info
Typical Use
Simple LP locks
Advanced strategies
Gas Costs
Lower
Higher
Related Documentation
Last updated
