ERC-5725: Transferable Vesting NFT
ERC-5725 represents a vesting schedule as an NFT. Each token owns a schedule of underlying-token releases (linear, cliff, custom curves); the NFT holder calls claim(tokenId) to withdraw vested tokens to themselves. The NFT is transferable — selling the vesting NFT transfers the future-claim rights to the buyer. Used for:
- Employee compensation — RSU-style vesting with secondary-sale liquidity.
- VC token vesting — investor allocations as tradeable NFTs.
- DAO contributor grants — vesting grants tradeable in treasury / market.
- Fundraising mechanics — bonding-curve vesting NFTs released over time.
The standard is token-agnostic — the underlying token can be any ERC-20 (or NEP-17 on Neo). Each NFT specifies its own asset, schedule, and start/end times.
Required Interface
interface IERC5725 {
event PayoutClaimed(uint256 indexed tokenId, address indexed recipient, uint256 claimAmount);
function claim(uint256 tokenId) external;
function vestedPayout(uint256 tokenId) external view returns (uint256);
function vestedPayoutAtTime(uint256 tokenId, uint256 timestamp) external view returns (uint256);
function vestingPayout(uint256 tokenId) external view returns (uint256); // remaining to vest
function claimablePayout(uint256 tokenId) external view returns (uint256); // vested - claimed
function payoutClaimed(uint256 tokenId) external view returns (uint256);
function vestingPeriod(uint256 tokenId) external view returns (uint256 startTime, uint256 endTime);
function payoutToken(uint256 tokenId) external view returns (address);
}The vesting curve is implementation-specific (the spec doesn't mandate linear); typical implementations expose a constructor / mint function that sets the curve parameters per token.
Neo Equivalent: NEP-11 + Per-Token Vesting Schedule + Claim Flow
public static void Claim(ByteString tokenId)
{
var owner = OwnerOf(tokenId);
if (!Runtime.CheckWitness(owner)) throw new Exception("NEP11:NotOwner");
var claimable = ClaimablePayout(tokenId);
if (claimable == 0) return;
var token = PayoutToken(tokenId);
var newClaimed = PayoutClaimed(tokenId) + claimable;
Storage.Put(Storage.CurrentContext, ClaimedKey(tokenId), newClaimed);
// Push underlying tokens to holder.
Contract.Call(token, "transfer", CallFlags.All,
new object[] { Runtime.ExecutingScriptHash, owner, claimable, null });
OnPayoutClaimed(tokenId, owner, claimable);
}
public static BigInteger VestedPayoutAtTime(ByteString tokenId, BigInteger timestamp)
{
var (start, end) = VestingPeriod(tokenId);
var totalAllocation = TotalAllocation(tokenId);
if (timestamp <= start) return 0;
if (timestamp >= end) return totalAllocation;
// Linear vesting; replace with cliff / custom curve as needed.
return (totalAllocation * (timestamp - start)) / (end - start);
}
public static BigInteger ClaimablePayout(ByteString tokenId)
=> VestedPayoutAtTime(tokenId, Runtime.Time) - PayoutClaimed(tokenId);| ERC-5725 (Ethereum) | Neo Equivalent | Notes |
|---|---|---|
claim(tokenId) | Claim(tokenId) owner-witness-checked | Pulls vested tokens to holder |
vestedPayoutAtTime(tokenId, t) | VestedPayoutAtTime(tokenId, timestamp) | Curve-specific |
claimablePayout(tokenId) | ClaimablePayout(tokenId) = VestedPayoutAtTime(now) - PayoutClaimed | Direct port |
vestingPeriod(tokenId) | VestingPeriod(tokenId) returning (start, end) | |
payoutToken(tokenId) | PayoutToken(tokenId) returning NEP-17 contract hash | |
| Per-token underlying-asset support | (tokenId → token, totalAllocation, start, end, claimed) | Storage layout |
Composition
- ERC-7540 — async vault. Vesting NFTs feed vested tokens into staking / vault flows.
- ERC-7066 — lockable. Lock the NFT during a governance / dispute period.
- ERC-2981 — royalties. Secondary-market sales of vesting NFTs may pay royalty to the original issuer.
- ERC-3475 + ERC-7092 — bonds. Vesting NFTs are often bond-like; reuse the bond mirror patterns for issuer-side accounting.
Migration Notes
For Solidity vesting protocols porting:
- Storage layout: per-tokenId (token, allocation, start, end, claimed) tuple stored serialised.
- Vesting curve: linear is the default; cliff = 0 vesting before
cliffEndthen linear; custom curves via per-token parameters. - Claim flow: pulls underlying tokens from the vesting contract's balance to the NFT holder.
- Pre-deposit the underlying tokens to the vesting contract before minting NFTs; otherwise claims revert.
The Neo port has the advantage that NEP-17 transfers inside the claim function are atomic with the storage update — no risk of state inconsistency between the claim record and the underlying-token movement.
