《独立开发者的商业指南·资本与退出》2.1 股权结构与表决权

股权不是一个百分比,而是所有权、表决权、分红权三层。用 Nord Studio 的 50/50 僵局、引入第三位股东后的 45/45/10、同股不同权把 10% 投票压到 1.10%,再用 Banzhaf 权力指数和一致行动、否决权,算清每种安排的比例变化与真实代价。

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

股权常被写成一个数字:你占 50%,他占 50%。但 50% 的股份、50% 的投票、50% 的分红,是三件可以分开的事。融资、引入合伙人、设置期权池,改的往往不是同一个数字。把三层拆开,才能回答「谁说了算」「谁拿钱」「谁可以拦住谁」。

Nord Studio 是这一节的主案例:两位合伙人各 50%,没有期权池,也没有书面退出条款。它的年收入 154,320 美元,SDE 34,320 美元,4.5 倍 SDE 的估值锚是 154,440 美元。下面所有比例和金额都从这三个数推出,不引入第二套事实。

Tidecast 是一人公司:年净得 138,171 美元,没有合伙人,也没有 50/50 结构。它的股权问题不是僵局,而是单一所有者离开时 IP、账号和发行协议如何承继;Nord 至少有两个决策人,Tidecast 的关键人风险更集中。

三层结构:所有权、表决权、分红权

第一层是所有权,按 fully diluted 的股份数算。它决定「公司值钱时你分到多少」,也是融资稀释最直接的刻度。第二层是表决权,按每股投票数算。第三层是分红权,按分红单位算;默认一票一权、一股一分红,但优先股、利润分享权、同股不同权都会让三层分叉。

层计算口径Nord 50/50同股不同权示例被什么改变
所有权持股数 ÷ 总股本50% / 50%45% / 45% / 10%新股发行、老股转让、期权池
表决权投票数 ÷ 总投票数50% / 50%49.45% / 49.45% / 1.10%每股投票数、一致行动、否决权
分红权分红单位 ÷ 总分红单位50% / 50%45% / 45% / 10%分红上限、优先股、利润分享权

Nord 的两层完全重合:450,000 股对 450,000 股,每股一票,每股一个分红单位。重合让事情看起来简单,也让问题被藏起来:一旦两个人意见相反,50% 对 50% 就是两个人都没有多数。

先记住一个口径:所有权看「发行了多少股」,表决权看「这些股带多少票」,分红权看「这些股分多少利润」。三者可以相同,但不能假设相同。

50/50 僵局:成本可以算出来

50/50 本身不是错误,错误是「50/50 且没有打破僵局的机制」。普通决议需要超过半数;两位合伙人各 50%,任何一位都能让决议停在原地。停一天不是抽象损失,它可以用 SDE 计价。

Nord 的年 SDE 是 34,320 美元,月均 2,860 美元。僵局持续 3 个月,损失 3 × 2,860 = 8,580 美元;6 个月是 17,160 美元;12 个月是 34,320 美元,正好是一整年的 SDE。

僵局时长按 SDE 计价的机会成本占年 SDE
3 个月8,580 美元25.0%
6 个月17,160 美元50.0%
12 个月34,320 美元100.0%

如果僵局拖住的是一笔出售,成本还可以用现值算。Nord 的估值锚是 154,440 美元;假设教学折现率 20%,延迟 12 个月出售的现值损失是 154,440 × (1 − 1 ÷ 1.2) = 25,740 美元,相当于估值锚的 16.7%。这不是「少赚一点」,而是把 154,440 美元的报价拖成了 128,700 美元的现值。

打破僵局的结构有四类,代价不同:

  • 51/49:一方有普通多数,决策快,但少数方失去制衡,通常会要求否决权或董事会席位作为交换。
  • 主席决定票:50/50 加一条「平票时主席多一票」,成本低,但主席人选本身就是新的争夺点。
  • 分级表决:把日常经营和重大事项分开,日常按多数,重大事项要求更高比例。
  • 买卖选择权(shotgun):一方报价,另一方选择按同价买入或卖出;它把僵局变成一次性博弈,但需要双方都有现金。

这些是结构算术,不是法律意见;具体条款要由当地执业律师起草。

引入第三位股东:45/45/10 与表决权杠杆

Nord 要引入第三位股东,有三条路径,比例完全不同。第一条是新发 100,000 股给第三人:原股本 900,000 股,新总股本 1,000,000 股,两位合伙人各 45%,第三人 10%。第二条是新发 225,000 股:新总股本 1,125,000 股,两位合伙人各 40%,第三人 20%。第三条是其中一位合伙人转让 100,000 股老股:总股本不变,转让方降到 40%,受让方 10%,另一位仍是 50%。

路径合伙人 A合伙人 B第三人 C总股本
新发 100,000 股45%45%10%1,000,000
新发 225,000 股40%40%20%1,125,000
A 转让 100,000 股40%50%10%900,000

新发和老股转让的区别,不只是比例:新发把 100,000 股的价值从两位合伙人身上稀释出来,老股转让只是换了一个股东,公司没有拿到新钱。把两者写成同一张「融资后股权表」,会漏掉「钱有没有进公司」这一行。

比例之外,还要看表决权。45/45/10 的表决权看起来是 45%、45%、10%,但用 Banzhaf 权力指数算,第三位股东的权力是三分之一。原因是普通决议需要超过 50%,任何两位股东结盟都能过:A+B 是 90%,A+C 是 55%,B+C 是 55%。在三场获胜联盟里,C 出现在两场,A 和 B 也各出现在两场;总 pivotal 次数是 6,每人 2 次,所以每人 33.3%。

股权表所有权表决权(一票一股)Banzhaf 权力
50/5050 / 5050 / 5050.0 / 50.0
45/45/1045 / 45 / 1045 / 45 / 1033.3 / 33.3 / 33.3
45/45/10,创始人 10 票/股45 / 45 / 1049.45 / 49.45 / 1.1033.3 / 33.3 / 33.3
51/29/2051 / 29 / 2051 / 29 / 20100 / 0 / 0
45/45/10,A+B 一致行动45 / 45 / 1045 / 45 / 1050.0 / 50.0 / 0

这张表最反直觉的是第三行:同股不同权把第三人的投票比例从 10% 压到 1.10%,但没有改变他的 Banzhaf 权力。两位创始人各 49.45% 的投票,谁都不能单独过半;第三人那 1.10% 仍然是打破平票的那一票。只要两位创始人股份相等,同股不同权就压不掉第三人的 pivotal 地位。

第四行是另一种极端:51/29/20 里,51% 的股东单独构成获胜联盟,Banzhaf 权力全部归他,29% 和 20% 都是零。第五行是第三种做法:A 和 B 签一致行动协议,合计 90%,第三人即使持股 10%,也永远不在获胜联盟里,权力归零。

所以「控制权」有三种来源:股份多数、投票权倍数、以及联盟。三者可以叠加,也可以互相替代。融资时投资人常说的「保护性条款」,就是第四条来源:不靠投票多数,而是在特定事项上要求同意。

同股不同权、一致行动与否决权

同股不同权解决的是「融资后创始人被稀释到 50% 以下」的问题。做法是给创始人股份更高的每股投票数,例如 10 票/股。Nord 的 45/45/10 在 10 票/股下,投票权变成 49.45%、49.45%、1.10%;两位创始人合计 98.9%,第三人只有 1.10%。但如上表,第三人的 Banzhaf 权力仍是三分之一,因为两位创始人之间的平衡没有被打破。

一致行动解决的是「两个股东能不能当成一个投票单位」。A 和 B 签一致行动协议后,45% + 45% = 90%,在任何普通决议上都过;第三人 10% 既不能阻止,也不能成为关键票。代价是 A 和 B 之间要先统一意见,僵局从「三个人谈」变成「两个人先谈」。

否决权解决的是「少数股东怎么保护自己」。它不是持股比例,而是一张事项清单:预算、招聘、借款、发行新股、出售公司、改变业务方向。第三人只有 10%,但如果清单里有「出售公司需第三人同意」,他就能拦住一笔 102,960 美元的拖售。否决权的成本是决策延迟;按前面的口径,3 个月延迟就是 8,580 美元的 SDE。

把三种安排放在一起,Nord 的选择不是「哪种最好」,而是「哪种代价可以接受」:同股不同权保创始人控制,但投资人会要求否决权;一致行动保两个人同步,但把第三人推到对立面;否决权保少数股东,但把决策变慢。每一种都要用数字标价,而不是只写进章程。

把结构写成代码

下面的 equity.ts 把三层结构固化成函数:equityLayers 同时返回所有权、表决权和分红权,issueShares 处理新股发行,banzhaf 用获胜联盟计算权力指数,deadlockCost 和 delayCost 把僵局与延迟折成金额。所有股份是整数,比率是基点,非法输入返回 Result 错误。

// file: src/capital/equity.ts
// tier: L1
// verify: node --test test/capital/equity.test.ts

import { type Cents } from "../core/money.ts";
import { type Bps, divide } from "../core/rate.ts";
import { type Result, fail, ok } from "../core/result.ts";

export type Holder = {
  name: string;
  shares: number;
  votesPerShare: number;
  dividendUnits?: number;
};

export type EquityLayer = {
  name: string;
  ownershipBps: Bps;
  votingBps: Bps;
  dividendBps: Bps;
};

export type EquityStructure = {
  holders: EquityLayer[];
  totalShares: number;
  totalVotes: number;
  totalDividendUnits: number;
  dualClass: boolean;
};

export type VotingPower = {
  name: string;
  powerBps: Bps;
};

function validate(holders: Holder[]): Result<null> {
  if (holders.length === 0) {
    return fail("at least one holder is required");
  }
  const names = new Set<string>();
  for (const holder of holders) {
    if (holder.name.trim() === "") {
      return fail("holder name must not be empty");
    }
    if (names.has(holder.name)) {
      return fail(`duplicate holder: ${holder.name}`);
    }
    names.add(holder.name);
    if (!Number.isInteger(holder.shares) || holder.shares <= 0) {
      return fail(`invalid shares for ${holder.name}`);
    }
    if (!Number.isInteger(holder.votesPerShare) || holder.votesPerShare < 0) {
      return fail(`invalid votes for ${holder.name}`);
    }
    const units = holder.dividendUnits ?? holder.shares;
    if (!Number.isInteger(units) || units < 0) {
      return fail(`invalid dividend units for ${holder.name}`);
    }
  }
  return ok(null);
}

/**
 * Three layers of the same cap table: ownership by shares, voting by
 * votes, and dividends by dividend units. By default one share carries
 * one vote and one dividend unit; both can be overridden per holder.
 */
export function equityLayers(holders: Holder[]): Result<EquityStructure> {
  const checked = validate(holders);
  if (!checked.ok) {
    return checked;
  }
  const totalShares = holders.reduce((acc, holder) => acc + holder.shares, 0);
  const totalVotes = holders.reduce(
    (acc, holder) => acc + holder.shares * holder.votesPerShare,
    0,
  );
  const totalDividendUnits = holders.reduce(
    (acc, holder) => acc + (holder.dividendUnits ?? holder.shares),
    0,
  );
  if (totalVotes <= 0) {
    return fail("total votes must be positive");
  }
  if (totalDividendUnits <= 0) {
    return fail("total dividend units must be positive");
  }

  const layers = holders.map((holder) => ({
    name: holder.name,
    ownershipBps: divide(holder.shares, totalShares),
    votingBps: divide(holder.shares * holder.votesPerShare, totalVotes),
    dividendBps: divide(
      holder.dividendUnits ?? holder.shares,
      totalDividendUnits,
    ),
  }));
  const dualClass = new Set(holders.map((holder) => holder.votesPerShare)).size > 1;

  return ok({
    holders: layers,
    totalShares,
    totalVotes,
    totalDividendUnits,
    dualClass,
  });
}

/** Add a newly issued holder without changing anyone else's share count. */
export function issueShares(
  holders: Holder[],
  name: string,
  newShares: number,
  votesPerShare: number,
  dividendUnits?: number,
): Result<EquityStructure> {
  if (!Number.isInteger(newShares) || newShares <= 0) {
    return fail("new shares must be a positive integer");
  }
  const candidate: Holder = { name, shares: newShares, votesPerShare };
  if (dividendUnits !== undefined) {
    candidate.dividendUnits = dividendUnits;
  }
  return equityLayers([...holders, candidate]);
}

/** SDE lost while a decision waits, using a straight months/12 proration. */
export function deadlockCost(
  annualSdeCents: Cents,
  months: number,
): Result<Cents> {
  if (!Number.isInteger(annualSdeCents) || annualSdeCents < 0) {
    return fail("annual SDE must be a non-negative integer amount");
  }
  if (!Number.isInteger(months) || months < 0) {
    return fail("months must be a non-negative integer");
  }
  return ok(Math.round((annualSdeCents * months) / 12));
}

/** Present-value loss from delaying a sale, with an explicit discount rate. */
export function delayCost(
  valueCents: Cents,
  months: number,
  annualDiscountBps: Bps,
): Result<Cents> {
  if (!Number.isInteger(valueCents) || valueCents < 0) {
    return fail("value must be a non-negative integer amount");
  }
  if (!Number.isInteger(months) || months < 0) {
    return fail("months must be a non-negative integer");
  }
  if (!Number.isInteger(annualDiscountBps) || annualDiscountBps < 0) {
    return fail("discount rate must be a non-negative integer in basis points");
  }
  const years = months / 12;
  const factor = 1 - 1 / Math.pow(1 + annualDiscountBps / 10000, years);
  return ok(Math.round(valueCents * factor));
}

/**
 * Banzhaf power for a small cap table: count the winning coalitions in
 * which each holder is pivotal, then divide by all pivotal appearances.
 * A 10% holder can carry a third of the power when the other two are tied.
 */
export function banzhaf(holders: Holder[]): Result<VotingPower[]> {
  const checked = validate(holders);
  if (!checked.ok) {
    return checked;
  }
  if (holders.length > 12) {
    return fail("banzhaf supports at most 12 holders");
  }
  const votes = holders.map((holder) => holder.shares * holder.votesPerShare);
  const totalVotes = votes.reduce((acc, value) => acc + value, 0);
  if (totalVotes <= 0) {
    return fail("total votes must be positive");
  }
  const majority = Math.floor(totalVotes / 2) + 1;
  const pivotal = new Array<number>(holders.length).fill(0);
  const subsets = 1 << holders.length;

  for (let mask = 1; mask < subsets; mask += 1) {
    let coalition = 0;
    for (let i = 0; i < holders.length; i += 1) {
      if (mask & (1 << i)) {
        coalition += votes[i];
      }
    }
    if (coalition < majority) {
      continue;
    }
    for (let i = 0; i < holders.length; i += 1) {
      if ((mask & (1 << i)) && coalition - votes[i] < majority) {
        pivotal[i] += 1;
      }
    }
  }

  const totalPivotal = pivotal.reduce((acc, value) => acc + value, 0);
  if (totalPivotal === 0) {
    return fail("no pivotal votes in this cap table");
  }
  return ok(
    holders.map((holder, index) => ({
      name: holder.name,
      powerBps: divide(pivotal[index], totalPivotal),
    })),
  );
}

代码有四个刻意的边界。第一,多数门槛是 floor(totalVotes / 2) + 1,所以恰好 50% 不算多数,50/50 必须找到第二个盟友。第二,banzhaf 只支持 12 位以内的股东,因为它是枚举所有联盟;股东更多时要用近似或按投票块合并。第三,dividendUnits 可以独立于股份数,用来表示利润分享权或分红上限,但默认等于股份。第四,所有股份和金额都是整数,比例用基点,避免浮点在多轮融资后累积误差。

小结

  • 股权有三层:所有权看股份,表决权看票数,分红权看分红单位;三层可以重合,也可以分开。
  • Nord 的 50/50 在两层上完全重合,但没有打破僵局的机制;3 个月僵局按 SDE 计价是 8,580 美元,12 个月是 34,320 美元。
  • 延迟出售 12 个月,在 20% 教学折现率下损失 25,740 美元,相当于 154,440 美元估值锚的 16.7%。
  • 引入第三人后,45/45/10 的所有权是 45/45/10,但 Banzhaf 权力是 33.3/33.3/33.3;10% 的股东是打破平票的关键票。
  • 同股不同权把第三人的投票比例压到 1.10%,但只要两位创始人股份相等,他的 pivotal 权力不变。
  • 51/29/20 让 51% 的股东拿走全部 Banzhaf 权力;一致行动协议则让 A+B 的 90% 变成稳定多数,第三人归零。
  • 控制权可以来自股份多数、投票权倍数、联盟与否决权;每一种都要用比例和金额标价。

阅读导航:上一节:1.3 什么时候不该融资 · 下一节:2.2 期权池与归属 。

常见错误与边界

  1. 把所有权当成表决权。 45% 的股份不一定有 45% 的表决权;同股不同权、一致行动、否决权都会改变实际控制。
  2. 把 50/50 当成公平。 公平是结果,不是结构;没有平票机制时,50/50 把每一次分歧都变成停摆。
  3. 只算僵局的直接损失。 僵局还会延迟招聘、产品和出售;3 个月 8,580 美元只是 SDE 口径,不含机会成本。
  4. 把新发和老股转让混为一谈。 新发 100,000 股让总股本变成 1,000,000,老股转让仍是 900,000;前者公司拿到钱,后者没有。
  5. 认为同股不同权一定保住控制。 两位创始人股份相等时,第三人那 1.10% 的投票仍是关键票;要真正归零,需要一致行动或一方超过 50%。
  6. 用持股比例替代 Banzhaf 权力。 45/45/10 里第三人只有 10% 股份,却有 33.3% 的权力;51/29/20 里 29% 的股东权力是 0。
  7. 忽略否决权的延迟成本。 一张事项清单可以让 10% 的股东拦住出售;拦住 3 个月就是 8,580 美元的 SDE。
  8. 把一致行动协议当成永远有效。 协议有期限、退出和违约条款;一方退出后,90% 的联盟可能瞬间变成 45/45/10。
  9. 在融资后才想起期权池。 期权池放在 pre-money 还是 post-money,稀释的是不同的人;结构要在签字前算,不是签字后补。
  10. 把结构算术当成法律意见。 本节只算比例和金额;章程、股东协议、投票权安排要由当地执业律师起草。

股权结构解决「谁说了算」,下一节解决「还没进来的人拿什么」。两者共用同一张 cap table,但一个是现在,一个是未来。

继续阅读

探索更多技术文章

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

全部文章 返回首页

「books」更多文章

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