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 payable

Parameters:

  • lpToken: Address of the LP token contract to lock

  • amount: Amount of LP tokens to lock (in token's decimals)

  • unlockTime: Unix timestamp when tokens can be unlocked

  • permanent: Whether the lock is permanent (cannot be unlocked)

  • condition: Optional condition contract address (use address(0) for time-based only)

Requirements:

  • Must send creationFee as msg.value

  • Must have approved LP tokens to this contract

  • Amount must be > 0

  • If condition is 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) external

Parameters:

  • 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
) external

Parameters:

  • lockId: The ID of the lock to add tokens to

  • amount: 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
) external

Parameters:

  • lockId: The ID of the lock to extend

  • newUnlockTime: 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 query

  • index: 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 lock

  • lpToken: Address of the locked LP token

  • amount: Amount of LP tokens locked

  • unlockTime: Unlock timestamp

  • permanent: Whether lock is permanent

  • active: Whether lock is active

  • condition: 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 token

Events

event Locked(...);                         // LP tokens locked
event Unlocked(...);                       // LP tokens unlocked
event LockExtended(...);                   // Lock duration extended
event LockIncreased(...);                  // More tokens added to lock

Complete 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 tokens

Gas Estimates

Approximate gas costs on Monad Testnet:

Function
Gas Cost
USD (at $3000 ETH, 0.001 gwei)

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

Feature
V2 Locker
V3 Locker

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


Last updated