Skip to content

Token Liquidity Mining

Liquidity mining is an important incentive mechanism in the DeFi ecosystem, used to attract users to provide liquidity. This tutorial will explain how to implement a complete liquidity mining system.

Features

  • LP token staking mining
  • Multi-pool support
  • Dynamic reward distribution
  • Reinvestment mechanism
  • Emergency withdrawal function

Contract Implementation

solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/Pausable.sol";

contract LiquidityMining is Ownable, ReentrancyGuard, Pausable {
    // Pool information
    struct Pool {
        IERC20 lpToken;        // LP token address
        IERC20 rewardToken;    // Reward token address
        uint256 lastRewardBlock;// Last block where rewards were updated
        uint256 accRewardPerShare; // Accumulated reward per share
        uint256 allocPoint;     // Allocation weight
        uint256 totalStaked;    // Total staked amount
    }

    // User information in pool
    struct UserInfo {
        uint256 amount;         // Staked amount
        uint256 rewardDebt;     // Reward debt
        uint256 pendingRewards; // Pending rewards
    }

    // Pool list
    Pool[] public pools;
    // User information mapping poolId => user => info
    mapping(uint256 => mapping(address => UserInfo)) public userInfo;
    
    // Reward per block
    uint256 public rewardPerBlock;
    // Total allocation points
    uint256 public totalAllocPoint;
    // Start mining block
    uint256 public startBlock;
    // End mining block
    uint256 public endBlock;

    // Events
    event Deposit(address indexed user, uint256 indexed pid, uint256 amount);
    event Withdraw(address indexed user, uint256 indexed pid, uint256 amount);
    event EmergencyWithdraw(address indexed user, uint256 indexed pid, uint256 amount);
    event RewardPaid(address indexed user, uint256 indexed pid, uint256 amount);
    event PoolAdded(uint256 indexed pid, address lpToken, address rewardToken, uint256 allocPoint);
    event PoolUpdated(uint256 indexed pid, uint256 allocPoint);

    constructor(
        uint256 _rewardPerBlock,
        uint256 _startBlock,
        uint256 _endBlock
    ) {
        rewardPerBlock = _rewardPerBlock;
        startBlock = _startBlock;
        endBlock = _endBlock;
    }

    // Add new mining pool
    function addPool(
        IERC20 _lpToken,
        IERC20 _rewardToken,
        uint256 _allocPoint,
        bool _withUpdate
    ) external onlyOwner {
        if (_withUpdate) {
            massUpdatePools();
        }
        uint256 lastRewardBlock = block.number > startBlock ? block.number : startBlock;
        totalAllocPoint += _allocPoint;
        pools.push(Pool({
            lpToken: _lpToken,
            rewardToken: _rewardToken,
            lastRewardBlock: lastRewardBlock,
            accRewardPerShare: 0,
            allocPoint: _allocPoint,
            totalStaked: 0
        }));
        emit PoolAdded(pools.length - 1, address(_lpToken), address(_rewardToken), _allocPoint);
    }

    // Update pool allocation points
    function setPoolAllocPoint(
        uint256 _pid,
        uint256 _allocPoint,
        bool _withUpdate
    ) external onlyOwner {
        if (_withUpdate) {
            massUpdatePools();
        }
        totalAllocPoint = totalAllocPoint - pools[_pid].allocPoint + _allocPoint;
        pools[_pid].allocPoint = _allocPoint;
        emit PoolUpdated(_pid, _allocPoint);
    }

    // Update all pools
    function massUpdatePools() public {
        uint256 length = pools.length;
        for (uint256 pid = 0; pid < length; ++pid) {
            updatePool(pid);
        }
    }

    // Update single pool
    function updatePool(uint256 _pid) public {
        Pool storage pool = pools[_pid];
        if (block.number <= pool.lastRewardBlock) {
            return;
        }
        if (pool.totalStaked == 0) {
            pool.lastRewardBlock = block.number;
            return;
        }
        uint256 multiplier = getMultiplier(pool.lastRewardBlock, block.number);
        uint256 reward = multiplier * rewardPerBlock * pool.allocPoint / totalAllocPoint;
        pool.accRewardPerShare += reward * 1e12 / pool.totalStaked;
        pool.lastRewardBlock = block.number;
    }

    // Stake LP tokens
    function deposit(uint256 _pid, uint256 _amount) external nonReentrant whenNotPaused {
        Pool storage pool = pools[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        updatePool(_pid);
        if (user.amount > 0) {
            uint256 pending = user.amount * pool.accRewardPerShare / 1e12 - user.rewardDebt;
            if (pending > 0) {
                user.pendingRewards += pending;
            }
        }
        if (_amount > 0) {
            pool.lpToken.transferFrom(msg.sender, address(this), _amount);
            user.amount += _amount;
            pool.totalStaked += _amount;
        }
        user.rewardDebt = user.amount * pool.accRewardPerShare / 1e12;
        emit Deposit(msg.sender, _pid, _amount);
    }

    // Withdraw LP tokens
    function withdraw(uint256 _pid, uint256 _amount) external nonReentrant {
        Pool storage pool = pools[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        require(user.amount >= _amount, "withdraw: not enough");
        updatePool(_pid);
        uint256 pending = user.amount * pool.accRewardPerShare / 1e12 - user.rewardDebt;
        if (pending > 0) {
            user.pendingRewards += pending;
        }
        if (_amount > 0) {
            user.amount -= _amount;
            pool.totalStaked -= _amount;
            pool.lpToken.transfer(msg.sender, _amount);
        }
        user.rewardDebt = user.amount * pool.accRewardPerShare / 1e12;
        emit Withdraw(msg.sender, _pid, _amount);
    }

    // Claim rewards
    function harvest(uint256 _pid) external nonReentrant {
        Pool storage pool = pools[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        updatePool(_pid);
        uint256 pending = user.amount * pool.accRewardPerShare / 1e12 - user.rewardDebt;
        if (pending > 0 || user.pendingRewards > 0) {
            uint256 totalReward = pending + user.pendingRewards;
            user.pendingRewards = 0;
            pool.rewardToken.transfer(msg.sender, totalReward);
            emit RewardPaid(msg.sender, _pid, totalReward);
        }
        user.rewardDebt = user.amount * pool.accRewardPerShare / 1e12;
    }

    // Emergency withdrawal
    function emergencyWithdraw(uint256 _pid) external nonReentrant {
        Pool storage pool = pools[_pid];
        UserInfo storage user = userInfo[_pid][msg.sender];
        uint256 amount = user.amount;
        user.amount = 0;
        user.rewardDebt = 0;
        user.pendingRewards = 0;
        pool.totalStaked -= amount;
        pool.lpToken.transfer(msg.sender, amount);
        emit EmergencyWithdraw(msg.sender, _pid, amount);
    }

    // Calculate block reward multiplier
    function getMultiplier(uint256 _from, uint256 _to) public view returns (uint256) {
        if (_to <= endBlock) {
            return _to - _from;
        } else if (_from >= endBlock) {
            return 0;
        } else {
            return endBlock - _from;
        }
    }

    // View pending rewards
    function pendingReward(uint256 _pid, address _user) external view returns (uint256) {
        Pool storage pool = pools[_pid];
        UserInfo storage user = userInfo[_pid][_user];
        uint256 accRewardPerShare = pool.accRewardPerShare;
        if (block.number > pool.lastRewardBlock && pool.totalStaked != 0) {
            uint256 multiplier = getMultiplier(pool.lastRewardBlock, block.number);
            uint256 reward = multiplier * rewardPerBlock * pool.allocPoint / totalAllocPoint;
            accRewardPerShare += reward * 1e12 / pool.totalStaked;
        }
        return user.amount * accRewardPerShare / 1e12 - user.rewardDebt + user.pendingRewards;
    }

    // Pause/Unpause (admin function)
    function setPaused(bool _paused) external onlyOwner {
        if (_paused) {
            _pause();
        } else {
            _unpause();
        }
    }
}

Key Concepts

Pool Management

The pool system supports:

  • Multiple pools running in parallel
  • Dynamic weight allocation
  • Independent reward tokens
  • Flexible parameter adjustment

Reward Calculation

The reward mechanism includes:

  • Block reward distribution
  • Weight ratio calculation
  • Accumulated reward update
  • User share calculation

Staking Operations

Staking functions support:

  • LP token deposit and withdrawal
  • Real-time reward calculation
  • Reinvestment operations
  • Emergency withdrawal

Security Considerations

  1. Fund Security

    • Balance check
    • Transfer verification
    • Overflow protection
  2. Permission Control

    • Admin function restrictions
    • Pause mechanism
    • Parameter validation
  3. Reentrancy Protection

    • Use ReentrancyGuard
    • State update order
    • External call protection
  4. Exception Handling

    • Emergency withdrawal
    • State recovery
    • Error handling

Best Practices

  1. Pool Configuration

    • Reasonable reward rate
    • Appropriate weight allocation
    • Regular parameter adjustments
  2. Revenue Management

    • Reward token reserve
    • Inflation control
    • Sustainable planning
  3. User Experience

    • Simple operation process
    • Clear revenue display
    • Timely state updates
  4. System Maintenance

    • Regular state check
    • Monitor abnormal situations
    • Address issues promptly

Extended Features

  1. Reward multiplier
  2. Lock period setting
  3. Automatic reinvestment
  4. Recommendation rewards
  5. Governance rights

Summary

Liquidity mining systems are important components of DeFi projects. Through this tutorial, you can:

  • Implement a complete mining mechanism
  • Establish a secure reward system
  • Optimize user participation experience
  • Ensure system sustainability

Frequently Asked Questions (FAQ)

Basic Concepts

Q: What is liquidity mining?

A: Liquidity mining is a DeFi incentive mechanism, characterized by:

  • Providing liquidity for rewards
  • Multiple pools running in parallel
  • Dynamic reward distribution
  • Automatic reinvestment mechanism
  • Flexible withdrawal mechanism

Q: What types of liquidity mining are there?

A: The main types include:

  • Single-token mining
  • Trading pair mining
  • Index fund mining
  • Stablecoin mining
  • Synthetic asset mining

Q: How to participate in liquidity mining?

A: The steps include:

  • Preparing LP tokens
  • Selecting mining pools
  • Staking LP tokens
  • Waiting for revenue generation
  • Claiming rewards periodically

Q: How to manage mining revenue?

A: Management methods include:

  • Monitoring yield
  • Calculating investment returns
  • Choosing reinvestment timing
  • Adjusting staking strategies
  • Optimizing pool selection

Q: What are the risks of liquidity mining?

A: The main risks include:

  • Impermanent loss risk
  • Smart contract risk
  • Token price volatility
  • Yield rate changes
  • Liquidity pool squeezes

Q: How to reduce mining risks?

A: Prevention measures include:

  • Diversifying investment pools
  • Regularly checking yields
  • Setting stop-loss strategies
  • Keeping an eye on project progress
  • Adjusting positions in a timely manner

Released under the MIT License by Vogeb.