-
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.
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 (build runs NFT core invariant check)
npm run codegen # Generate JS bindings after DAML changes (merges packages into lib/)
npm run verify-package # After codegen: import resolution + Node load of merged lib (same as CI)
# 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 | Purpose |
|---|---|
OpenCapTable-v34/ |
Core OCF contracts (current) |
OpenCapTableReports-v01/ |
Reports |
OpenCapTableProofOfOwnership-v01/ |
Proof of ownership |
NftApi-v01/ |
Nft interface + shared NFT types (Nft.Api.V1) |
NftReference-v01/ |
Reference impl: registry, assets, metadata (Nft.Reference.V1) |
EquityCertificateShared/ |
Shared equity certificate types and validation |
EquityCertificate-v01/ |
Certificate + registry DAML (EquityCertificate package) |
Shared/ |
Shared helpers/types |
CantonPayments/ |
Payment streams |
CouponMinter/ |
Coupon minting contracts |
Test/ |
DAML Script tests (depends on packages above) |
scripts/ |
TypeScript deployment scripts |
dars/ |
Backed-up DAR files for mainnet deployments |
All OCF object types are implemented. See ADR-001 for architecture and excluded/deprecated types.
Tasks: Tracked in Linear under the Eng team (including NFT and OCP contract work). See canton/AGENTS.md for Linear workflow.
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
- OCF JSON schema is the source of truth for data validity.
- Contract validators must not be stricter than schema unless an explicit ADR documents the stricter rule.
- 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, NFT integrations, and ownership proofs.
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
- Issuer as factory - Issuer contract creates all other OCF objects
- 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 tiersnpm run codegen:captable # Regenerate CapTable.daml
npm run build # Also runs codegen before buildingcaptable-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 ID type (need to know which map to delete from)
data OcfObjectId = OcfFooId Text | OcfBarId 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-v34/daml/-
Major upgrade (breaking change): Creates new package directory (e.g.,
OpenCapTable-v33→OpenCapTable-v34) -
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.
npm run upgrade-package -- --package OpenCapTable --type majorThis will:
- Rename folder (e.g.,
OpenCapTable-v33/→OpenCapTable-v34/) - Update
daml.yaml(name and version reset to0.0.1) - Search/replace all references across the repo
See Upgrade-Guide for full documentation.
-
Upgrade package:
npm run upgrade-package -- --package <name> --type <major|minor> - Make code changes: Edit DAML files as needed
-
Build & test:
npm run build && npm run test - Document the release on the wiki Releases page (see naming convention there)
-
Deploy:
npm run upload-dar -- --package ocp --network devnet npm run upload-dar -- --package ocp --network mainnet npx tsx scripts/create-ocp-factory.ts --network devnet npx tsx scripts/create-ocp-factory.ts --network mainnet npm run verify-dars
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-v34 --version 0.0.1 --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 automatically published to NPM when changes are merged to main.
The published tarball includes one merged lib/ directory (OCP, reports, CantonPayments, NFT packages, and bundled Splice/DA paths codegen needs). The root export exposes Fairmint, Nft, CantonPayments, DA, and Splice, plus OCP_TEMPLATES: stable aliases for CapTable, IssuerAuthorization, and OcpFactory templateId values (same as the nested Fairmint.OpenCapTable.* modules). Subpath imports use package.json exports (e.g. @fairmint/open-captable-protocol-daml-js/lib/...).
Equity certificate packages (EquityCertificateShared, EquityCertificate-v01) are built and tested in this monorepo but are not merged into that published lib/—npm run codegen only runs JS codegen for OCP, Shared, Reports, NFT API/Reference (then CantonPayments) before bundling.
- Create a PR with your changes
- Get it reviewed and approved
-
Merge to main - CI automatically:
- Builds DAML packages and generates TypeScript bindings
- Runs
npm run verify-package(static import checks plus a Node load oflib/index.jsfor the merged bundle) - Increments the patch version in
package.json - Generates a changelog from commits since last release
- Publishes to NPM
- Creates and pushes a git tag (e.g.,
v0.2.52)
- Patch bumps (0.2.51 → 0.2.52): Automatic on every merge to main
-
Minor/Major bumps: Manually update
package.jsonversion before merging
npm run prepare-release # Bumps version, generates changelog
npm publish # Publishes to NPM (requires NPM_TOKEN)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
| # | 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 |
| ADR-004-CouponMinter | CouponMinter Contract | Implemented |
| ADR-005-Canton-Payments | CantonPayments - Payment Streams and Airdrops | Implemented |
| ADR-006-Reports | OpenCapTableReports Package | Implemented |
| ADR-007-Proof-of-Ownership | Proof of Ownership Contract | Implemented |
See ADRs for the full ADR index.
| Repo | Purpose | Docs |
|---|---|---|
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 |
See also (SDK/tooling); equity certificate DAML lives in this repo — EquityCertificate-Module | AGENTS.md |
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.