-
Notifications
You must be signed in to change notification settings - Fork 0
Home
DAML implementation of the Open Cap Format (OCF) standard on Canton Network.
This repository contains the OpenCapTable DAML package and npm bindings. Release automation uses package-scoped tags such as OpenCapTable-v35-v0.0.1; see Package-Tag-Release-Process.
| Layer | Package / version | Notes |
|---|---|---|
| Source |
OpenCapTable-v35 (0.0.2, SDK 3.5.1) |
Current repo; 0.0.2 enforces conversion mechanism validation at issuance (#233; see Release-2026-06-22-ocp-v35-conversion-mechanism-validation) |
| On-chain |
OpenCapTable-v34 (0.0.1) |
Still deployed on devnet + mainnet per dars/dars.lock until v35 upload |
| npm |
@fairmint/open-captable-protocol-daml-js 0.3.5
|
JS bindings from v35 source; separate from on-chain package id |
if [ -d wiki/.git ]; then git -C wiki pull; else git clone https://github.com/fairmint/open-captable-protocol-daml.wiki.git wiki; finpm run build && npm run test # Verify changes before commit
npm run codegen # OCP JS bindings + merged lib/ (see NPM section)
npm run verify-package # After codegen: same checks as CI (merged lib + import tests)
# Deployment (after build/test pass)
npm run upload-dar -- --package ocp --network devnet
npm run upload-dar -- --package ocp --network mainnetThis repo uses dpm (Digital Asset Package Manager) for DAML builds. The daml CLI is deprecated.
Key points:
-
dpm buildautomatically handles multi-package dependencies viamulti-package.yaml - Running
dpm buildfrom any package directory builds all dependencies first -
dpmis installed to~/.dpm/bin(add to PATH)
Direct dpm commands (if not using npm scripts):
dpm build # Build current package + dependencies
dpm test # Run tests
dpm codegen-js # Generate JavaScript bindings
dpm clean # Clean build artifactsInstallation:
curl https://get.digitalasset.com/install/install.sh | sh
export PATH="$HOME/.dpm/bin:$PATH"| Directory / item | Purpose |
|---|---|
OpenCapTable-v35/ |
Core OCF contracts (source in this repo) |
Test/ |
DAML Script tests; data-dependencies → built OpenCapTable-v35 DAR |
scripts/ |
TypeScript: codegen, deploy, verify, npm runtime |
libs/splice/ |
Git submodule (Splice DARs for OCP data-dependencies) |
dars/ |
OpenCapTable DAR backups and dars.lock (OCP only; see DAR-Backup) |
generated/, lib/
|
Build/codegen output (gitignored where applicable) |
Other Fairmint DAML (Shared, Reports, proof-of-ownership, NFT, CantonPayments, equity certificate, CouponMinter, …) is not in this tree; it lives in fairmint/daml. Consumed on-chain and in TypeScript via @fairmint/daml-js, not from this package’s root export.
All OCF object types are implemented. See ADR-001 for architecture and excluded/deprecated types.
Contributors: Use GitHub Issues for bugs and feature requests.
Before submitting a PR, verify by running:
npm run build # All packages must build
npm run test # All tests must passAfter DAML or codegen pipeline changes, run npm run codegen then npm run verify-package so the merged lib/ tree matches what CI publishes.
| Status | Meaning |
|---|---|
| Proposed | Design under discussion, not yet approved |
| Implemented | Design approved AND supporting code/contracts are implemented |
Skip "Accepted" as an intermediate state—go directly from "Proposed" to "Implemented" when the implementation is complete.
Write tight, concise, easy-to-read DAML code. Fail fast on invalid inputs. Code should be self-documenting—prefer comments that explain intent, schema links, or non-obvious constraints.
-
No empty Text - Validate with
validateOptionalText -
Arrays always present - Use
[]for empty, never omit - No trivial type aliases - Use validators instead
-
Shorthand
..- Use when all remaining record fields are in scope -
Dynamic controllers - Use
actor : Partyas first choice param when controller varies
-
assert*- Includes error message, performs assertion (fail fast) -
validate*- ReturnsBoolfor composability; combine withassertMsgfor context
- The official OCF schema and explicit OCF protocol rules are the only sources of truth for data validity.
- Contract validators must not add Fairmint-specific, inferred, or ADR-authorized requirements beyond OCF.
- See OCF-Validation-Policy for the evidence standard, cross-object reference rules, and review checklist.
- For any schema/contract mismatch incident:
- Fix contract validation to match schema intent.
- Add regression tests for the exact offending field/value.
- Perform a package minor upgrade for the affected DAML package.
- One-line file summary (first line of the file, before
module) module ... where- Imports
-
templateblock - Main object
datarecord (with OCF object block + schema URL on the type) - Subtype/helper
datadefinitions - Validators immediately after types
Skip the one-line summary in test-only modules if it adds no signal.
Non-test modules start with a one-line file summary (before module), then module ... where, then the OCF object title and raw schema URL before imports, then the template and data definitions:
-- OCF convertible cancellation transaction template and data; referenced from the generated CapTable with other OCF records, and used by tests and integrations.
module Fairmint.OpenCapTable.OCF.ConvertibleCancellation where
-- Object - Convertible Cancellation Transaction
-- Object describing a cancellation of a convertible security
-- OCF: https://raw.githubusercontent.com/Open-Cap-Table-Coalition/Open-Cap-Format-OCF/main/schema/objects/transactions/cancellation/ConvertibleCancellation.schema.json
import Fairmint.OpenCapTable.Types.Core (Context)
import ...
template ConvertibleCancellation
with
context: Context
cancellation_data: ConvertibleCancellationOcfData
where
signatory context.issuer, context.system_operator
ensure validateConvertibleCancellationOcfData cancellation_data
data ConvertibleCancellationOcfData = ConvertibleCancellationOcfData
with
...
- Lifecycle order when clear, otherwise alphabetical
- Create-style choices before archive-style
data Example = Example with
id: Text -- ID first
required_field: Text -- Required (alphabetical)
items: [Text] -- Arrays (alphabetical)
optional_field: Optional Text -- Optional (alphabetical)
See canton/docs/developer/adr/001-ocf-captable-on-canton.md for detailed decisions.
Contract diagrams: See Contract-Diagram for visual Mermaid diagrams of the contract hierarchy and flow.
Diagram maintenance: Update the Contract-Diagram wiki page when:
- Adding new OCF object types or transactions
- Changing the contract hierarchy or relationships
- Modifying validation patterns or signatories
-
CapTable as authority — Single
CapTablecontract holds maps of OCF object IDs and handles all create/edit/delete viaUpdateCapTable(see ADR-002-Stateful-Issuer);Issueris a plain OCF object, not a factory - Dual signatories — All contracts require issuer + system operator
- Archive + recreate - No direct mutation; archive old contract, create new one
-
Batch operations - Use
UpdateCapTablechoice for efficient bulk creates/edits/deletes
CapTable.daml is auto-generated. Never edit it directly. Modify the generator instead:
# Files involved
scripts/codegen/generate-captable.ts # TypeScript generator
scripts/codegen/captable-config.yaml # Config: validations, processing tiersCapTable.daml is regenerated as part of npm run build (via scripts/codegen/generate-captable.ts). There is no separate npm script—run a full build from the repo root after changing OCF types or generator config.
captable-config.yaml defines:
- validations - Reference validation rules (e.g., stakeholder_id → stakeholders map)
- tiers - Processing order for batch operations (dependency ordering)
validations:
StockIssuance: [stakeholder_id, stock_class_id]
tiers:
1: [Stakeholder, StockClass, ...] # Create first
2: [Valuation, ...] # Depends on tier 1
3: [StockIssuance, ...] # Depends on tier 1 & 2Sum types in DAML require each constructor to have a single argument:
-- Creates/Edits: Use OCF data type directly (ID is in the data record)
data OcfCreateData = OcfCreateFoo FooOcfData | OcfCreateBar BarOcfData
data OcfEditData = OcfEditFoo FooOcfData | OcfEditBar BarOcfData
-- Deletes: tagged by type (need to know which map to delete from)
data OcfDeleteData = OcfDeleteFoo Text | OcfDeleteBar Text
Cannot exercise self OtherChoice inside a choice body—the contract is already consumed:
-- WRONG: Double consumption error
choice CreateFoo : ContractId CapTable
do
result <- exercise self UpdateCapTable with ... -- ERROR!
pure result.updatedCapTableCid
-- CORRECT: Implement logic directly
choice CreateFoo : ContractId CapTable
do
cid <- create Foo with ...
create this with foos = Map.insert id cid foos
Before adding a type to Types.daml, search the codebase for existing definitions.
# Check if type already exists
grep -r "data OcfYourType" OpenCapTable-v35/daml/-
Major upgrade (breaking change): Creates new package directory (e.g.,
OpenCapTable-v34→OpenCapTable-v35) -
Minor upgrade (non-breaking): Increments patch version (e.g.,
0.0.1→0.0.2)
AI agents: Never perform a major version upgrade without explicit instructions from the user.
For this repo, the versioned OCP package folder is OpenCapTable-vNN (e.g. OpenCapTable-v35). The script matches the base name of that folder (e.g. OpenCapTable → OpenCapTable-v35).
npm run upgrade-package -- --package OpenCapTable --type majorThis renames the folder, resets daml.yaml, and search/replaces references. Other Fairmint packages are upgraded in fairmint/daml, not here.
See Upgrade-Guide for full documentation.
Use package-scoped release tags such as OpenCapTable-v35-v0.0.1. The full automated release workflow, required secrets, and manual checks are documented in Package-Tag-Release-Process.
Document human-facing release notes on the wiki Releases page using the naming convention there.
Other packages: Upload, vetting, and Splice lineages for CantonPayments and related DAML are documented and operated from fairmint/daml — not this repository.
CantonPayments + Splice: If mainnet upload fails with NOT_VALID_UPGRADE_PACKAGE across splice lineages, or you change splice-amulet in daml.yaml, follow the upload and lineage playbooks in fairmint/daml before iterating.
DAR files uploaded to mainnet are backed up in dars/ to preserve the exact bytes. DAML builds are only deterministic with the same compiler version.
# After successful mainnet upload, backup the DAR:
npm run backup-dar -- --package OpenCapTable-v35 --version 0.0.2 --network mainnet
# Verify all backed-up DARs (also runs in CI):
npm run verify-darsSee DAR-Backup for full documentation.
The package @fairmint/open-captable-protocol-daml-js is published to the public npm registry via package-scoped release tags (e.g. OpenCapTable-v35-v0.0.1), not by merging to main alone. See Package-Tag-Release-Process and publishConfig.access in package.json.
Release workflows:
| Workflow | Trigger | What it does |
|---|---|---|
release.yml (canonical) |
Push package-scoped tag or manual workflow_dispatch
|
Build, DAR backup, lint/test, upload DAR (devnet + mainnet), create missing factories, npm publish, commit artifacts to main
|
publish.yml (legacy) |
Every push to main
|
Auto patch bump, prepare-release, npm publish, v$VERSION git tag — npm only; does not upload DARs or create factories |
npm auth: release.yml uses the NPM_TOKEN secret; publish.yml uses npm trusted publishing (GitHub Actions OIDC, no NPM_TOKEN). New releases should use package-scoped tags and release.yml only; publish.yml remains for backward compatibility on main merges.
CI workflows:
| Workflow | Trigger | What it does |
|---|---|---|
ci.yml |
Every push | Build, lint/format (with auto-fix), DAML lint, upgrade-compat check, codegen, verify-package, tests |
check-dars.yml |
Changes under dars/
|
Verifies backed-up DAR integrity (npm run verify-dars) |
The published tarball includes one merged lib/ tree built from OpenCapTable (v35) codegen plus bundled DA and Splice modules required at runtime. The root export exposes Fairmint (including Fairmint.OpenCapTable.*), DA, Splice, and OCP_TEMPLATES — stable aliases for CapTable, IssuerAuthorization, and OcpFactory templateId values (written by scripts/create-root-index.ts; #210 fixed a regression where OCP_TEMPLATES was missing from the root index). It does not export Nft, CantonPayments, or OpenCapTableReports on the root — use @fairmint/daml-js for those packages. Subpath exports include opencaptable.dar, openCapTableDarPath, and ocp-factory-contract-id.json (see root package.json exports).
Other packages: JS bindings for Reports, NFT, CantonPayments, and related Fairmint DAML are published from fairmint/daml (@fairmint/daml-js), not this repo.
OpenCapTable DAR (npm): The package also publishes a stable subpath @fairmint/open-captable-protocol-daml-js/opencaptable.dar, resolved to published-dars/OpenCapTable.dar in the tarball. That file is produced at the end of npm run codegen (which copies the built DAR from OpenCapTable-v35/.daml/dist/ after npm run build). It is gitignored locally; CI and npm publish run codegen so consumers get the DAR matching the published JS.
-
Programmatic path: import
getOpenCapTableDarPath(),resolveOpenCapTableDarPath(),OPEN_CAP_TABLE_DAR_PATH_ENV, andOPEN_CAP_TABLE_DAR_EXPORT_SUBPATHonly from@fairmint/open-captable-protocol-daml-js/openCapTableDarPath(Nodefs; not exposed on the package root so Next.js and other browser bundles stay valid). Returns an absolute path; throws if the staged DAR is missing—e.g. corrupt install or checkout without codegen.OPEN_CAP_TABLE_DAR_EXPORT_SUBPATHis'./opencaptable.dar', matchingpackage.jsonexports["./opencaptable.dar"]for bundlers and tooling. -
Resolver-style:
require.resolve('@fairmint/open-captable-protocol-daml-js/opencaptable.dar')orcreateRequire(import.meta.url).resolve(...)from ESM.
For tests and local tooling, use resolveOpenCapTableDarPath() from the same subpath as above. The merged root lib/index.js intentionally does not re-export these helpers. Source in-repo: scripts/npm-published-lib/openCapTableDarPath.ts.
Resolution order:
-
OPEN_CAP_TABLE_DAR_PATH— if set, must point to an existing.dar(absolute or relative toprocess.cwd()); throws if set but the file is missing. -
Packaged DAR —
getOpenCapTableDarPath()(normal install). -
siblingDarPath— optional path (absolute, or relative tosiblingSearchFrom); ignored unless it resolves to an existing file. RelativesiblingDarPathwithoutsiblingSearchFromthrows. -
Default sibling layout — when
siblingSearchFromis the dependent repo root:{siblingSearchFrom}/../open-captable-protocol-daml/published-dars/OpenCapTable.dar(monorepo dev against a sibling checkout).
If the packaged DAR is missing and steps 3–4 do not yield a file, the thrown Error may set cause to the original packaged-DAR error (Error instances only), with a message that hints at env/sibling options.
CI: npm run verify-package runs verify-merged-lib and test:imports: required lib/ files, no Nft / CantonPayments on the root index, DAR helpers only on the openCapTableDarPath subpath, OCP_TEMPLATES present, and (verify-merged-lib + test:imports) keep fs off the browser-facing root entry.
Equity certificate packages (EquityCertificateShared, EquityCertificate-v01) live in fairmint/daml (#216) and are not part of the @fairmint/open-captable-protocol-daml-js root export — use @fairmint/daml-js.
- Create a PR with your changes.
- Get it reviewed and approved.
- Merge to main after CI passes.
-
Push a package-scoped release tag such as
OpenCapTable-v35-v0.0.1when the DAML package and npm package are ready to publish. -
Let the tag workflow publish after it builds, validates, uploads the DARs, creates missing factories, prepares npm artifacts, publishes npm, and records generated release artifacts back to
main.
Two version numbers are intentional and independent:
-
DAML package version (
OpenCapTable-v35/daml.yaml, e.g.0.0.1) — source of truth for on-chain package identity; the package-scoped release tag suffix must match this.scripts/packages.tsreadsdaml.yamlat build time. -
npm package version (root
package.json, e.g.0.3.x) — tracks the published JS tarball; bump manually before tagging when a new npm publication is expected. Latest published on npm: 0.3.5 (rootpackage.jsonmay lag until the next release bump).
Prefer the tag workflow in Package-Tag-Release-Process. Use local npm publish only for an explicitly approved manual recovery path.
Party assignments are documented in canton/docs/contract-party-configuration.md.
When adding or modifying contract creation scripts (e.g., create-*-factory.ts), update the party configuration doc to reflect:
- Which party operates the contract
- Network-specific party IDs
- Script location and output files
OcpFactory (scripts/create-ocp-factory.ts): submits via Intellect only so system_operator is the Intellect/Catalyst participant (not 5n). upload-dar still targets both Intellect and 5n so either participant can exercise vetted contracts. Factory state is recorded in generated/ocp-factory-contract-id.json (per-network contract IDs plus optional packageName, packageVersion, sourceDir, updatedAt written on factory creation; read by detect-factory-need.ts during tag releases). On-chain factories still reference v34 until v35 is uploaded and factories are redeployed.
| # | Title | Status |
|---|---|---|
| ADR-001 | OCF Cap Table on Canton | Implemented |
| ADR-002-Stateful-Issuer | Stateful Cap Table with OCF Object References | Implemented |
| ADR-003-Featured-App-Markers | Value-Based Coupon Minting for OCP Transactions | Implemented |
ADRs for CouponMinter, CantonPayments, Reports, proof-of-ownership, and other packages now in fairmint/daml are not listed in this public wiki — see ADRs and that repo’s documentation.
See ADRs for the full ADR index.
The main repository and the npm package @fairmint/open-captable-protocol-daml-js are distributed under GNU General Public License v3.0 only (SPDX GPL-3.0-only). See LICENSE in the repo root.
| Repo / package | Purpose | Docs / notes |
|---|---|---|
fairmint/daml |
Additional Fairmint DAML packages (Shared, Reports, NFT, CantonPayments, equity certificate, …) | fairmint/daml — companion after #216 |
@fairmint/daml-js (npm) |
Generated JS for fairmint/daml packages |
Use for Nft, CantonPayments, reports, etc. — not from @fairmint/open-captable-protocol-daml-js root |
canton |
Trading infrastructure, ADRs, party configuration |
AGENTS.md, docs/contract-party-configuration.md
|
canton-explorer |
Next.js explorer UI |
AGENTS.md, cantonops.fairmint.com
|
canton-fairmint-sdk |
Shared TypeScript utilities | AGENTS.md |
canton-node-sdk |
Low-level Canton client |
AGENTS.md, sdk.canton.fairmint.com
|
ocp-canton-sdk |
High-level OCP TypeScript SDK |
AGENTS.md, ocp.canton.fairmint.com
|
ocp-equity-certificate |
SDK/tooling around equity certificates | Equity certificate DAML is in fairmint/daml; see that repo’s wiki for product docs |
Keep this wiki up-to-date. Update it when:
- A best practice or pattern is established
- An architectural or coding decision is made
- New features, endpoints, or patterns are added
- Before creating a PR: review for generalizable learnings
Develop
Decisions
Releases
- Releases (index)
- Package tag release process
- 2026-06-22 — OCP v35 conversion mechanism validation
- 2026-06-22 — OCP v35 SDK scaffold
- 2026-05-04 — OCP v34 major upgrade
- 2026-05-04 — DAML extraction / fairmint-daml
- 2026-03-05 — OCP v32 PPS-based conversion rename
- 2026-02-17 — OCP v31 ArchiveCapTable
- 2026-02-12 — OCP v31 vesting zero portion
Other Fairmint DAML
Documentation for CantonPayments, NFT, Reports, CouponMinter, equity certificate, proof-of-ownership, and related deployment playbooks lives with fairmint/daml and @fairmint/daml-js — not in this wiki.