Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
d633bc6
Harden cap table batch typing
HardlyDifficult Jul 9, 2026
797f4b9
Discriminate canonical OCF entities
HardlyDifficult Jul 10, 2026
68a5c5c
Validate generated DAML batch operations
HardlyDifficult Jul 10, 2026
5ea3468
Route client reads through validated dispatch
HardlyDifficult Jul 10, 2026
9b0bdd4
Keep stock issuance readback schema valid
HardlyDifficult Jul 10, 2026
a9e68d6
Merge discriminated OCF reader updates
HardlyDifficult Jul 10, 2026
1c01ea1
Keep declaration smoke tests lintable
HardlyDifficult Jul 10, 2026
1d6e182
Merge declaration lint fix
HardlyDifficult Jul 10, 2026
51f7778
Validate OCF date boundaries
HardlyDifficult Jul 10, 2026
b54502c
Reject year zero in OCF dates
HardlyDifficult Jul 10, 2026
e543619
Merge validated batch operation stack
HardlyDifficult Jul 10, 2026
fec5409
Curate strict public SDK declarations
HardlyDifficult Jul 10, 2026
ecf2f6d
Merge validated OCF dates
HardlyDifficult Jul 10, 2026
af64851
Fix canonical stock plan quickstart fixtures
HardlyDifficult Jul 10, 2026
4400af9
Merge commit 'af64851' into codex/validated-daml-batch-operations
HardlyDifficult Jul 10, 2026
6ec8b4b
Merge branch 'codex/validated-daml-batch-operations' into codex/valid…
HardlyDifficult Jul 10, 2026
3957ed4
Merge branch 'codex/validated-ocf-dates' into codex/curated-public-sdk
HardlyDifficult Jul 10, 2026
3b3cde6
Enforce canonical OCF ingestion
HardlyDifficult Jul 10, 2026
8698e3e
Cover every schema-supported PlanSecurity wrapper
HardlyDifficult Jul 10, 2026
365067b
Reject non-object normalization input cleanly
HardlyDifficult Jul 10, 2026
bfc9f56
Limit RFC3339 fractional seconds
HardlyDifficult Jul 10, 2026
9e80fac
Route authorization response through public types
HardlyDifficult Jul 10, 2026
de68023
Keep authorization boundary test focused
HardlyDifficult Jul 10, 2026
6898be8
Merge remote-tracking branch 'origin/main' into codex/discriminated-o…
HardlyDifficult Jul 10, 2026
c18f192
Format restacked type contracts
HardlyDifficult Jul 10, 2026
fb41fb5
Merge remote-tracking branch 'origin/codex/discriminated-ocf-inputs' …
HardlyDifficult Jul 10, 2026
dd8c212
Merge remote-tracking branch 'origin/codex/validated-daml-batch-opera…
HardlyDifficult Jul 10, 2026
cbb254d
Merge remote-tracking branch 'origin/codex/validated-ocf-dates' into …
HardlyDifficult Jul 10, 2026
435e805
Auto-fix: build changes
github-actions[bot] Jul 10, 2026
2919049
docs: clarify entity guard export
HardlyDifficult Jul 10, 2026
cf8877e
fix: harden curated package exports
HardlyDifficult Jul 10, 2026
d7ef4f1
Align date conversion types with validation
HardlyDifficult Jul 10, 2026
bbf7d95
Merge remote-tracking branch 'origin/codex/validated-ocf-dates' into …
HardlyDifficult Jul 10, 2026
a21d3fa
Validate issuance approval dates on read
HardlyDifficult Jul 10, 2026
cf98eb6
Validate optional trigger dates on read
HardlyDifficult Jul 10, 2026
846945f
Merge remote-tracking branch 'origin/codex/validated-ocf-dates' into …
HardlyDifficult Jul 10, 2026
ea9dac1
Validate present optional dates
HardlyDifficult Jul 10, 2026
6c55274
Merge remote-tracking branch 'origin/codex/validated-ocf-dates' into …
HardlyDifficult Jul 10, 2026
de8b391
Report convertible issuance date context
HardlyDifficult Jul 10, 2026
baab60d
Enforce contextual OCF date boundaries
HardlyDifficult Jul 10, 2026
10f05f0
Merge remote-tracking branch 'origin/codex/validated-ocf-dates' into …
HardlyDifficult Jul 10, 2026
a0a5398
Enforce OCF trigger discriminators
HardlyDifficult Jul 10, 2026
6233888
Merge remote-tracking branch 'origin/codex/validated-ocf-dates' into …
HardlyDifficult Jul 10, 2026
e9dc120
fix: address typed OCF review findings
HardlyDifficult Jul 10, 2026
0cc6a29
fix: keep operation converter internal
HardlyDifficult Jul 10, 2026
960216f
Merge commit '0cc6a29940f810eeea8b25789cf80d4fab14cb9d' into codex/va…
HardlyDifficult Jul 10, 2026
cfb1e7a
Merge validated batch fixes into date boundaries
HardlyDifficult Jul 10, 2026
96ce3cf
Merge validated date boundaries into curated public API
HardlyDifficult Jul 10, 2026
be84650
fix: validate ledger read payloads
HardlyDifficult Jul 11, 2026
5a6cf6c
Merge commit 'be846500fe3ebb04426d5431861e8957676cf7a0' into codex/va…
HardlyDifficult Jul 11, 2026
016ed34
Merge commit '5a6cf6cef4efe3f1294f5a9fc7cc06976f09f906' into codex/va…
HardlyDifficult Jul 11, 2026
dcb7e31
Merge commit '016ed3489892747d160d8d6bf0521d4ead3a0833' into codex/cu…
HardlyDifficult Jul 11, 2026
16a3232
fix: index nested date validation paths
HardlyDifficult Jul 11, 2026
5453fea
fix: address exact date review feedback
HardlyDifficult Jul 11, 2026
2487c1c
Merge branch 'codex/validated-ocf-dates' into codex/curated-public-sdk
HardlyDifficult Jul 11, 2026
a41fb16
fix: freeze public entity mapping
HardlyDifficult Jul 11, 2026
d70c951
refactor: share batch operation decoding
HardlyDifficult Jul 11, 2026
6e8b7f6
fix: enforce exact trigger and date boundaries
HardlyDifficult Jul 11, 2026
aa523cc
Merge latest validated batch operations into date boundaries
HardlyDifficult Jul 11, 2026
7e2a757
fix: make stock class conversion rights schema exact
HardlyDifficult Jul 11, 2026
6f396ca
fix: reject lossy stock class conversion reads
HardlyDifficult Jul 11, 2026
9c9b388
Merge PR #411 into curated public API
HardlyDifficult Jul 11, 2026
02bb2a1
fix: address exact-head review feedback
HardlyDifficult Jul 11, 2026
f094c35
fix: preserve vesting amount diagnostic indexes
HardlyDifficult Jul 11, 2026
7507f8a
Merge latest validated date hardening into curated public API
HardlyDifficult Jul 11, 2026
d9c6080
fix: isolate malformed manifest transactions
HardlyDifficult Jul 11, 2026
351c666
refactor: centralize generated operation building
HardlyDifficult Jul 11, 2026
8780203
Merge branch 'codex/validated-ocf-dates' into codex/curated-public-sdk
HardlyDifficult Jul 11, 2026
c267e67
refactor: correlate generated operation builders
HardlyDifficult Jul 11, 2026
306ca74
fix: validate vesting element shapes
HardlyDifficult Jul 11, 2026
2e2c65e
Merge commit '306ca74277fab09107e969e4faa435e067cb7128' into codex/cu…
HardlyDifficult Jul 11, 2026
de8198c
fix: validate warrant vesting shapes
HardlyDifficult Jul 11, 2026
ac336cc
Merge commit 'de8198cbc21d2ad6d1d5d61f1ba2061f57d4c09c' into codex/cu…
HardlyDifficult Jul 11, 2026
a37e1f5
fix: harden nested DAML collections
HardlyDifficult Jul 11, 2026
601af5e
Merge commit 'a37e1f58f47ed0349d45a03d61ddebc52a4cc74c' into codex/cu…
HardlyDifficult Jul 11, 2026
9d459b2
fix: validate nested write records
HardlyDifficult Jul 11, 2026
f625fbb
Merge commit '9d459b27f3894265e49bb90f9ddc945d8e18bcc3' into codex/cu…
HardlyDifficult Jul 11, 2026
05ad2a3
refactor: curate public sdk declarations
HardlyDifficult Jul 13, 2026
a28dfb8
refactor: realign public SDK scope with DAML boundary
HardlyDifficult Jul 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@
"url": "https://github.com/Fairmint/ocp-canton-sdk.git"
},
"license": "GPL-3.0-only",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.js",
"default": "./dist/index.js"
},
"./package.json": "./package.json"
},
Comment thread
coderabbitai[bot] marked this conversation as resolved.
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
Expand Down
43 changes: 36 additions & 7 deletions scripts/check-declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import ts from 'typescript';
const projectRoot = process.cwd();
const configPath = path.join(projectRoot, 'tsconfig.json');
const declarationEntryPoint = path.join(projectRoot, 'dist', 'index.d.ts');
const strictConsumerEntryPoint = path.join(projectRoot, 'test', 'declarations', 'publicApi.types.ts');
const declarationRoot = `${path.dirname(declarationEntryPoint)}${path.sep}`;
const generatedDamlPackage = '@fairmint/open-captable-protocol-daml-js';
const cantonTransactionTreeOperationsModule = '@fairmint/canton-node-sdk/build/src/clients/ledger-json-api/operations';
const commonTypesDeclaration = path.join(declarationRoot, 'types', 'common.d.ts');
const diagnosticHost: ts.FormatDiagnosticsHost = {
getCanonicalFileName: (fileName) => fileName,
getCurrentDirectory: () => projectRoot,
Expand All @@ -30,16 +34,41 @@ if (parsedConfig.errors.length > 0) {
}

const program = ts.createProgram({
rootNames: [declarationEntryPoint],
options: parsedConfig.options,
rootNames: [declarationEntryPoint, strictConsumerEntryPoint],
options: { ...parsedConfig.options, rootDir: projectRoot },
});

const sdkDiagnostics = ts
.getPreEmitDiagnostics(program)
.filter((diagnostic) => diagnostic.file?.fileName.startsWith(declarationRoot));
const diagnostics = ts.getPreEmitDiagnostics(program);

if (sdkDiagnostics.length > 0) {
if (diagnostics.length > 0) {
throw new Error(
`SDK declaration validation failed:\n${ts.formatDiagnosticsWithColorAndContext(sdkDiagnostics, diagnosticHost)}`
`Strict consumer declaration validation failed:\n${ts.formatDiagnosticsWithColorAndContext(diagnostics, diagnosticHost)}`
);
}

const generatedDamlLeaks = program
.getSourceFiles()
.filter((sourceFile) => sourceFile.fileName.startsWith(declarationRoot))
.filter((sourceFile) => sourceFile.text.includes(generatedDamlPackage))
.map((sourceFile) => path.relative(projectRoot, sourceFile.fileName));

if (generatedDamlLeaks.length > 0) {
throw new Error(
`Public declaration graph references ${generatedDamlPackage}:\n${generatedDamlLeaks.map((file) => `- ${file}`).join('\n')}`
);
}

const duplicatedTransactionTreeResponseImports = program
.getSourceFiles()
.filter((sourceFile) => sourceFile.fileName.startsWith(declarationRoot))
.filter((sourceFile) => sourceFile.fileName !== commonTypesDeclaration)
.filter((sourceFile) => sourceFile.text.includes(cantonTransactionTreeOperationsModule))
.map((sourceFile) => path.relative(projectRoot, sourceFile.fileName));

if (duplicatedTransactionTreeResponseImports.length > 0) {
throw new Error(
`Public declarations must import transaction-tree response types through src/types/common:\n${duplicatedTransactionTreeResponseImports
.map((file) => `- ${file}`)
.join('\n')}`
);
}
59 changes: 24 additions & 35 deletions src/OcpClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,35 +68,38 @@ import {
type OcpEnvironment,
} from './environment';
import { OcpErrorCodes, OcpValidationError } from './errors';
import {
authorizeIssuer,
buildCreateIssuerCommand,
getEntityAsOcf,
withdrawAuthorization,
type AuthorizeIssuerParams,
type AuthorizeIssuerResult,
type CreateIssuerParams,
type WithdrawAuthorizationParams,
type WithdrawAuthorizationResult,
} from './functions';
import {
archiveCapTable,
CapTableBatch,
classifyIssuerCapTables,
getCapTableState,
mapOcfObjectTypeToEntityType,
type ArchiveCapTableParams,
type ArchiveCapTableResult,
type CapTableState,
type IssuerCapTableClassification,
} from './functions/OpenCapTable/capTable/archiveCapTable';
import { CapTableBatch, type CapTableBatchParams } from './functions/OpenCapTable/capTable/CapTableBatch';
import { getEntityAsOcf } from './functions/OpenCapTable/capTable/damlToOcf';
import {
mapOcfObjectTypeToEntityType,
type OcfDataTypeFor,
type OcfEntityType,
type OcfReadableDataForObjectType,
type OcfReadableObjectType,
} from './functions/OpenCapTable/capTable';
} from './functions/OpenCapTable/capTable/entityTypes';
import {
classifyIssuerCapTables,
getCapTableState,
type CapTableState,
type IssuerCapTableClassification,
} from './functions/OpenCapTable/capTable/getCapTableState';
import { buildCreateIssuerCommand } from './functions/OpenCapTable/issuer/api';
import type { CreateIssuerParams } from './functions/OpenCapTable/issuer/types';
import { authorizeIssuer } from './functions/OpenCapTable/issuerAuthorization/authorizeIssuer';
import type {
AuthorizeIssuerParams,
AuthorizeIssuerResult,
WithdrawAuthorizationParams,
WithdrawAuthorizationResult,
} from './functions/OpenCapTable/issuerAuthorization/types';
import { withdrawAuthorization } from './functions/OpenCapTable/issuerAuthorization/withdrawAuthorization';
import { mergeCommandContext, type CommandObservabilityOptions, type OcpObservabilityOptions } from './observability';
import type { CommandWithDisclosedContracts } from './types';
import type { ContractResult, GetByContractIdParams } from './types/common';
import type { CommandWithDisclosedContracts, ContractResult, GetByContractIdParams } from './types/common';
import type {
OcfConvertibleAcceptanceOutput,
OcfConvertibleCancellationOutput,
Expand Down Expand Up @@ -827,21 +830,7 @@ export interface GetByObjectTypeParams<
}

/** Parameters for creating a batch cap table update */
interface CapTableUpdateParams extends CommandObservabilityOptions {
/** The contract ID of the CapTable to update */
capTableContractId: string;
/** Optional contract details for the CapTable (used to get correct templateId from ledger) */
capTableContractDetails?: { templateId: string };
/**
* Optional deterministic command ID for idempotent retry handling.
* Takes precedence over `defaultContext.commandId` and `context.commandId`.
*/
commandId?: string;
/** Party IDs to act as (signatories) */
actAs: string[];
/** Optional additional party IDs for read access */
readAs?: string[];
}
type CapTableUpdateParams = CapTableBatchParams;

/** Entity namespace with a single `get()` method */
interface EntityReader<T> {
Expand Down
127 changes: 11 additions & 116 deletions src/functions/OpenCapTable/capTable/CapTableBatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,38 +7,35 @@

import type { LedgerJsonApiClient } from '@fairmint/canton-node-sdk/build/src/clients/ledger-json-api';
import type { Command } from '@fairmint/canton-node-sdk/build/src/clients/ledger-json-api/schemas/api/commands';
import { Fairmint, OCP_TEMPLATES } from '@fairmint/open-captable-protocol-daml-js';
import { OCP_TEMPLATES } from '@fairmint/open-captable-protocol-daml-js';
import { OcpContractError, OcpErrorCodes, OcpValidationError } from '../../../errors';
import {
mergeCommandContext,
submitObservedTransactionTree,
type CommandObservabilityOptions,
} from '../../../observability';
import type { CommandWithDisclosedContracts } from '../../../types';
import type { CommandWithDisclosedContracts } from '../../../types/common';
import { type OcfCreateData, type OcfDeleteData, type OcfEditData, type UpdateCapTableResult } from './batchTypes';
import {
Comment on lines +17 to 19
ENTITY_TAG_MAP,
isOcfCreatableEntityType,
isOcfDeletableEntityType,
isOcfEditableEntityType,
type CapTableBatchExecuteResult,
type CapTableBatchOperations,
type OcfCreateArguments,
type OcfCreateData,
type OcfCreateDataFor,
type OcfCreateOperation,
type OcfDataTypeFor,
type OcfDeletableEntityType,
type OcfDeleteData,
type OcfDeleteDataFor,
type OcfDeleteOperation,
type OcfEditArguments,
type OcfEditData,
type OcfEditDataFor,
type OcfEditOperation,
type OcfEntityType,
type UpdateCapTableResult,
} from './batchTypes';
import { convertOperationToDaml, convertToDaml } from './ocfToDaml';
} from './entityTypes';
import {
buildOcfCreateData,
buildOcfCreateDataFromOperation,
buildOcfDeleteData,
buildOcfEditData,
buildOcfEditDataFromOperation,
} from './generatedBatchOperations';

/** Parameters for initializing a batch update. */
export interface CapTableBatchParams extends CommandObservabilityOptions {
Expand Down Expand Up @@ -71,108 +68,6 @@ export interface BatchItemMeta {
securityId?: string;
}

function decodeGeneratedOperation<T>(
decoder: { runWithException: (input: unknown) => T },
input: unknown,
operation: 'create' | 'edit' | 'delete',
entityType: OcfEntityType
): T {
try {
return decoder.runWithException(input);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
throw new OcpValidationError(
`batch.${operation}.${entityType}`,
`Converter output does not match the generated DAML ${operation} variant: ${message}`,
{
code: OcpErrorCodes.INVALID_FORMAT,
context: { operation, entityType },
}
);
}
}

function buildOcfCreateDataWith(type: OcfEntityType, convert: () => unknown): OcfCreateData {
if (!isOcfCreatableEntityType(type)) {
throw new OcpValidationError('type', `Create operation not supported for entity type: ${String(type)}`, {
code: OcpErrorCodes.INVALID_TYPE,
});
}

const tag = ENTITY_TAG_MAP[type].create;
return decodeGeneratedOperation(
Fairmint.OpenCapTable.CapTable.OcfCreateData.decoder,
{ tag, value: convert() },
'create',
type
);
}

/** Build and validate one generated DAML create variant from a correlated entity-kind/data pair. */
export function buildOcfCreateData<const Arguments extends OcfCreateArguments>(
...args: Arguments
): OcfCreateDataFor<Arguments[0]>;
export function buildOcfCreateData(...args: OcfCreateArguments): OcfCreateData {
const [type] = args;
return buildOcfCreateDataWith(type, () => convertToDaml(...args));
}

function buildOcfCreateDataFromOperation(operation: OcfCreateOperation): OcfCreateData {
const { type } = operation;
return buildOcfCreateDataWith(type, () => convertOperationToDaml(operation));
}

function buildOcfEditDataWith(type: OcfEntityType, convert: () => unknown): OcfEditData {
if (!isOcfEditableEntityType(type)) {
throw new OcpValidationError('type', `Edit operation not supported for entity type: ${String(type)}`, {
code: OcpErrorCodes.INVALID_TYPE,
});
}

const tag = ENTITY_TAG_MAP[type].edit;
return decodeGeneratedOperation(
Fairmint.OpenCapTable.CapTable.OcfEditData.decoder,
{ tag, value: convert() },
'edit',
type
);
}

/** Build and validate one generated DAML edit variant from a correlated entity-kind/data pair. */
export function buildOcfEditData<const Arguments extends OcfEditArguments>(
...args: Arguments
): OcfEditDataFor<Arguments[0]>;
export function buildOcfEditData(...args: OcfEditArguments): OcfEditData {
const [type] = args;
return buildOcfEditDataWith(type, () => convertToDaml(...args));
}

function buildOcfEditDataFromOperation(operation: OcfEditOperation): OcfEditData {
const { type } = operation;
return buildOcfEditDataWith(type, () => convertOperationToDaml(operation));
}

/** Build and validate one generated DAML delete variant. */
export function buildOcfDeleteData<const EntityType extends OcfDeletableEntityType>(
type: EntityType,
id: string
): OcfDeleteDataFor<EntityType>;
export function buildOcfDeleteData(type: OcfDeletableEntityType, id: string): OcfDeleteData {
if (!isOcfDeletableEntityType(type)) {
throw new OcpValidationError('type', `Delete operation not supported for entity type: ${String(type)}`, {
code: OcpErrorCodes.INVALID_TYPE,
});
}

const tag = ENTITY_TAG_MAP[type].delete;
return decodeGeneratedOperation(
Fairmint.OpenCapTable.CapTable.OcfDeleteData.decoder,
{ tag, value: id },
'delete',
type
);
}

/**
* Fluent batch builder for cap table updates.
*
Expand Down
2 changes: 1 addition & 1 deletion src/functions/OpenCapTable/capTable/archiveCapTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import type { LedgerJsonApiClient } from '@fairmint/canton-node-sdk/build/src/clients/ledger-json-api';
import { OcpErrorCodes, OcpValidationError } from '../../../errors';
import { submitObservedTransactionTree, type CommandObservabilityOptions } from '../../../observability';
import type { CommandWithDisclosedContracts } from '../../../types';
import type { CommandWithDisclosedContracts } from '../../../types/common';
import { buildCapTableCommand } from './buildCapTableCommand';

/** Parameters for archiving a CapTable contract. */
Expand Down
2 changes: 1 addition & 1 deletion src/functions/OpenCapTable/capTable/archiveFullCapTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import type { LedgerJsonApiClient } from '@fairmint/canton-node-sdk/build/src/cl
import { OcpErrorCodes } from '../../../errors/codes';
import { OcpContractError } from '../../../errors/OcpContractError';
import { archiveCapTable } from './archiveCapTable';
import type { OcfEntityType } from './batchTypes';
import { CapTableBatch } from './CapTableBatch';
import type { OcfEntityType } from './entityTypes';
import { classifyIssuerCapTables, type CapTableWithArchiveContext } from './getCapTableState';

/** Minimal contract state needed for the archive operation. */
Expand Down
Loading