Skip to content
Cursor Agent edited this page Apr 8, 2026 · 32 revisions

open-captable-protocol-daml

DAML implementation of the Open Cap Format (OCF) standard on Canton Network.

Clone This Wiki

if [ -d wiki/.git ]; then git -C wiki pull; else git clone https://github.com/fairmint/open-captable-protocol-daml.wiki.git wiki; fi

Quick Commands

npm 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 mainnet

Build Tooling (dpm)

This repo uses dpm (Digital Asset Package Manager) for DAML builds. The daml CLI is deprecated.

Key points:

  • dpm build automatically handles multi-package dependencies via multi-package.yaml
  • Running dpm build from any package directory builds all dependencies first
  • dpm is 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 artifacts

Installation:

curl https://get.digitalasset.com/install/install.sh | sh
export PATH="$HOME/.dpm/bin:$PATH"

Repo Structure

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)
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

Implementation Status

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.

Verifying Changes

Before submitting a PR, verify by running:

npm run build    # All packages must build
npm run test     # All tests must pass

After DAML or codegen pipeline changes, run npm run codegen then npm run verify-package so the merged lib/ tree matches what CI publishes.

ADR Status Values

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.

DAML Coding Standards

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.

Style

  • 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 : Party as first choice param when controller varies

Naming

  • assert* - Includes error message, performs assertion (fail fast)
  • validate* - Returns Bool for composability; combine with assertMsg for context

Schema Alignment Rule (Critical)

  • 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:
    1. Fix contract validation to match schema intent.
    2. Add regression tests for the exact offending field/value.
    3. Perform a package minor upgrade for the affected DAML package.

File Structure

  1. One-line file summary (first line of the file, before module)
  2. module ... where
  3. Imports
  4. template block
  5. Main object data record (with OCF object block + schema URL on the type)
  6. Subtype/helper data definitions
  7. Validators immediately after types

Skip the one-line summary in test-only modules if it adds no signal.

Module summary and OCF link

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
    ...

Choice Ordering

  • Lifecycle order when clear, otherwise alphabetical
  • Create-style choices before archive-style

Field Ordering

data Example = Example with
    id: Text                    -- ID first
    required_field: Text        -- Required (alphabetical)
    items: [Text]               -- Arrays (alphabetical)
    optional_field: Optional Text  -- Optional (alphabetical)

Architecture

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

Key Patterns

  • 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 UpdateCapTable choice for efficient bulk creates/edits/deletes

CapTable Code Generation

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 tiers

Running the Generator

npm run codegen:captable    # Regenerate CapTable.daml
npm run build               # Also runs codegen before building

Config Structure

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 & 2

DAML Sum Types

Sum 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

Choice Wrapping Limitation

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

Adding New Types

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/

Package Upgrades & Releases

Major vs Minor Upgrades

  • Major upgrade (breaking change): Creates new package directory (e.g., OpenCapTable-v33OpenCapTable-v34)
  • Minor upgrade (non-breaking): Increments patch version (e.g., 0.0.10.0.2)

AI agents: Never perform a major version upgrade without explicit instructions from the user.

Upgrade Script

npm run upgrade-package -- --package OpenCapTable --type major

This will:

  1. Rename folder (e.g., OpenCapTable-v33/OpenCapTable-v34/)
  2. Update daml.yaml (name and version reset to 0.0.1)
  3. Search/replace all references across the repo

See Upgrade-Guide for full documentation.

Release Process

  1. Upgrade package: npm run upgrade-package -- --package <name> --type <major|minor>
  2. Make code changes: Edit DAML files as needed
  3. Build & test: npm run build && npm run test
  4. Document the release on the wiki Releases page (see naming convention there)
  5. 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 File Backup System

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-dars

See DAR-Backup for full documentation.

NPM Publishing

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 Nft.Api.V1 and Nft.Reference.V1 alongside OCP modules. Subpath imports use package.json exports (e.g. @fairmint/open-captable-protocol-daml-js/lib/...).

How It Works

  1. Create a PR with your changes
  2. Get it reviewed and approved
  3. Merge to main - CI automatically:
    • Builds DAML packages and generates TypeScript bindings
    • Runs npm run verify-package (static import checks plus a Node load of lib/index.js for 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)

Version Bumping

  • Patch bumps (0.2.51 → 0.2.52): Automatic on every merge to main
  • Minor/Major bumps: Manually update package.json version before merging

Manual Release (Local)

npm run prepare-release   # Bumps version, generates changelog
npm publish               # Publishes to NPM (requires NPM_TOKEN)

Contract Party Configuration

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

Architecture Decision Records

# 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.

Related Repos

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 Soulbound equity certificate smart contracts AGENTS.md

Living Document

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

Clone this wiki locally