Extending Locks
Comprehensive guide to extending lock durations, adding liquidity, and managing long-term locked positions.
Overview
RealSafe allows lock owners to extend their lock duration anytime, providing flexibility for long-term liquidity commitment strategies. This guide covers all aspects of lock extension and management.
Table of Contents
Basic Lock Extension
Extending V3 Locks
V3 locks can have their unlock date extended anytime before unlocking.
/**
* @dev Extends a V3 lock duration
*/
function extendLock(
uint256 lockId,
uint256 newUnlockDate
) externalRequirements:
Must be lock owner
Lock must be active
newUnlockDate> current unlock datenewUnlockDate<= current time + 10 years
Example:
// Get current lock details
Lock memory lock = v3Locker.getLock(myLockId);
// Extend by 90 days
uint256 newUnlockDate = lock.unlockDate + 90 days;
// Execute extension
v3Locker.extendLock(myLockId, newUnlockDate);Extending V2 Locks
V2 locks work the same way as V3 locks for extension.
// Get current unlock time
(, , , uint256 currentUnlockTime, , , ) = v2Locker.locks(myLockId);
// Extend by 6 months
uint256 newUnlockDate = currentUnlockTime + 180 days;
// Execute extension
v2Locker.extendLock(myLockId, newUnlockDate);Events
Both V2 and V3 emit similar events:
event LockExtended(
address indexed owner,
uint256 indexed lockId,
uint256 oldUnlockTime,
uint256 newUnlockTime
);Adding to V2 Locks
V2 Locker allows adding more LP tokens to existing locks without creating a new lock.
addToLock Function
function addToLock(
uint256 lockId,
uint256 amount
) externalRequirements:
Must be lock owner
Lock must be active
Amount must be > 0
Must have approved additional tokens
Example:
// Original lock: 1000 LP tokens
// Want to add: 500 more LP tokens
// 1. Approve additional tokens
IERC20(lpToken).approve(v2LockerAddress, 500 * 1e18);
// 2. Add to existing lock
v2Locker.addToLock(myLockId, 500 * 1e18);
// Now total: 1500 LP tokens lockedWhy Add Instead of Creating New Lock?
Gas Savings: Cheaper than creating new lock
Simpler Management: One lock ID instead of multiple
Better UX: Easier to track and unlock
Same Unlock Date: All tokens unlock together
Events
event LockIncreased(
address indexed owner,
uint256 indexed lockId,
uint256 addedAmount,
uint256 newTotalAmount
);Advanced Strategies
Strategy 1: Progressive Extension
Gradually extend lock duration over time to maintain commitment.
contract ProgressiveExtension {
V3Locker public v3Locker;
struct ExtensionSchedule {
uint256 lockId;
uint256[] extensionDates;
uint256[] extensionDurations;
uint256 nextIndex;
}
mapping(address => ExtensionSchedule) public schedules;
/**
* @dev Create an extension schedule
* Example: Extend by 30 days every 3 months
*/
function createSchedule(
uint256 lockId,
uint256[] calldata intervals,
uint256[] calldata durations
) external {
require(intervals.length == durations.length, "Length mismatch");
schedules[msg.sender] = ExtensionSchedule({
lockId: lockId,
extensionDates: intervals,
extensionDurations: durations,
nextIndex: 0
});
}
/**
* @dev Execute next scheduled extension
*/
function executeNextExtension() external {
ExtensionSchedule storage schedule = schedules[msg.sender];
require(schedule.nextIndex < schedule.extensionDates.length, "No more extensions");
uint256 extensionDate = schedule.extensionDates[schedule.nextIndex];
require(block.timestamp >= extensionDate, "Too early");
Lock memory lock = v3Locker.getLock(schedule.lockId);
uint256 duration = schedule.extensionDurations[schedule.nextIndex];
uint256 newUnlockDate = lock.unlockDate + duration;
v3Locker.extendLock(schedule.lockId, newUnlockDate);
schedule.nextIndex++;
}
}Usage:
// Extend by 30 days every 90 days, 4 times
uint256[] memory intervals = new uint256[](4);
intervals[0] = block.timestamp + 90 days;
intervals[1] = block.timestamp + 180 days;
intervals[2] = block.timestamp + 270 days;
intervals[3] = block.timestamp + 360 days;
uint256[] memory durations = new uint256[](4);
durations[0] = 30 days;
durations[1] = 30 days;
durations[2] = 30 days;
durations[3] = 30 days;
progressiveExtension.createSchedule(myLockId, intervals, durations);Strategy 2: Conditional Extension
Automatically extend based on conditions.
contract ConditionalExtension {
V3Locker public v3Locker;
struct ConditionalRule {
uint256 lockId;
address priceOracle;
int256 priceThreshold;
uint256 extensionDuration;
}
mapping(address => ConditionalRule) public rules;
/**
* @dev Set rule: If ETH > $4000, extend by 90 days
*/
function setRule(
uint256 lockId,
address priceOracle,
int256 priceThreshold,
uint256 extensionDuration
) external {
rules[msg.sender] = ConditionalRule({
lockId: lockId,
priceOracle: priceOracle,
priceThreshold: priceThreshold,
extensionDuration: extensionDuration
});
}
/**
* @dev Check and extend if condition met
*/
function checkAndExtend() external {
ConditionalRule memory rule = rules[msg.sender];
(, int256 price, , , ) = AggregatorV3Interface(rule.priceOracle)
.latestRoundData();
if (price >= rule.priceThreshold) {
Lock memory lock = v3Locker.getLock(rule.lockId);
uint256 newUnlockDate = lock.unlockDate + rule.extensionDuration;
v3Locker.extendLock(rule.lockId, newUnlockDate);
}
}
}Strategy 3: DAO-Controlled Extension
Let DAO vote on lock extensions.
contract DAOExtension {
V3Locker public v3Locker;
struct ExtensionProposal {
uint256 lockId;
uint256 newUnlockDate;
uint256 votesFor;
uint256 votesAgainst;
bool executed;
mapping(address => bool) hasVoted;
}
mapping(uint256 => ExtensionProposal) public proposals;
uint256 public nextProposalId;
uint256 public constant QUORUM = 100; // 100 votes needed
/**
* @dev Create extension proposal
*/
function propose(uint256 lockId, uint256 newUnlockDate) external returns (uint256) {
uint256 proposalId = nextProposalId++;
ExtensionProposal storage proposal = proposals[proposalId];
proposal.lockId = lockId;
proposal.newUnlockDate = newUnlockDate;
return proposalId;
}
/**
* @dev Vote on proposal
*/
function vote(uint256 proposalId, bool support) external {
ExtensionProposal storage proposal = proposals[proposalId];
require(!proposal.executed, "Already executed");
require(!proposal.hasVoted[msg.sender], "Already voted");
if (support) {
proposal.votesFor++;
} else {
proposal.votesAgainst++;
}
proposal.hasVoted[msg.sender] = true;
}
/**
* @dev Execute passed proposal
*/
function execute(uint256 proposalId) external {
ExtensionProposal storage proposal = proposals[proposalId];
require(!proposal.executed, "Already executed");
uint256 totalVotes = proposal.votesFor + proposal.votesAgainst;
require(totalVotes >= QUORUM, "Quorum not reached");
require(proposal.votesFor > proposal.votesAgainst, "Proposal defeated");
v3Locker.extendLock(proposal.lockId, proposal.newUnlockDate);
proposal.executed = true;
}
}Strategy 4: Staggered Unlocks
Split liquidity into multiple locks with different unlock dates.
contract StaggeredLocks {
V2Locker public v2Locker;
/**
* @dev Create multiple locks with staggered unlock dates
*/
function createStaggeredLocks(
address lpToken,
uint256 totalAmount,
uint256 numberOfLocks,
uint256 intervalDays
) external payable {
uint256 amountPerLock = totalAmount / numberOfLocks;
uint256 creationFee = v2Locker.creationFee();
require(msg.value >= creationFee * numberOfLocks, "Insufficient fee");
IERC20(lpToken).approve(address(v2Locker), totalAmount);
for (uint i = 0; i < numberOfLocks; i++) {
uint256 unlockTime = block.timestamp + (intervalDays * (i + 1) * 1 days);
v2Locker.lockLP{value: creationFee}(
lpToken,
amountPerLock,
unlockTime,
false,
address(0)
);
}
}
}Example:
// Lock 10,000 LP tokens
// Split into 4 locks
// Unlock every 90 days
staggeredLocks.createStaggeredLocks{value: 0.0004 ether}(
lpTokenAddress,
10000 * 1e18, // Total amount
4, // Number of locks
90 // Days between unlocks
);
// Results in:
// Lock 1: 2,500 tokens, unlocks in 90 days
// Lock 2: 2,500 tokens, unlocks in 180 days
// Lock 3: 2,500 tokens, unlocks in 270 days
// Lock 4: 2,500 tokens, unlocks in 360 daysUse Cases
1. Long-Term Project Commitment
Scenario: Project wants to show long-term commitment but maintain flexibility.
Solution:
// Initial lock: 1 year
lockPosition(tokenId, block.timestamp + 365 days, ...);
// After 6 months, extend by another year
extendLock(lockId, block.timestamp + 365 days + 180 days);
// Keep extending to show continued commitment2. Yield Farming Strategy
Scenario: Want to keep farming rewards while maintaining locked liquidity.
Solution:
// Lock V3 position for 90 days
uint256 lockId = v3Locker.lockPosition{value: fee}(...);
// Collect trading fees monthly while locked
for (uint i = 0; i < 3; i++) {
// Wait 30 days
vm.warp(block.timestamp + 30 days);
// Claim fees
v3Locker.claimFees(lockId, type(uint128).max, type(uint128).max);
}
// Decide to extend based on yields
if (yieldsAreGood) {
extendLock(lockId, block.timestamp + 90 days);
}3. Vesting Schedule
Scenario: Team wants progressive liquidity unlocking.
Solution:
// Create 4 locks for team allocation
// 25% unlock every 6 months
uint256 teamAllocation = 1000000 * 1e18;
for (uint i = 0; i < 4; i++) {
uint256 amount = teamAllocation / 4;
uint256 unlockTime = block.timestamp + ((i + 1) * 180 days);
v2Locker.lockLP{value: fee}(lpToken, amount, unlockTime, false, address(0));
}4. Building Trust with Community
Scenario: New project wants to build trust by demonstrating long-term liquidity commitment.
Timeline:
Launch: Lock 100% of initial liquidity for 30 days
After 2 weeks: Extend to 90 days (shows commitment)
After 1 month: Extend to 6 months (building confidence)
After 3 months: Extend to 1 year (long-term commitment proven)
// Launch
uint256 lockId = v3Locker.lockPosition{value: fee}(
tokenId,
block.timestamp + 30 days,
treasury,
address(0)
);
// Week 2: Extend to 90 days
v3Locker.extendLock(lockId, block.timestamp + 90 days);
// Month 1: Extend to 6 months
v3Locker.extendLock(lockId, block.timestamp + 180 days);
// Month 3: Extend to 1 year
v3Locker.extendLock(lockId, block.timestamp + 365 days);Best Practices
1. Plan Extensions in Advance
// Document your extension strategy
struct ExtensionPlan {
uint256 lockId;
uint256[] milestones; // When to extend
uint256[] durations; // How much to extend
string reasoning; // Why this schedule
}
// Review quarterly
function reviewExtensionPlan() external onlyOwner {
// Check market conditions
// Check project performance
// Decide on next extension
}2. Communicate Extensions to Community
event LockExtensionPlanned(
uint256 indexed lockId,
uint256 currentUnlockTime,
uint256 newUnlockTime,
string reason
);
function announceExtension(
uint256 lockId,
uint256 newUnlockTime,
string calldata reason
) external {
emit LockExtensionPlanned(lockId, lock.unlockDate, newUnlockTime, reason);
// Then execute
v3Locker.extendLock(lockId, newUnlockTime);
}3. Monitor Gas Costs
// Batch multiple operations to save gas
function batchExtend(uint256[] calldata lockIds, uint256[] calldata newDates) external {
require(lockIds.length == newDates.length, "Length mismatch");
for (uint i = 0; i < lockIds.length; i++) {
v3Locker.extendLock(lockIds[i], newDates[i]);
}
}4. Set Maximum Extension Limits
// For governance/safety
uint256 public constant MAX_EXTENSION = 365 days;
function safeExtend(uint256 lockId) external {
Lock memory lock = v3Locker.getLock(lockId);
uint256 newUnlockDate = lock.unlockDate + MAX_EXTENSION;
require(newUnlockDate <= block.timestamp + 10 years, "Too far");
v3Locker.extendLock(lockId, newUnlockDate);
}Gas Optimization
Estimated Gas Costs
extendLock()
~50,000 gas
~50,000 gas
addToLock()
~100,000 gas
N/A
Optimization Tips
Batch Operations: Extend multiple locks in one transaction
Optimal Timing: Extend during low gas periods
Combine Operations: Add to lock + extend together (V2)
// Optimized: Add and extend in one transaction
function addAndExtend(
uint256 lockId,
uint256 additionalAmount,
uint256 newUnlockDate
) external {
// Add tokens
IERC20(lpToken).approve(address(v2Locker), additionalAmount);
v2Locker.addToLock(lockId, additionalAmount);
// Extend lock
v2Locker.extendLock(lockId, newUnlockDate);
}Monitoring Extensions
Track Extension History
contract ExtensionTracker {
struct ExtensionRecord {
uint256 timestamp;
uint256 oldUnlockDate;
uint256 newUnlockDate;
address extender;
}
mapping(uint256 => ExtensionRecord[]) public history;
function recordExtension(
uint256 lockId,
uint256 oldDate,
uint256 newDate
) external {
history[lockId].push(ExtensionRecord({
timestamp: block.timestamp,
oldUnlockDate: oldDate,
newUnlockDate: newDate,
extender: msg.sender
}));
}
function getExtensionHistory(uint256 lockId)
external
view
returns (ExtensionRecord[] memory)
{
return history[lockId];
}
}Related Documentation
Last updated
