Quickstart
Get up and running with RealSafe in minutes. This guide covers basic setup, deployment, and usage for both V2 and V3 lockers.
Prerequisites
Required Tools
# Install Foundry
curl -L https://foundry.paradigm.xyz | bash
foundryup
# Verify installation
forge --version
cast --versionEnvironment Setup
Create a .env file in the contracts/ directory:
# Private key (without 0x prefix)
PRIVATE_KEY=your_private_key_here
# RPC URLs
MONAD_TESTNET_RPC=https://testnet-rpc.monad.xyz
# Block explorers (optional)
MONAD_EXPLORER_API_KEY=your_api_keyInstallation
# Clone the repository
git clone https://github.com/realsafe/locker.git
cd locker/contracts
# Install dependencies
forge install
# Build contracts
forge buildDeployment
Deploy on Monad Testnet
# Deploy contracts
forge script script/DeployMonad.s.sol \
--rpc-url monad_testnet \
--broadcast
# Output will show deployment detailsUsage Examples
Example 1: Lock a V3 Position
Step 1: Create a Pool and Position
# Deploy test tokens
forge script script/DeployTwoTokens.s.sol \
--rpc-url monad_testnet \
--broadcast
# Create pool and mint position
forge script script/CreatePool.s.sol \
--rpc-url monad_testnet \
--broadcastStep 2: Lock the Position
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "forge-std/Script.sol";
import {V3Locker} from "../src/V3Locker.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
contract LockMyPosition is Script {
address constant V3_LOCKER = 0x0b4619Ed28429a392C79aed87E6572F34ab6199e;
address constant POSITION_MANAGER = 0x46A15B0b27311cedF172AB29E4f4766fbE7F4364;
function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
// Your NFT token ID
uint256 myTokenId = 83;
// Lock for 30 days
uint256 unlockTime = block.timestamp + 30 days;
vm.startBroadcast(deployerPrivateKey);
// Approve NFT
IERC721(POSITION_MANAGER).approve(V3_LOCKER, myTokenId);
// Lock position
uint256 creationFee = V3Locker(V3_LOCKER).creationFee();
V3Locker(V3_LOCKER).lockPosition{value: creationFee}(
myTokenId,
unlockTime,
msg.sender, // collect address
address(0) // no condition
);
vm.stopBroadcast();
console.log("Position locked successfully!");
}
}Run the script:
forge script script/LockMyPosition.s.sol \
--rpc-url monad_testnet \
--broadcastExample 2: Lock V2 LP Tokens
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "forge-std/Script.sol";
import {V2Locker} from "../src/V2Locker.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract LockLPTokens is Script {
address constant V2_LOCKER = 0x527713F26D0769E5EaE5A77b530511025fB4FDe7;
function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
// Your LP token details
address lpToken = 0x324cc0b7E34882A41aFdf1a623529E6ce6BA9623;
uint256 amount = 1000 * 1e18; // 1000 LP tokens
// Lock for 90 days
uint256 unlockTime = block.timestamp + 90 days;
vm.startBroadcast(deployerPrivateKey);
// Approve LP tokens
IERC20(lpToken).approve(V2_LOCKER, amount);
// Lock tokens
uint256 creationFee = V2Locker(V2_LOCKER).creationFee();
V2Locker(V2_LOCKER).lockLP{value: creationFee}(
lpToken,
amount,
unlockTime,
false, // not permanent
address(0) // no condition
);
vm.stopBroadcast();
console.log("LP tokens locked successfully!");
}
}Example 3: Claim Fees from Locked V3 Position
Note: This only applies to V3 positions. V2 LP tokens don't have a separate fee claiming function because trading fees automatically accrue to the LP token's value. You receive all accrued fees when you unlock your V2 LP tokens.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "forge-std/Script.sol";
import {V3Locker} from "../src/V3Locker.sol";
contract ClaimFeesFromLock is Script {
address constant V3_LOCKER = 0x0b4619Ed28429a392C79aed87E6572F34ab6199e;
function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
// Your lock ID
uint256 myLockId = 0;
vm.startBroadcast(deployerPrivateKey);
// Claim all available fees (V3 only)
V3Locker(V3_LOCKER).claimFees(
myLockId,
type(uint128).max, // max token0
type(uint128).max // max token1
);
vm.stopBroadcast();
console.log("Fees claimed successfully!");
}
}Using Cast (Command Line)
Get Your Lock IDs
# Get all your lock IDs
cast call 0x0b4619Ed28429a392C79aed87E6572F34ab6199e \
"getOwnerLockIds(address)" \
YOUR_ADDRESS \
--rpc-url monad_testnetCheck Lock Details
# Get specific lock details
cast call 0x0b4619Ed28429a392C79aed87E6572F34ab6199e \
"getLock(uint256)" \
0 \
--rpc-url monad_testnetCheck if You Can Unlock
# Check unlock status
cast call 0x0b4619Ed28429a392C79aed87E6572F34ab6199e \
"canUnlock(uint256)" \
0 \
--rpc-url monad_testnetUnlock Your Position
# Unlock when ready
cast send 0x0b4619Ed28429a392C79aed87E6572F34ab6199e \
"withdraw(uint256,address)" \
0 \
YOUR_ADDRESS \
--private-key $PRIVATE_KEY \
--rpc-url monad_testnetTesting
Run All Tests
# Run full test suite
forge test
# Run with gas reporting
forge test --gas-report
# Run specific test
forge test --match-test testLockPosition -vvvTest V3 Locker
forge test --match-contract V3LockerTest -vvvTest V2 Locker
forge test --match-contract V2LockerTest -vvvVerification
Verify on Monad Explorer
# Verify V3 Locker
forge verify-contract \
0x0b4619Ed28429a392C79aed87E6572F34ab6199e \
src/V3Locker.sol:V3Locker \
--chain-id 84532 \
--constructor-args $(cast abi-encode "constructor(address,address,address,uint256,uint256,uint256)" \
0x46A15B0b27311cedF172AB29E4f4766fbE7F4364 \
0x735c60c7FA167a8F69Ae631CD7d020942d9923B6 \
0x735c60c7FA167a8F69Ae631CD7d020942d9923B6 \
100000000000000 \
100 \
100)Common Issues & Solutions
Issue: "Insufficient Creation Fee"
Solution: Check the current creation fee:
cast call V3_LOCKER_ADDRESS "creationFee()" --rpc-url base_sepoliaSend the correct amount with --value flag.
Issue: "NFT Transfer Failed"
Solution: Approve the NFT first:
cast send POSITION_MANAGER \
"approve(address,uint256)" \
V3_LOCKER_ADDRESS \
TOKEN_ID \
--private-key $PRIVATE_KEY \
--rpc-url monad_testnetIssue: "Lock Not Unlocked"
Solution: Check the unlock time:
# Get lock details
cast call V3_LOCKER_ADDRESS "getLock(uint256)" LOCK_ID --rpc-url base_sepolia
# Compare unlockDate with current timestamp
date +%sNext Steps
Read the V3 Interface Documentation
Read the V2 Interface Documentation
Review Security Best Practices
Check Deployment Addresses
Explore Advanced Features
Support
X: @RealSafeLP
Documentation: Read the docs
Last updated
