Vesting Schedule for Team and Investors Development
Vesting without cliff is bad vesting. A founder getting tokens linearly from day one has no financial incentive to stay after the first few months. Standard Web3 scheme: 1-year cliff (no tokens at all) + 3 years of linear vesting. Investors usually get shorter cycles: 6-month cliff + 18–24 months linear.
Smart Contract Vesting
Industry standard — OpenZeppelin VestingWallet. For team and investors with different parameters, deploy separate instances:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import "@openzeppelin/contracts/finance/VestingWallet.sol";
// Deploy for specific beneficiary
// VestingWallet(beneficiary, startTimestamp, durationSeconds)
// Team: 1-year cliff, 4-year total vesting
// start = TGE + 1 year (cliff), duration = 3 years
address teamMemberVesting = address(new VestingWallet(
teamMemberAddress,
block.timestamp + 365 days, // start after cliff
3 * 365 days // 3 years linear vesting
));
// Replenish with tokens
IERC20(tokenAddress).transfer(teamMemberVesting, allocatedAmount);
VestingWallet — simple and audited contract. Beneficiary calls release(tokenAddress) to claim unlocked tokens. Cannot revoke—if revocability is needed (fired employee), custom implementation is required.
Revocable Vesting for Team
Standard VestingWallet doesn't support revoke. For employees, stopping vesting on termination is essential:
contract RevocableVesting is Ownable2Step {
IERC20 public immutable token;
address public beneficiary;
uint256 public immutable start;
uint256 public immutable cliff;
uint256 public immutable duration;
uint256 public immutable totalAllocation;
uint256 public released;
bool public revoked;
uint256 public revokedAt;
constructor(
address _token,
address _beneficiary,
address _owner,
uint256 _start,
uint256 _cliff,
uint256 _duration,
uint256 _totalAllocation
) Ownable2Step() {
token = IERC20(_token);
beneficiary = _beneficiary;
start = _start;
cliff = _cliff;
duration = _duration;
totalAllocation = _totalAllocation;
_transferOwnership(_owner);
}
function vestedAmount() public view returns (uint256) {
uint256 endTime = revoked ? revokedAt : block.timestamp;
if (endTime < start + cliff) return 0;
if (endTime >= start + cliff + duration) return totalAllocation;
uint256 elapsed = endTime - (start + cliff);
return totalAllocation * elapsed / duration;
}
function releasable() public view returns (uint256) {
return vestedAmount() - released;
}
function release() external {
require(msg.sender == beneficiary, "Not beneficiary");
uint256 amount = releasable();
require(amount > 0, "Nothing to release");
released += amount;
token.safeTransfer(beneficiary, amount);
emit TokensReleased(amount);
}
// Owner (company) can revoke unvested tokens
function revoke() external onlyOwner {
require(!revoked, "Already revoked");
revoked = true;
revokedAt = block.timestamp;
// Already vested—give to beneficiary
uint256 vestedNow = vestedAmount() - released;
if (vestedNow > 0) {
released += vestedNow;
token.safeTransfer(beneficiary, vestedNow);
}
// Unvested—return to treasury
uint256 remaining = token.balanceOf(address(this));
if (remaining > 0) {
token.safeTransfer(owner(), remaining);
}
emit VestingRevoked(revokedAt, vestedNow, remaining);
}
}
Cliff is implemented as a check endTime < start + cliff—before this date, vestedAmount() returns 0. After cliff—linear growth proportional to elapsed time.
Factory for Multi-Contract Deployment
Deploying one contract per recipient manually is inconvenient. Factory:
contract VestingFactory is Ownable2Step {
address public token;
address[] public allVestings;
mapping(address => address) public vestingOf; // beneficiary => vesting contract
event VestingCreated(address indexed beneficiary, address vestingContract, uint256 amount);
struct VestingParams {
address beneficiary;
uint256 amount;
uint256 cliffDays;
uint256 vestingDays;
}
function batchCreate(
VestingParams[] calldata params,
uint256 tgeTimestamp
) external onlyOwner {
for (uint256 i = 0; i < params.length; i++) {
VestingParams memory p = params[i];
require(vestingOf[p.beneficiary] == address(0), "Already has vesting");
RevocableVesting vesting = new RevocableVesting(
token,
p.beneficiary,
owner(),
tgeTimestamp,
p.cliffDays * 1 days,
p.vestingDays * 1 days,
p.amount
);
IERC20(token).safeTransferFrom(msg.sender, address(vesting), p.amount);
allVestings.push(address(vesting));
vestingOf[p.beneficiary] = address(vesting);
emit VestingCreated(p.beneficiary, address(vesting), p.amount);
}
}
}
One batchCreate call—deploy all contracts for team and investors. Token approval to factory before call.
Parameters by Recipient Type
| Type | Cliff | Vesting | Revocable |
|---|---|---|---|
| Founders | 12 months | 36 months after cliff | Yes |
| Early employees | 12 months | 24–36 months after cliff | Yes |
| Seed investors | 6–12 months | 12–24 months after cliff | No |
| Strategic partners | 6 months | 12–18 months | Partial |
| Advisors | 3–6 months | 12–18 months | No |
For investors, the contract is often non-revocable (condition of investment agreement). For employees, revocable on termination.
Tax Considerations
Vesting on-chain is on-chain events that leave a trace. Different jurisdictions have different taxable moment: upon vesting (unlock) or upon sale. This is not technical but legal problem, but smart contract should emit proper events with timestamps for audit.
Development timeline: 3–5 days for standard vesting contracts with factory and tests. Audit optional for small allocations, mandatory for amounts >$500k.







