以下是一个集成"初始化代币发行数量""锁仓机制"和"释放规则"的完整ERC20代币合约示例(基于OpenZeppelin安全库),支持按时间线性释放锁仓代币,适合项目方对团队、投资者代币进行分阶段解锁场景。
核心功能说明
-
初始化发行:部署时设定总供应量,自动分配给部署者(项目方)。
-
锁仓配置:支持对指定地址设置锁仓金额和释放周期(如分12个月线性释放)。
-
释放规则:锁仓代币按"时间比例"逐步解锁,未到释放时间的代币无法转账。
完整合约代码
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract LockableToken is ERC20, Ownable {
// 锁仓信息结构:记录每个地址的锁仓详情
struct LockInfo {
uint256 totalLocked; // 总锁仓数量(含未释放部分)
uint256 releasedAmount; // 已释放数量
uint256 startTime; // 锁仓开始时间(时间戳)
uint256 duration; // 释放周期(秒,如365天=31536000秒)
bool isLocked; // 是否处于锁仓状态
}
// 地址 => 锁仓信息
mapping(address => LockInfo) public lockInfos;
// 事件:记录锁仓操作
event TokenLocked(address indexed user, uint256 amount, uint256 startTime, uint256 duration);
// 事件:记录代币释放
event TokenReleased(address indexed user, uint256 amount);
/**
* @dev 构造函数:初始化代币名称、符号、总供应量
* @param name_ 代币名称
* @param symbol_ 代币符号
* @param totalSupply_ 初始总供应量(单位:枚,自动处理18位小数)
* @param initialOwner 合约初始拥有者(项目方地址)
*/
constructor(
string memory name_,
string memory symbol_,
uint256 totalSupply_,
address initialOwner
) ERC20(name_, symbol_) Ownable(initialOwner) {
// 发行总代币:乘以10^18处理小数位,全部分配给初始拥有者
uint256 totalSupplyWithDecimals = totalSupply_ * (10 **decimals());
_mint(initialOwner, totalSupplyWithDecimals);
}
/**
* @dev 为指定地址设置锁仓(仅合约拥有者可操作)
* @param user 被锁仓地址
* @param amount 锁仓数量(单位:枚,自动处理小数位)
* @param duration 释放周期(秒,如"365天"传入31536000)
*/
function lockTokens(
address user,
uint256 amount,
uint256 duration
) external onlyOwner {
require(user != address(0), "无效地址");
require(amount > 0, "锁仓数量需大于0");
require(duration > 0, "释放周期需大于0");
require(balanceOf(owner()) >= amount * (10** decimals()), "拥有者余额不足");
// 1. 将代币从拥有者转账到被锁仓地址(但处于锁仓状态)
uint256 amountWithDecimals = amount * (10 **decimals());
_transfer(owner(), user, amountWithDecimals);
// 2. 记录锁仓信息
LockInfo storage lockInfo = lockInfos[user];
require(!lockInfo.isLocked, "该地址已处于锁仓状态"); // 简化版:暂不支持重复锁仓
lockInfo.totalLocked = amountWithDecimals;
lockInfo.releasedAmount = 0;
lockInfo.startTime = block.timestamp; // 锁仓开始时间为当前时间
lockInfo.duration = duration;
lockInfo.isLocked = true;
emit TokenLocked(user, amountWithDecimals, lockInfo.startTime, duration);
}
/**
* @dev 计算指定地址当前可释放的代币数量
* @param user 被锁仓地址
* @return 可释放数量(含小数位)
*/
function getReleasableAmount(address user) public view returns (uint256) {
LockInfo storage lockInfo = lockInfos[user];
if (!lockInfo.isLocked) return 0;
uint256 elapsedTime = block.timestamp - lockInfo.startTime;
uint256 totalLockTime = lockInfo.duration;
// 若未到释放周期:可释放0;若已过周期:可释放全部未释放部分
if (elapsedTime >= totalLockTime) {
return lockInfo.totalLocked - lockInfo.releasedAmount;
}
// 按时间比例计算可释放数量(线性释放)
uint256 releasable = (lockInfo.totalLocked * elapsedTime) / totalLockTime - lockInfo.releasedAmount;
return releasable;
}
/**
* @dev 手动触发代币释放(被锁仓地址可自行调用)
*/
function releaseTokens() external {
address user = msg.sender;
LockInfo storage lockInfo = lockInfos[user];
require(lockInfo.isLocked, "无锁仓代币");
uint256 releasable = getReleasableAmount(user);
require(releasable > 0, "暂无可释放代币");
// 更新已释放数量
lockInfo.releasedAmount += releasable;
// 若全部释放,标记锁仓结束
if (lockInfo.releasedAmount == lockInfo.totalLocked) {
lockInfo.isLocked = false;
}
emit TokenReleased(user, releasable);
}
/**
* @dev 重写transfer函数:限制转账金额不得超过"可用余额"