Skip to content

1984vc/cap-table

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

258 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@1984vc/cap-table

TypeScript library for modeling startup cap table ownership across funding events — SAFE note conversions, priced rounds, option pools, and dilution.

Used by the 1984 Ventures Cap Table Worksheet, a free web tool for founders.

Install

npm install @1984vc/cap-table

Quick Start

import {
  fitConversion,
  buildPricedRoundCapTable,
  CapTableRowType,
  CommonRowType,
} from "@1984vc/cap-table";

// 1. Define your current shareholders
const founders = [
  { name: "Founder 1", shares: 4_500_000, type: CapTableRowType.Common, commonType: CommonRowType.Shareholder },
  { name: "Founder 2", shares: 4_500_000, type: CapTableRowType.Common, commonType: CommonRowType.Shareholder },
  { name: "Options Pool", shares: 1_000_000, type: CapTableRowType.Common, commonType: CommonRowType.UnusedOptions },
];

// 2. Define outstanding SAFEs
const safes = [
  { name: "Seed SAFE", investment: 1_000_000, cap: 10_000_000, discount: 0, conversionType: "post", type: CapTableRowType.Safe },
];

// 3. Solve for share counts at a priced Series A
const conversion = fitConversion(
  12_000_000,   // pre-money valuation
  9_000_000,    // total common shares (founders + issued options)
  safes,
  1_000_000,    // unused options
  0.10,         // target options pool percentage post-round
  [2_000_000],  // series A investment amounts
);

// 4. Build the full cap table
const { common, safes: safeRows, series, refreshedOptionsPool, total } =
  buildPricedRoundCapTable(conversion, [...founders, ...safes, {
    name: "Lead Investor",
    investment: 2_000_000,
    type: CapTableRowType.Series,
    round: 0,
  }]);

Concepts

SAFE Conversion Types

conversionType Description
"pre" Pre-money SAFE — converts on pre-money valuation
"post" Post-money SAFE — converts on post-money (YC standard)
"mfn" MFN (Most Favored Nation) — no cap, gets lowest subsequent cap
"yc7p" YC 7% post-money — guarantees 7% ownership
"ycmfn" YC MFN variant (legacy)

MFN Side Letters

SAFEs with sideLetters: ["mfn"] automatically receive the lowest cap of any subsequent capped SAFE via populateSafeCaps().

Rounding

Share counts and price-per-share (PPS) are rounded to match legal standards. The default strategy floors shares and rounds PPS to 5 decimal places. Override via RoundingStrategy:

const strategy = {
  roundDownShares: true,   // floor shares (default)
  roundPPSPlaces: 5,       // PPS decimal places (default)
};

Ownership Errors

Some rows can't be fully calculated and are flagged rather than crashed:

  • "tbd" — Needs more info (e.g. uncapped SAFE before a priced round)
  • "caveat" — Calculated with assumptions (e.g. MFN cap assigned)
  • "error" — Invalid input (e.g. investment ≥ cap)

API Reference

Cap Table Builders

buildExistingShareholderCapTable(stockholders)

Calculates ownership percentages for existing shareholders with no round.

import { buildExistingShareholderCapTable } from "@1984vc/cap-table";

const rows = buildExistingShareholderCapTable(founders);
// rows[0].ownershipPct === 0.45

buildEstimatedPreRoundCapTable(stakeHolders, roundingStrategy?)

Estimates SAFE ownership before a priced round is known. Marks uncapped SAFEs as "tbd".

const { common, safes, total } = buildEstimatedPreRoundCapTable([
  ...founders,
  ...safes,
]);

Returns { common: CommonCapTableRow[], safes: SafeCapTableRow[], total: TotalCapTableRow }.


buildPreRoundCapTable(pricedConversion, stakeHolders)

Builds the pre-round view using a solved BestFit from fitConversion().

const { common, safes, total } = buildPreRoundCapTable(conversion, stakeHolders);

buildPricedRoundCapTable(pricedConversion, stakeHolders)

Builds the full cap table including the new priced round and refreshed options pool.

const { common, safes, series, refreshedOptionsPool, total, error } =
  buildPricedRoundCapTable(conversion, stakeHolders);

Conversion Solver

fitConversion(preMoneyValuation, commonShares, safes, unusedOptions, targetOptionsPct, seriesInvestments, roundingStrategy?)

Iteratively solves for share counts at a priced round, accounting for SAFE conversions and option pool refresh.

const conversion = fitConversion(
  12_000_000,   // pre-money valuation
  9_000_000,    // common shares outstanding (excluding unused options)
  safes,        // SAFENote[]
  1_000_000,    // unused options
  0.10,         // target option pool % post-round
  [2_000_000],  // one entry per series investor (or total)
);

Returns a BestFit object:

{
  pps: number               // series round price per share
  ppss: number[]            // per-SAFE conversion price
  convertedSafeShares: number
  seriesShares: number
  preMoneyShares: number    // fully diluted pre-money share count
  postMoneyShares: number
  newSharesIssued: number
  totalShares: number
  additionalOptions: number // new options beyond existing pool
  totalOptions: number
  totalInvested: number
  totalSeriesInvestment: number
  roundingStrategy: RoundingStrategy
}

SAFE Utilities

populateSafeCaps(safeNotes)

Applies MFN logic — assigns each uncapped MFN SAFE the lowest cap from subsequent capped SAFEs.

const processedSafes = populateSafeCaps(rawSafes);

safeConvert(safe, preShares, postShares, pps)

Returns the effective conversion price per share for a single SAFE.

checkSafeNotesForErrors(safeNotes)

Validates SAFE inputs. Returns a CapTableOwnershipError or undefined.


Number Formatting

import { stringToNumber, formatUSDWithCommas, shortenedUSD } from "@1984vc/cap-table";

stringToNumber("$1.5M")          // → 1_500_000
stringToNumber("1,000,000")      // → 1_000_000
formatUSDWithCommas(1_234_567)   // → "$1,234,567"
shortenedUSD(1_500_000)          // → "$1.5M"
shortenedUSD(50_000)             // → "$50K"

Type Reference

Input Types

type CommonStockholder = {
  id?: string;
  name: string;
  shares: number;
  type: CapTableRowType.Common;
  commonType: CommonRowType.Shareholder | CommonRowType.UnusedOptions;
};

type SAFENote = {
  id?: string;
  name?: string;
  investment: number;
  cap: number;
  discount: number;
  type: CapTableRowType.Safe;
  conversionType: "pre" | "post" | "mfn" | "yc7p" | "ycmfn";
  sideLetters?: ("mfn" | "pro-rata")[];
};

type SeriesInvestor = {
  id?: string;
  name?: string;
  investment: number;
  type: CapTableRowType.Series;
  round: number; // 0-indexed round number
};

type StakeHolder = CommonStockholder | SAFENote | SeriesInvestor;

Enums

enum CapTableRowType {
  Common = "common",
  Safe = "safe",
  Series = "series",
  Total = "total",
  RefreshedOptions = "refreshedOptions",
}

enum CommonRowType {
  Shareholder = "shareholder",
  UnusedOptions = "unusedOptions",
}

Development

npm install
npm run build      # tsup → dist/
npm test           # vitest

License

MIT — 1984 Ventures

About

Open Source Captable Worksheet

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors