Vesting Contracts Overview
Vesting contracts are smart contracts that control the gradual release of tokens over time according to predefined schedules. TokenOps SDK provides a comprehensive suite of vesting solutions for different use cases, from employee compensation to investor token distributions.
What is Token Vesting?
Token vesting is a mechanism that locks tokens and releases them gradually over time according to a predefined schedule. This ensures long-term commitment from recipients and prevents immediate token dumps that could harm project value.
Key Benefits
- Alignment of Interests: Recipients have incentive to support long-term project success
- Market Stability: Prevents sudden large token releases that could crash prices
- Compliance: Meets regulatory requirements for token distributions
- Flexibility: Customizable schedules for different stakeholder groups
Vesting Contract Types
TokenOps SDK offers four main vesting contract variants to suit different needs:
1. Token Vesting Manager
Standard ERC20 token vesting
// Perfect for: Team tokens, investor distributions, advisor compensation
const manager = new TokenVestingManager(contractAddress, tokenAddress, provider);
Use Cases:
- Employee equity compensation
- Investor token distributions
- Advisor and consultant payments
- Partnership agreements
2. Native Token Vesting Manager
ETH and native token vesting
// Perfect for: ETH distributions, native token rewards
const nativeManager = new NativeTokenVestingManager(contractAddress, provider);
Use Cases:
- ETH-based compensation
- Native token rewards
- Revenue sharing in ETH
- Validator rewards
3. Token Vesting Manager with Votes
Governance-enabled vesting
// Perfect for: DAO governance tokens, voting rights preservation
const votingManager = new TokenVestingManagerVotes(contractAddress, tokenAddress, provider);
Use Cases:
- DAO governance tokens
- Voting rights during vesting
- Community governance participation
- Delegated voting mechanisms
4. Vested Milestone Manager
Achievement-based token releases
// Perfect for: Performance-based compensation, project milestones
const milestoneManager = new VestedMilestoneManager(contractAddress, tokenAddress, provider);
Use Cases:
- Performance-based bonuses
- Project milestone rewards
- Achievement-based unlocks
- Conditional token releases
Vesting Schedule Components
All vesting schedules in TokenOps SDK share common components:
Timeline Components
interface VestingSchedule {
startTimestamp: bigint; // When vesting begins
endTimestamp: bigint; // When vesting completes
cliffReleaseTimestamp: bigint; // When cliff period ends
releaseIntervalSecs: bigint; // Seconds between releases
timelock: bigint; // Additional holding period after vesting
}
Amount Components
interface VestingAmounts {
cliffAmount: bigint; // Tokens released at cliff
linearVestAmount: bigint; // Tokens for linear vesting
initialUnlock: bigint; // Immediate unlock amount
}
Control Components
interface VestingControls {
isRevocable: boolean; // Can vesting be cancelled?
recipient: Address; // Who receives tokens
vestingId: string; // Unique vesting identifier (bytes32)
}
Vesting Schedule Types
1. Linear Vesting
Tokens are released continuously over time.
Example: 1000 tokens over 12 months = ~83.33 tokens/month
2. Cliff Vesting
No tokens released until a specific date, then a large amount unlocks.
Example: 0 tokens for 6 months, then 500 tokens unlock, then 500 tokens over 6 months
3. Step Vesting
Tokens are released in discrete intervals.
Example: 250 tokens every 3 months for 12 months
4. Milestone Vesting
Tokens are released when specific conditions are met.
Example: 333 tokens after each product launch milestone
Factory Pattern Architecture
TokenOps uses a factory pattern for deploying vesting contracts:
Benefits of Factory Pattern
- Standardization: All contracts deployed with consistent patterns
- Upgradeability: New contract versions can be deployed
- Gas Efficiency: Optimized deployment costs
- Security: Audited factory reduces individual contract risks
Funding Types
TokenOps vesting supports two funding models:
Full Funding (Type 0)
const fundingType = BigInt(0); // Full funding required upfront
// Must fund entire vesting amount before creating schedules
// More secure - ensures all tokens are available
// Prevents over-allocation
Partial Funding (Type 1)
const fundingType = BigInt(1); // Partial funding allowed
// Can create vesting schedules before full funding
// Better cash flow management
// Allows iterative funding
Common Use Cases
1. Employee Compensation
// 4-year vesting with 1-year cliff
const employeeVesting = {
startTimestamp: BigInt(Math.floor(Date.now() / 1000)),
endTimestamp: BigInt(Math.floor(Date.now() / 1000) + (4 * 365 * 24 * 60 * 60)),
cliffReleaseTimestamp: BigInt(Math.floor(Date.now() / 1000) + (365 * 24 * 60 * 60)),
cliffAmount: BigInt('25000000000000000000000'), // 25,000 tokens at cliff
linearVestAmount: BigInt('75000000000000000000000'), // 75,000 tokens linear
releaseIntervalSecs: BigInt(30 * 24 * 60 * 60), // Monthly releases
isRevocable: true // Can be revoked if employee leaves
};
2. Investor Distribution
// 2-year linear vesting with 6-month cliff
const investorVesting = {
startTimestamp: BigInt(Math.floor(Date.now() / 1000)),
endTimestamp: BigInt(Math.floor(Date.now() / 1000) + (2 * 365 * 24 * 60 * 60)),
cliffReleaseTimestamp: BigInt(Math.floor(Date.now() / 1000) + (6 * 30 * 24 * 60 * 60)),
cliffAmount: BigInt('500000000000000000000000'), // 500,000 tokens at cliff
linearVestAmount: BigInt('1500000000000000000000000'), // 1,500,000 tokens linear
releaseIntervalSecs: BigInt(7 * 24 * 60 * 60), // Weekly releases
isRevocable: false // Cannot be revoked
};
3. Advisor Compensation
// 18-month vesting with immediate partial unlock
const advisorVesting = {
startTimestamp: BigInt(Math.floor(Date.now() / 1000)),
endTimestamp: BigInt(Math.floor(Date.now() / 1000) + (18 * 30 * 24 * 60 * 60)),
cliffReleaseTimestamp: BigInt(0), // No cliff
cliffAmount: BigInt(0),
linearVestAmount: BigInt('90000000000000000000000'), // 90,000 tokens linear
initialUnlock: BigInt('10000000000000000000000'), // 10,000 tokens immediate
releaseIntervalSecs: BigInt(30 * 24 * 60 * 60), // Monthly releases
isRevocable: true
};
4. Milestone-Based Rewards
// Achievement-based milestone vesting
const milestones = [
{
description: "Launch Mainnet",
amount: BigInt('1000000000000000000000000'), // 1M tokens
completed: false
},
{
description: "Reach 100K Users",
amount: BigInt('2000000000000000000000000'), // 2M tokens
completed: false
},
{
description: "Launch Governance",
amount: BigInt('3000000000000000000000000'), // 3M tokens
completed: false
}
];
Core Operations
Creating Vesting Schedules
// Standard token vesting
const vestingResult = await manager.createVesting(
recipientAddress, // Address receiving tokens
startTimestamp, // When vesting starts
endTimestamp, // When vesting ends
timelock, // Additional lock period
initialUnlock, // Immediate unlock amount
cliffReleaseTimestamp, // When cliff ends
cliffAmount, // Amount released at cliff
releaseIntervalSecs, // Time between releases
linearVestAmount, // Amount for linear vesting
isRevocable, // Can be revoked
{ account: creatorAddress, amount: totalAmount }
);
const vestingId = vestingResult.vestingId; // Returns string (bytes32)
Claiming Tokens
// Claim vested tokens
const claimResult = await manager.claim(
vestingId, // Vesting schedule ID
{ account: recipientAddress, amount: gasFee }
);
Reading Vesting Information
// Get complete vesting information
const vestingInfo = await manager.getVestingInfo(vestingId);
// Get claimable amount
const claimable = await manager.getClaimableAmount(vestingId);
// Get vested amount at specific time
const vested = await manager.getVestedAmount(vestingId, timestamp);
// Get basic vesting data
const vestingData = await manager.vestingById(vestingId);
Event System
All vesting contracts emit comprehensive events for tracking:
// Vesting creation event
interface VestingCreatedEvent {
vestingId: string; // bytes32 ID
recipient: Address; // Token recipient
vesting: any; // Complete vesting data
}
// Token claim event
interface ClaimedEvent {
vestingId: string; // bytes32 ID
recipient: Address; // Who claimed
withdrawalAmount: bigint; // Amount claimed
}
// Vesting transfer event
interface VestingTransferredEvent {
previousOwner: Address; // Previous owner
newOwner: Address; // New owner
vestingId: string; // Vesting ID transferred
}
// Vesting revocation event
interface VestingRevoked {
vestingId: string; // Revoked vesting ID
numTokensWithheld: bigint; // Tokens returned to admin
vesting: any; // Vesting data
}
Administrative Functions
Funding Management
// Fund a vesting schedule (for partial funding type)
const fundResult = await manager.fundVesting(
vestingId, // Vesting to fund
fundingAmount, // Amount to add
{ account: adminAddress, amount: fundingAmount }
);
// Check funding status
const fundingInfo = await manager.getVestingFundingInfo(vestingId);
const isFullyFunded = await manager.isVestingFullyFunded(vestingId);
Vesting Transfers
// Direct transfer of vesting ownership
const transferResult = await manager.directVestingTransfer(
vestingId, // Vesting to transfer
newOwnerAddress, // New recipient
{ account: currentOwner }
);
// Cancel pending transfer
await manager.cancelVestingTransfer(vestingId, { account: currentOwner });
Batch Operations
// Create multiple vestings in one transaction
const batchResult = await manager.createVestingBatch(
recipientsArray, // Array of recipients
startTimestampsArray, // Array of start times
endTimestampsArray, // Array of end times
timelocksArray, // Array of timelocks
initialUnlocksArray, // Array of initial unlocks
cliffTimestampsArray, // Array of cliff times
cliffAmountsArray, // Array of cliff amounts
intervalsArray, // Array of release intervals
linearAmountsArray, // Array of linear amounts
revocableArray, // Array of revocable flags
{ account: creatorAddress, amount: totalFunding }
);
// Batch admin claim for multiple vestings
await manager.batchAdminClaim(
vestingIdsArray, // Array of vesting IDs
{ account: adminAddress, amount: gasFee }
);
Best Practices
1. Design Patterns
- Use factory pattern for standardized deployments
- Choose appropriate funding type for your use case
- Plan vesting schedules carefully with proper cliff periods
- Document all parameters and conditions clearly
2. Security
- Use revocable vesting for employees (protection against departure)
- Use non-revocable for investors (protection against arbitrary revocation)
- Implement proper access controls for admin functions
- Validate all input parameters before contract creation
3. Gas Optimization
- Batch similar operations when possible
- Use appropriate release intervals (not too frequent)
- Consider funding type impact on gas costs
- Monitor contract interaction costs
4. User Experience
- Provide clear vesting progress displays
- Send notifications for claimable tokens
- Explain revocation conditions upfront
- Implement intuitive claiming interfaces
Integration Examples
Vesting Dashboard
interface VestingDashboard {
totalVestings: number;
totalAllocated: bigint;
totalClaimed: bigint;
totalClaimable: bigint;
activeVestings: VestingInfo[];
}
async function getVestingDashboard(
manager: TokenVestingManager,
userAddress: Address
): Promise<VestingDashboard> {
// Get all recipients and filter for user
const allRecipients = await manager.getAllRecipients();
const isRecipient = await manager.isRecipient(userAddress);
if (!isRecipient) {
return {
totalVestings: 0,
totalAllocated: BigInt(0),
totalClaimed: BigInt(0),
totalClaimable: BigInt(0),
activeVestings: []
};
}
// You would need to track vesting IDs for each user
// This could be done through event monitoring
const userVestingIds = await getUserVestingIds(userAddress);
let totalAllocated = BigInt(0);
let totalClaimed = BigInt(0);
let totalClaimable = BigInt(0);
const activeVestings = [];
for (const vestingId of userVestingIds) {
const vestingInfo = await manager.getVestingInfo(vestingId);
const claimable = await manager.getClaimableAmount(vestingId);
totalAllocated += vestingInfo.cliffAmount + vestingInfo.linearVestAmount;
totalClaimed += vestingInfo.claimedAmount;
totalClaimable += claimable;
activeVestings.push({
vestingId,
...vestingInfo,
claimableAmount: claimable
});
}
return {
totalVestings: userVestingIds.length,
totalAllocated,
totalClaimed,
totalClaimable,
activeVestings
};
}
Event Monitoring
// Monitor vesting events for real-time tracking
manager.on('VestingCreated', (event) => {
console.log('New vesting created:', {
vestingId: event.vestingId,
recipient: event.recipient,
vesting: event.vesting
});
});
manager.on('Claimed', (event) => {
console.log('Tokens claimed:', {
vestingId: event.vestingId,
recipient: event.recipient,
amount: event.withdrawalAmount
});
// Send notification to recipient
sendClaimNotification(event.recipient, event.withdrawalAmount);
});
manager.on('VestingTransferred', (event) => {
console.log('Vesting ownership transferred:', {
previousOwner: event.previousOwner,
newOwner: event.newOwner,
vestingId: event.vestingId
});
});