Skip to content

Reference

OcpClient — Canton entry point for OCP

One namespaced facade over LedgerJsonApiClient / optional ValidatorApiClient — OpenCapTable reads and batch writes, cached context, and createBatch for composing DAML submissions.

OcpClient is the entry point for @open-captable-protocol/canton. Instantiate it once with your Canton ledger clients, then use OpenCapTable for cap table reads and writes. context caches issuer party and cap table contract id across calls.

Reads through OpenCapTable.*.get return ContractResult<T> { data, contractId }, matching OCF payloads with Canton handles—see the per-entity pages in the sidebar for one page per get. Stateful writes that mutate embedded Open Cap Table Format rows run through CapTableBatch (capTable.update) unless you deliberately emit bare commands (issuer.buildCreate) with createBatch for multi-command Canton submissions.


Minimal setup

import { Canton } from '@fairmint/canton-node-sdk';
import { OcpClient } from '@open-captable-protocol/canton';

const canton = new Canton({ network: 'localnet' });

const ocp = new OcpClient({
  ledger: canton.ledger,
  validator: canton.validator, // optional for strict Open Cap Table Format workloads
});

Dependencies (new OcpClient)

ParameterRequirementMeaning
ledgerRequiredCanton LedgerJsonApiClient from new Canton(...). Required for all reads and writes.
validatorOptionalCanton ValidatorApiClient. Optional when you only use Open Cap Table Format APIs.
factoryOptional{ contractId, templateId } — override the OCP Factory for custom networks (localnet, staging). See createFactory. Omit on mainnet and devnet.

If your transport layer changes (localnet preview vs pinned package line), disclose the pinned template ids explicitly through helper params—see capTable.update({ capTableContractDetails }) and IssuerAuthorization flows below.


Namespace overview

OpenCapTable

  • Readers (get) — hydrate any OCF object or transactional record by contract id. Every namespace key matches the OcpClient table in src/OcpClient.ts.
  • issuer.buildCreate — emit CreateCapTable payloads that must ride on ocp.createBatch (addBuiltCommand).
  • issuerAuthorization.authorize / withdraw — authorize an issuer party through the pinned OCP Factory or withdraw from an IssuerAuthorization contract.
  • capTableclassify / getState (pinned-template queries only), update (fluent CapTableBatch), archive (system-operator teardown after empty maps). Package-level helpers (archiveFullCapTable, getSystemOperatorPartyId, buildUpdateCapTableCommand) live on the @open-captable-protocol/canton root export—not nested under OcpClient.

Cached context (OcpContextManager)

Expose ocp.context accessors:

Set: setIssuerParty, setCapTableContractId, setAll, clear.

Lazy helpers: requireIssuerParty, requireCapTableContractId throw OcpValidationError with REQUIRED_FIELD_MISSING when not set. isReadyForBatchOperations is true when a cap table contract id is cached.

Contexts are process-local—not persisted.


createBatch

Signature:

ocp.createBatch({ actAs: string[]; readAs?: string[] }): TransactionBatch

Dependency contract: Canton Node SDK TransactionBatch expects the ledger you injected earlier. Populate actAs with signer parties (issuer rows for UpdateCapTable, system_operator for WithdrawAuthorization or ArchiveCapTable, etc.). Optionally include readAs when contract resolution spans stakeholders.

Purpose: Glue issuer creation, factory exercises, miscellaneous commands, etc., into atomic submissions that also include manually built payloads:

const built = ocp.OpenCapTable.issuer.buildCreate({
  issuerAuthorizationContractDetails,
  issuerParty: ISSUER_PARTY_ID,
  issuerData: ISSUER_OCF_SHAPE,
});

await ocp.createBatch({ actAs: [ISSUER_PARTY_ID] }).addBuiltCommand(built).submitAndWaitForTransactionTree();

For only Open Cap Table Format row edits, ocp.OpenCapTable.capTable.update(...) remains the ergonomically constrained path (CapTableBatch) issuing a single UpdateCapTable command.


Returns & errors surfaced from the façade

Writes wrap Canton responses with richer errors:

ErrorTypical trigger
OcpValidationErrorMissing actAs, empty CapTableBatch, malformed schema inputs, context.require* misses. Codes include REQUIRED_FIELD_MISSING and INVALID_TYPE.
OcpContractErrorLedger rejects template mismatch, concurrency, or Canton-specific guard rails; CapTableBatch wraps UpdateCapTable failures with CHOICE_FAILED.

Reads may raise OcpParseError or OcpContractError if template ids diverge (SCHEMA_MISMATCH, RESULT_NOT_FOUND).

Always surface cause when inspecting OcpContractError—the SDK attaches batch metadata for UpdateCapTable.


Auth, parties, and disclosure checklist

OcpClient never invents JWT scopes—supply a ledger client backed by Canton credentials that satisfy:

  • UpdateCapTable: signer is issuer.
  • CreateCapTable: exercising party matches issuerParty disclosed on IssuerAuthorization.
  • AuthorizeIssuer: exercised through OCP Factory with appropriate factory admin rights per deployment.
  • WithdrawAuthorization, ArchiveCapTable, IssuerAuthorization admin ops: signer is system_operator (see withdraw, capTable.archive) per DAML privileges.
  • readAs on reads/batches broaden visibility windows for stakeholders when your token cannot otherwise witness contracts.

IssuerAuthorization disclosures MUST stay consistent with Canton network configuration (staging vs pinned package hashes).



Source