TokenVesting
Linear token vesting with a configurable cliff and total vesting duration.
TokenVesting.sol
// SPDX-License-Identifier: MITpragma solidity ^0.8.20; interface IERC20 { function transfer(address to, uint256 amount) external returns (bool); function balanceOf(address account) external view returns (uint256);} contract TokenVesting { IERC20 public immutable token; address public immutable beneficiary; uint256 public immutable start; uint256 public immutable cliff; uint256 public immutable duration; uint256 public released; event Released(uint256 amount); constructor(address token_, address beneficiary_, uint256 cliffDays_, uint256 vestingDays_) { require(beneficiary_ != address(0), "ZERO_BENEFICIARY"); require(vestingDays_ > 0, "ZERO_DURATION"); token = IERC20(token_); beneficiary = beneficiary_; start = block.timestamp; cliff = block.timestamp + cliffDays_ * 1 days; duration = vestingDays_ * 1 days; } function vestedAmount() public view returns (uint256) { uint256 totalBalance = token.balanceOf(address(this)) + released; if (block.timestamp < cliff) return 0; if (block.timestamp >= start + duration) return totalBalance; return (totalBalance * (block.timestamp - start)) / duration; } function releasable() public view returns (uint256) { return vestedAmount() - released; } function release() external { uint256 amount = releasable(); require(amount > 0, "NOTHING_VESTED"); released += amount; require(token.transfer(beneficiary, amount), "TRANSFER_FAILED"); emit Released(amount); }}