以下情境为教学假设,人物与数字不代表经核实的真实案例。
期权池是一块提前切出来的蛋糕。它不在创始人名下,也不在投资人名下,而是留给还没进来的人。切多大、放在融资前还是融资后、分几年给、离开时怎么收回,每个选择都对应一个可以算的金额。
LedgerLite 是一人 B2B 订阅 SaaS:260 家客户,ARR 240,240 美元,3.2 倍 ARR 的估值锚是 768,768 美元。假设融资后总股本 1,000,000 股,期权池占 10%,即 100,000 股。下面所有每股价值都从 768,768 美元这个锚推出,不引入第二套估值。
期权池:先切一块,再发
10% 的池子意味着创始人从 100% 降到 90%。这不是「以后再说」的稀释,而是签字当天就发生的稀释:池子里的 100,000 股按 768,768 美元的估值算是 76,876.80 美元,这部分价值从创始人手里转移到未来员工手里。
| 持有人 | 股数 | 融资后比例 | 按 768,768 估值 |
|---|---|---|---|
| 创始人 | 900,000 | 90% | 691,891.20 美元 |
| 期权池 | 100,000 | 10% | 76,876.80 美元 |
| 合计 | 1,000,000 | 100% | 768,768 美元 |
授予关键工程师 1%,就是 10,000 股,价值 7,687.68 美元。授予后,创始人仍是 90%,池子从 10% 降到 9%,工程师拿到 1%。这 1% 不是从创始人身上再切一刀,而是从池子里转出来;真正让创始人付钱的是最初那 10% 的池子。
| 授予后 | 股数 | 比例 | 价值 |
|---|---|---|---|
| 创始人 | 900,000 | 90% | 691,891.20 美元 |
| 未授予池 | 90,000 | 9% | 69,189.12 美元 |
| 工程师 | 10,000 | 1% | 7,687.68 美元 |
池子的大小要倒着算:先列未来 18 个月要招的几个关键角色,每人给多少,再留 20% 缓冲。LedgerLite 的 10% 池子如果只发 5 个 1% 的 grant,就用掉一半,剩 5%;如果每个 grant 平均 2%,五个就正好用光。池子用完再发,就要新开一轮稀释,或者从老股东手里转老股。
还有一个常被忽略的时点:池子放在 pre-money 还是 post-money。放在 pre-money,池子由创始人独自承担稀释;放在 post-money,投资人和创始人按比例共同承担。同样的 10%,两种放法对创始人的差距可以到一个百分点以上。签字前要把「池子在哪一侧」写清楚,而不是只写「池子 10%」。
归属:四年、1 年 cliff、36 个月
标准的四年归属是「1 年 cliff + 36 个月按月」。第 12 个月一次性归属 25%,剩下 75% 在之后的 36 个月里平均归属,每月 75% ÷ 36 = 2.0833%。到第 48 个月,累计 100%。
1% 的 grant 价值 768,768 美分,cliff 归属 25% 是 192,192 美分,即 1,921.92 美元;剩下 576,576 美分,除以 36 个月,每月 16,016 美分,即 160.16 美元。这个数能整除,是因为 768,768 × 75% = 576,576,正好是 36 的倍数。
| 月份 | 归属比例 | 归属金额(美分) | 归属金额(美元) |
|---|---|---|---|
| 0 | 0% | 0 | 0 |
| 11 | 0% | 0 | 0 |
| 12 | 25.00% | 192,192 | 1,921.92 |
| 13 | 27.08% | 208,208 | 2,082.08 |
| 18 | 37.50% | 288,288 | 2,882.88 |
| 24 | 50.00% | 384,384 | 3,843.84 |
| 36 | 75.00% | 576,576 | 5,765.76 |
| 48 | 100.00% | 768,768 | 7,687.68 |
cliff 的作用不是「少给一年」,而是「把第一年的离职风险留给员工」。第 11 个月离开,归属为零;第 12 个月离开,拿到 25%;第 13 个月离开,拿到 27.08%。cliff 前后一个月的差距是 192,192 美分,相当于整份 grant 的四分之一。
离职回购:公司留下什么
员工离开时,已归属部分通常归员工,未归属部分由公司按约定价格回购。教学假设这份 1% grant 的总行权价是 100 美元,即 10,000 美分;回购未归属部分时,公司最多支付这个行权价,差额就是公司留下的价值。
| 离职月份 | 已归属价值 | 未归属价值 | 回购成本 | 公司留下 |
|---|---|---|---|---|
| 6 | 0 | 768,768 | 10,000 | 758,768 |
| 12 | 192,192 | 576,576 | 10,000 | 566,576 |
| 18 | 288,288 | 480,480 | 10,000 | 470,480 |
| 24 | 384,384 | 384,384 | 10,000 | 374,384 |
| 36 | 576,576 | 192,192 | 10,000 | 182,192 |
| 48 | 768,768 | 0 | 0 | 0 |
第 18 个月离职,公司用 100 美元收回价值 4,804.80 美元的未归属部分,留下 4,704.80 美元;员工带走已归属的 2,882.88 美元。第 36 个月离职,未归属只剩 1,921.92 美元,公司留下的价值降到 1,821.92 美元。回购价越低,公司留下越多,但员工会把这一点折进 offer 的比较里。
这张表只算「公司留下多少」,不算「员工为什么走」。如果回购价显著低于公允价值,grant 的实际吸引力会下降;如果回购价接近公允价值,公司又失去了「未归属」的保护。两端都要用同一个估值锚来谈,而不是用「一股一美元」之类的口头价。
加速归属:谁承担剩下的 62.5%
加速归属分两种。single trigger 是「公司被收购,未归属部分立即全部归属」;double trigger 是「被收购且员工被解雇,才加速」。两者在收购那一刻的账面成本一样,都是未归属部分的价值;区别是这个成本是确定的,还是条件触发的。
第 18 个月,未归属是 62.5%,价值 480,480 美分,即 4,804.80 美元。single trigger 下,收购方要立刻承担这 4,804.80 美元;double trigger 下,只有员工被解雇才承担。假设教学触发概率 50%,期望成本是 240,240 美分,即 2,402.40 美元。
| 触发方式 | 条件 | 第 18 个月成本 | 谁承担 |
|---|---|---|---|
| single trigger | 收购即加速 | 4,804.80 美元 | 收购方/全体股东 |
| double trigger | 收购且解雇 | 4,804.80 美元(条件触发) | 收购方,按概率 |
| double trigger,50% 概率 | 教学假设 | 2,402.40 美元期望值 | 收购方 |
池子要为加速留出空间。如果池子里有 5 个 1% 的 grant,第 18 个月全部 single trigger,加速成本是 5 × 480,480 = 2,402,400 美分,即 24,024 美元,相当于 76,876.80 美元池子价值的 31.25%。这不是小数:池子名义上还剩 5%,但加速会把其中三分之一立刻变成员工已归属的权益。
还有一个未来稀释的提醒:如果公司之后再发 100,000 股新股(教学假设),总股本变成 1,100,000 股,工程师那 10,000 股从 1% 降到 0.909%,按同一估值算价值从 7,687.68 美元降到 6,988.80 美元,少了 698.88 美元。grant 的价值不是一个固定数字,它随每一次新发而变。
把归属写成代码
下面的 option-pool.ts 把归属、池子消耗、回购和加速固化成函数。vestedAt 按「cliff 一次性 + 剩余按月」计算,金额用整数美分,最后一个月精确落在 grant 总额;poolUse 算 1% 的 grant 吃掉池子的多少;repurchase 和 accelerationCost 分别算离职与加速的账面成本。
// file: src/capital/option-pool.ts
// tier: L1
// verify: node --test test/capital/option-pool.test.ts
import { type Cents } from "../core/money.ts";
import { type Bps, applyRate, divide } from "../core/rate.ts";
import { type Result, fail, ok } from "../core/result.ts";
export type VestingTerms = {
grantCents: Cents;
totalMonths: number;
cliffMonths: number;
};
export type VestingPoint = {
month: number;
vestedBps: Bps;
vestedCents: Cents;
};
export type PoolUse = {
consumedBps: Bps;
remainingBps: Bps;
};
export type Repurchase = {
unvestedCents: Cents;
costCents: Cents;
retainedCents: Cents;
};
function validateTerms(terms: VestingTerms): Result<null> {
if (!Number.isInteger(terms.grantCents) || terms.grantCents <= 0) {
return fail("grant must be a positive integer amount");
}
if (!Number.isInteger(terms.totalMonths) || terms.totalMonths <= 0) {
return fail("total months must be a positive integer");
}
if (
!Number.isInteger(terms.cliffMonths) ||
terms.cliffMonths < 0 ||
terms.cliffMonths > terms.totalMonths
) {
return fail("cliff months must be between zero and total months");
}
return ok(null);
}
/**
* Four-year vesting with a one-year cliff: 25% at the cliff, then the
* remaining 75% in equal monthly steps. Cents are computed from the
* remaining balance so the final month lands exactly on the grant.
*/
export function vestedAt(terms: VestingTerms, month: number): Result<VestingPoint> {
const checked = validateTerms(terms);
if (!checked.ok) {
return checked;
}
if (!Number.isInteger(month) || month < 0 || month > terms.totalMonths) {
return fail("month must be an integer between zero and total months");
}
if (month < terms.cliffMonths) {
return ok({ month, vestedBps: 0, vestedCents: 0 });
}
const cliffBps = divide(terms.cliffMonths, terms.totalMonths);
const cliffCents = applyRate(terms.grantCents, cliffBps);
const tailMonths = terms.totalMonths - terms.cliffMonths;
if (tailMonths === 0) {
return ok({ month, vestedBps: 10000, vestedCents: terms.grantCents });
}
const monthlyCents = Math.round((terms.grantCents - cliffCents) / tailMonths);
const steps = month - terms.cliffMonths;
const vestedCents = Math.min(
terms.grantCents,
cliffCents + monthlyCents * steps,
);
return ok({
month,
vestedBps: divide(vestedCents, terms.grantCents),
vestedCents,
});
}
/** Every month boundary from zero through the end of the schedule. */
export function vestingSchedule(terms: VestingTerms): Result<VestingPoint[]> {
const checked = validateTerms(terms);
if (!checked.ok) {
return checked;
}
const points: VestingPoint[] = [];
for (let month = 0; month <= terms.totalMonths; month += 1) {
const point = vestedAt(terms, month);
if (!point.ok) {
return point;
}
points.push(point.value);
}
return ok(points);
}
/** Value of a grant, using the company valuation as the per-share anchor. */
export function grantValue(valuationCents: Cents, grantBps: Bps): Result<Cents> {
if (!Number.isInteger(valuationCents) || valuationCents < 0) {
return fail("valuation must be a non-negative integer amount");
}
if (!Number.isInteger(grantBps) || grantBps < 0 || grantBps > 10000) {
return fail("grant must be between zero and 10000 basis points");
}
return ok(applyRate(valuationCents, grantBps));
}
/** How much of the pool a grant consumes, and what remains. */
export function poolUse(poolBps: Bps, grantBps: Bps): Result<PoolUse> {
if (!Number.isInteger(poolBps) || poolBps <= 0 || poolBps > 10000) {
return fail("pool must be between one and 10000 basis points");
}
if (!Number.isInteger(grantBps) || grantBps < 0 || grantBps > poolBps) {
return fail("grant must be between zero and the pool size");
}
return ok({
consumedBps: divide(grantBps, poolBps),
remainingBps: poolBps - grantBps,
});
}
/**
* Repurchase of unvested units at the original strike. The company pays
* at most the unvested value; the difference is the value it retains.
*/
export function repurchase(
grantCents: Cents,
vestedCents: Cents,
strikeCents: Cents,
): Result<Repurchase> {
if (!Number.isInteger(grantCents) || grantCents <= 0) {
return fail("grant must be a positive integer amount");
}
if (
!Number.isInteger(vestedCents) ||
vestedCents < 0 ||
vestedCents > grantCents
) {
return fail("vested amount must be between zero and the grant");
}
if (!Number.isInteger(strikeCents) || strikeCents < 0) {
return fail("strike must be a non-negative integer amount");
}
const unvestedCents = grantCents - vestedCents;
const costCents = Math.min(strikeCents, unvestedCents);
return ok({
unvestedCents,
costCents,
retainedCents: unvestedCents - costCents,
});
}
/** Value that must vest immediately under a single- or double-trigger. */
export function accelerationCost(
grantCents: Cents,
vestedCents: Cents,
): Result<Cents> {
if (
!Number.isInteger(grantCents) ||
grantCents <= 0 ||
!Number.isInteger(vestedCents) ||
vestedCents < 0 ||
vestedCents > grantCents
) {
return fail("grant and vested amounts are inconsistent");
}
return ok(grantCents - vestedCents);
}
/** Expected acceleration cost when a double trigger is only probabilistic. */
export function expectedAcceleration(
unvestedCents: Cents,
triggerProbabilityBps: Bps,
): Result<Cents> {
if (!Number.isInteger(unvestedCents) || unvestedCents < 0) {
return fail("unvested amount must be a non-negative integer");
}
if (
!Number.isInteger(triggerProbabilityBps) ||
triggerProbabilityBps < 0 ||
triggerProbabilityBps > 10000
) {
return fail("probability must be between zero and 10000 basis points");
}
return ok(applyRate(unvestedCents, triggerProbabilityBps));
}
代码有四个边界。第一,cliff 的 25% 由 cliffMonths / totalMonths 得出,所以 12/48 正好是 2500 基点;如果改成 6 个月 cliff,cliff 比例会变成 12.5%,不是固定 25%。第二,剩余金额按月平均后用整数美分,最后一个月由 Math.min 封顶到 grant 总额,不会多出一分钱。第三,poolUse 的 consumedBps 是「grant 占池子的比例」,不是「grant 占公司的比例」;1% 的 grant 对 10% 的池子是 10%,不是 1%。第四,repurchase 的成本上限是行权价,不是公允价值;这是教学简化,真实条款可能按折价或公允价值回购。
小结
- 期权池不是免费的预留:LedgerLite 的 10% 池子把创始人从 100% 降到 90%,池子价值 76,876.80 美元。
- 授予 1% 是从池子里转出 10,000 股,价值 7,687.68 美元;池子剩 9%,不是创始人再被稀释一次。
- 四年归属 = 1 年 cliff 的 25% + 36 个月每月 2.0833%;1% grant 的 cliff 是 1,921.92 美元,之后每月 160.16 美元。
- 第 18 个月离职,未归属 62.5% 价值 4,804.80 美元;按 100 美元行权价回购,公司留下 4,704.80 美元。
- single trigger 把第 18 个月的 4,804.80 美元全部变成确定成本;double trigger 在 50% 概率下期望成本是 2,402.40 美元。
- 5 个 1% grant 全部加速,成本 24,024 美元,相当于池子价值的 31.25%;池子要提前留出这笔钱。
阅读导航:上一节:2.1 股权结构与表决权 · 下一节:2.3 合伙人协议与退出机制 。
常见错误与边界
- 把期权池当成「以后再说」。 池子一设立即稀释;10% 的池子就是创始人从 100% 到 90%,不是等到授予才发生。
- 把 1% 的 grant 当成公司 1% 的现金。 1% 的价值随估值变,LedgerLite 的锚是 7,687.68 美元;再发新股后可能只剩 6,988.80 美元。
- 混淆「grant 占池子」和「grant 占公司」。 1% 的 grant 吃掉 10% 池子的十分之一,不是池子的 1%。
- cliff 比例写死成 25%。 12/48 的 cliff 是 25%;6/48 的 cliff 是 12.5%,比例随 cliff 月数变。
- 忽略最后一个月。 按月平均会有余数;不封顶到 grant 总额,最后一个月可能多一分钱。
- 回购价只写「按成本」。 成本是行权价,不是公允价值;员工会比较回购价与市场价,太低会削弱 grant 的吸引力。
- 把 single trigger 和 double trigger 当成同一件事。 账面成本一样,但一个是确定支出,一个是条件支出;期望值要用概率算。
- 不为加速留池子。 5 个 1% grant 全部加速是 24,024 美元,占池子价值 31.25%;池子用完就没有下一轮 grant 的空间。
- 把池子放在 pre-money 却按 post-money 解释。 两种放法的稀释承担者不同;同一句「池子 10%」可以差出一个百分点。
- 用期权池替代现金薪酬。 期权价值取决于估值和归属,不能按面值当工资发;要给候选人同时看现金、归属曲线和回购条款。
期权池解决「未来的人拿什么」,下一节解决「已经进来的人怎么出去」。两者都是 cap table 上的同一块地,只是一个向前切,一个向后收。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。