《独立开发者的商业指南·资本与退出》2.2 期权池与归属

期权池不是免费的预留,而是提前从创始人手里切走的一块。用 LedgerLite 融资后 10% 的池子、授予关键工程师 1% 的四年归属、100 美元行权价的离职回购,以及 24,024 美元的加速成本,算清稀释、每月 160.16 美元的归属曲线与公司保留价值。

以下情境为教学假设,人物与数字不代表经核实的真实案例。

期权池是一块提前切出来的蛋糕。它不在创始人名下,也不在投资人名下,而是留给还没进来的人。切多大、放在融资前还是融资后、分几年给、离开时怎么收回,每个选择都对应一个可以算的金额。

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,00090%691,891.20 美元
期权池100,00010%76,876.80 美元
合计1,000,000100%768,768 美元

授予关键工程师 1%,就是 10,000 股,价值 7,687.68 美元。授予后,创始人仍是 90%,池子从 10% 降到 9%,工程师拿到 1%。这 1% 不是从创始人身上再切一刀,而是从池子里转出来;真正让创始人付钱的是最初那 10% 的池子。

授予后股数比例价值
创始人900,00090%691,891.20 美元
未授予池90,0009%69,189.12 美元
工程师10,0001%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 的倍数。

月份归属比例归属金额(美分)归属金额(美元)
00%00
110%00
1225.00%192,1921,921.92
1327.08%208,2082,082.08
1837.50%288,2882,882.88
2450.00%384,3843,843.84
3675.00%576,5765,765.76
48100.00%768,7687,687.68

cliff 的作用不是「少给一年」,而是「把第一年的离职风险留给员工」。第 11 个月离开,归属为零;第 12 个月离开,拿到 25%;第 13 个月离开,拿到 27.08%。cliff 前后一个月的差距是 192,192 美分,相当于整份 grant 的四分之一。

离职回购:公司留下什么

员工离开时,已归属部分通常归员工,未归属部分由公司按约定价格回购。教学假设这份 1% grant 的总行权价是 100 美元,即 10,000 美分;回购未归属部分时,公司最多支付这个行权价,差额就是公司留下的价值。

离职月份已归属价值未归属价值回购成本公司留下
60768,76810,000758,768
12192,192576,57610,000566,576
18288,288480,48010,000470,480
24384,384384,38410,000374,384
36576,576192,19210,000182,192
48768,768000

第 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 合伙人协议与退出机制 。

常见错误与边界

  1. 把期权池当成「以后再说」。 池子一设立即稀释;10% 的池子就是创始人从 100% 到 90%,不是等到授予才发生。
  2. 把 1% 的 grant 当成公司 1% 的现金。 1% 的价值随估值变,LedgerLite 的锚是 7,687.68 美元;再发新股后可能只剩 6,988.80 美元。
  3. 混淆「grant 占池子」和「grant 占公司」。 1% 的 grant 吃掉 10% 池子的十分之一,不是池子的 1%。
  4. cliff 比例写死成 25%。 12/48 的 cliff 是 25%;6/48 的 cliff 是 12.5%,比例随 cliff 月数变。
  5. 忽略最后一个月。 按月平均会有余数;不封顶到 grant 总额,最后一个月可能多一分钱。
  6. 回购价只写「按成本」。 成本是行权价,不是公允价值;员工会比较回购价与市场价,太低会削弱 grant 的吸引力。
  7. 把 single trigger 和 double trigger 当成同一件事。 账面成本一样,但一个是确定支出,一个是条件支出;期望值要用概率算。
  8. 不为加速留池子。 5 个 1% grant 全部加速是 24,024 美元,占池子价值 31.25%;池子用完就没有下一轮 grant 的空间。
  9. 把池子放在 pre-money 却按 post-money 解释。 两种放法的稀释承担者不同;同一句「池子 10%」可以差出一个百分点。
  10. 用期权池替代现金薪酬。 期权价值取决于估值和归属,不能按面值当工资发;要给候选人同时看现金、归属曲线和回购条款。

期权池解决「未来的人拿什么」,下一节解决「已经进来的人怎么出去」。两者都是 cap table 上的同一块地,只是一个向前切,一个向后收。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「books」更多文章

  1. 《移动游戏产品的架构·商业化与发行工程》附录 C 商业化与发行评审清单
  2. 《移动游戏产品的架构·商业化与发行工程》附录 B 双案例商业化事实表
  3. 《移动游戏产品的架构·商业化与发行工程》附录 A 商业化与发行术语速查