vesting-schedule-math
v1.0.0
Published
Equity vesting schedule math — cliff + monthly/quarterly/annual tranches, exact fractional-share conventions, vested-as-of queries, and single/double-trigger acceleration. Zero dependencies.
Maintainers
Readme
vesting-schedule-math
Equity vesting schedule math — cliff + monthly/quarterly/annual tranches, exact fractional-share conventions, vested-as-of queries, and single/double-trigger acceleration. Zero dependencies.
npm install vesting-schedule-mathWhy
Every cap-table tool, offer-letter calculator, and equity dashboard rebuilds the same schedule math — and the details bite: what does a January 31 grant vest on in February? Where do the fractional shares go when 1,000 shares don't divide by 48 months? npm had nothing for it. This package encodes the standard conventions: month-end clamping, round-down-per-tranche with a final true-up (cumulative vesting never overstates and always sums exactly to the grant), and the acceleration variants term sheets actually use.
import { vestingSchedule, vestedShares, accelerate } from "vesting-schedule-math";
const grant = { shares: 4800, startDate: "2026-01-15", totalMonths: 48, cliffMonths: 12 };
vestingSchedule(grant);
// [{ date: "2027-01-15", shares: 1200, isCliff: true }, then 100/month × 36]
vestedShares(grant, "2028-01-15"); // 2400 — halfway
accelerate({ grant, asOf: "2028-01-15", acceleration: 0.5 });
// { vestedBefore: 2400, accelerated: 1200, vestedAfter: 3600, remainingUnvested: 1200 }API
vestingSchedule(grant)— the full tranche list{ date, shares, cumulative, isCliff }[].grant:{ shares, startDate, totalMonths, cliffMonths?, frequency? }("monthly"default,"quarterly","annual").vestedShares(grant, asOf)/unvestedShares(grant, asOf).accelerate({ grant, asOf, acceleration })—accelerationis a fraction of unvested (1= full trigger,0.5= half) or{ months: n }to pull the schedule forward n months.
Conventions: months clamp to month-end (Jan 31 → Feb 28/29); every tranche is a whole number of shares, rounded down against exact linear accrual, with the final tranche truing up — the invariant sum(tranches) === grant.shares always holds. Invalid inputs throw RangeError.
Related
Part of a cap-table math family: safe-stack-conversion · priced-round-math · exit-waterfall.
Author
Built by Moshe Malka — engineering leader in New York City. Studio work at Quentin.Code.
MIT © Moshe Malka
