@fairmint/open-captable-protocol-daml-js
v0.2.133
Published
Open CapTable Protocol DAML smart contracts with generated JavaScript bindings.
Readme
Shared Coding Guidelines for OCP DAML Packages
AI context
- Start here:
AGENTS.md - Docs folder:
docs/README.md(optional; keep minimal)
This repository contains multiple DAML packages (e.g., OpenCapTable-v31,
OpenCapTableReports-v01, OpenCapTableProofOfOwnership-v01, Shared, CantonPayments). This
document defines coding guidelines that apply to all packages.
For package-specific details about each implementation, see the README.md file in the respective
package directory (e.g., open-captable-protocol-daml/OpenCapTable-v31/README.md).
Global exceptions
- We use DAML
Timeinstead of schemaDate(schema excludes time) as a workaround for a JavaScript parsing issue.
Implementation guidance
- Non-empty Text values:
- Never allow empty
Textstrings. ForOptional Text, if provided (Some t), ensuret /= "". - For arrays of
Text, validate each element is non-empty.
- Never allow empty
- Arrays are non-optional: Do not omit array fields. When there are no items, emit an empty
array (
[]) instead of leaving the field out. - Avoid trivial type aliases: Do not create semantic aliases for native types (e.g.,
type OcfMd5 = Text). Prefer native types with validators. Example:
-- MD5
-- OCF: https://raw.githubusercontent.com/Open-Cap-Table-Coalition/Open-Cap-Format-OCF/main/schema/types/Md5.schema.json
validateOcfMd5 : Text -> Bool
validateOcfMd5 md5 =
let n = Text.length md5 in
n == 32 && CryptoText.isHex md5- Shared types and organization:
- Define any type used by more than one template in
Types.daml(with its validator) and import where needed. - Keep template files focused; if a type is only used by a single template, define it in that template file.
- Do not import a template module solely to access a type; move that type to
Types.damlinstead. - Within each record, maintain field ordering for readability:
idfirst- Required scalar fields (alphabetical)
- Arrays (alphabetical)
- Optional fields (alphabetical)
- This ordering applies to all types/records (not only top-level transaction objects). If a record
does not have an
id, skip step 1 and still follow required → arrays → optional with alphabetical ordering within each group. - Required refers to types which are not
Optionalor an array. Keep the groups strict and correct. - Use short section headers and separators to denote these groups exactly as:
-- Required fields (alphabetical)-- ----------------------------------- Arrays (alphabetical)-- ----------------------------------- Optional fields (alphabetical)-- ---------------------------------
- Define any type used by more than one template in
- Declaration order in template files: Place declarations in this order within each template
file: 1)
templateblock first, 2) top-leveldata(the main object record), 3) subtypedata(helper or nested records specific to the template). Keep the two data definitions adjacent. - Validator placement: Define the validator immediately after the corresponding
datatype it validates, in the same file. Example:
data OcfThing = OcfThing with field: Text deriving (Eq, Show)
validateOcfThing : OcfThing -> Bool
validateOcfThing t = t.field /= ""Test helpers: Place test helpers only in the
Testpackage, never in main packages.Choice ordering in templates: When there is no clear logical lifecycle ordering, sort choice declarations alphabetically by choice name. As a convention, place create-style choices before archive-style choices (e.g., put
ArchiveByIssuerlast).
Package-specific guidance
Each package may include additional constraints and domain guidance. Refer to:
open-captable-protocol-daml/OpenCapTable-v31/README.mdfor Open Cap Table specifics (e.g., Issuer management patterns).- Other package READMEs as applicable.
Adding Support for New Packages
When adding a new DAML package to this repository (e.g., NewPackage-v01), follow these steps to
ensure full integration with the build, deployment, and publishing pipeline:
1. Create the Package Directory
Create a new directory at the root level with your package name and version (e.g.,
NewPackage-v01/).
2. Set Up Package Structure
- Create
daml.yamlwith proper configuration - Add your DAML modules under
daml/directory - Create a package-specific
README.mddocumenting the package purpose and usage
3. Update Build Scripts
Update the following files to include your new package:
package.json
- codegen script: Add codegen step for your package in the
codegenscriptcd NewPackage-v01 && daml codegen js && cd .. - upload-dar script: Add new script for uploading DAR files
"upload-dar:newpackage": "npm run build && ts-node scripts/upload-dar-newpackage.ts --network devnet && ts-node scripts/upload-dar-newpackage.ts --network mainnet" - create-factory script: Add new script for creating factory contracts
"create-factory:newpackage": "npm run codegen && ts-node scripts/create-newpackage-factory.ts --network devnet && ts-node scripts/create-newpackage-factory.ts --network mainnet" - upload-and-create script: Add combined script
"upload-and-create:newpackage": "npm run upload-dar:newpackage && npm run create-factory:newpackage" - exports: Add export for factory contract ID JSON
"./newpackage-factory-contract-id.json": { "types": "./generated/newpackage-factory-contract-id.json.d.ts", "default": "./generated/newpackage-factory-contract-id.json" } - files: Add generated files to the npm package
"generated/newpackage-factory-contract-id.json", "generated/newpackage-factory-contract-id.json.d.ts" - typesVersions: Add type definitions for JSON imports
"newpackage-factory-contract-id.json": [ "generated/newpackage-factory-contract-id.json.d.ts" ]
scripts/bundle-dependencies.ts
Add your package directory to the PACKAGE_DIRS array:
const PACKAGE_DIRS = [
path.join(__dirname, '../generated/js/OpenCapTable-v31-0.0.1'),
path.join(__dirname, '../generated/js/OpenCapTableReports-v01-0.0.2'),
path.join(__dirname, '../generated/js/NewPackage-v01-0.0.1'),
];scripts/create-package-index.ts
Add your package directory to the packageDirs array:
const packageDirs = [
path.join(__dirname, '..', 'generated', 'js', 'OpenCapTable-v31-0.0.1'),
path.join(__dirname, '..', 'generated', 'js', 'OpenCapTableReports-v01-0.0.2'),
path.join(__dirname, '..', 'generated', 'js', 'NewPackage-v01-0.0.1'),
];scripts/create-root-index.ts
Add constants for your package directories:
const NEWPACKAGE_DIR = path.join(ROOT_DIR, 'generated', 'js', 'NewPackage-v01-0.0.1'); const NEWPACKAGE_LIB = path.join(NEWPACKAGE_DIR, 'lib');Copy your package namespace in
buildCombinedLib():copyDir( path.join(NEWPACKAGE_LIB, 'Fairmint', 'NewPackage'), path.join(destFairmint, 'NewPackage') );Update Fairmint index files to export your namespace:
// In index.js: var NewPackage = require('./NewPackage'); exports.NewPackage = NewPackage; // In index.d.ts: export * as NewPackage from './NewPackage';Add JSON type definition in
ensureJsonDts():ensureJson( path.join(ROOT_DIR, 'generated', 'newpackage-factory-contract-id.json'), path.join(ROOT_DIR, 'generated', 'newpackage-factory-contract-id.json.d.ts'), `declare const data: {\n devnet: {\n newpackageFactoryContractId: string;\n templateId: string;\n };\n mainnet: {\n newpackageFactoryContractId: string;\n templateId: string;\n };\n};\nexport default data;\n` );
4. Create Deployment Scripts
Create two new scripts in the scripts/ directory:
scripts/upload-dar-newpackage.ts
Upload the DAR file to both devnet and mainnet. Use existing scripts as templates (e.g.,
upload-dar-reports.ts).
Key elements:
- Parse
--networkargument - Upload to both
intellectand5nproviders - Point to correct DAR file path:
NewPackage-v01/.daml/dist/NewPackage-v01-{version}.dar
scripts/create-newpackage-factory.ts
Create factory contract on both networks. Use existing factory scripts as templates.
Key elements:
- Import generated DAML types from
../lib - Create factory contract with appropriate arguments
- Save contract ID to
generated/newpackage-factory-contract-id.json - Include proper error handling and network argument parsing
5. Test the Integration
Build DAML packages:
npm run buildGenerate JavaScript bindings:
npm run codegenVerify generated files exist in
lib/Fairmint/NewPackage/Test deployment scripts:
npm run upload-and-create:newpackage
6. Update Documentation
- Add package to the list in this README's introduction
- Create package-specific README with domain guidance
- Document any new contract templates and their usage
