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)
| Parameter | Requirement | Meaning |
|---|---|---|
| ledger | Required | Canton LedgerJsonApiClient from new Canton(...). Required for all reads and writes. |
| validator | Optional | Canton ValidatorApiClient. Optional when you only use Open Cap Table Format APIs. |
| factory | Optional | { 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 theOcpClienttable insrc/OcpClient.ts. issuer.buildCreate— emitCreateCapTablepayloads that must ride onocp.createBatch(addBuiltCommand).issuerAuthorization.authorize/withdraw— authorize an issuer party through the pinned OCP Factory or withdraw from an IssuerAuthorization contract.capTable—classify/getState(pinned-template queries only),update(fluentCapTableBatch),archive(system-operator teardown after empty maps). Package-level helpers (archiveFullCapTable,getSystemOperatorPartyId,buildUpdateCapTableCommand) live on the@open-captable-protocol/cantonroot export—not nested underOcpClient.
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:
| Error | Typical trigger |
|---|---|
OcpValidationError | Missing actAs, empty CapTableBatch, malformed schema inputs, context.require* misses. Codes include REQUIRED_FIELD_MISSING and INVALID_TYPE. |
OcpContractError | Ledger 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 matchesissuerPartydisclosed onIssuerAuthorization.AuthorizeIssuer: exercised through OCP Factory with appropriate factory admin rights per deployment.WithdrawAuthorization,ArchiveCapTable,IssuerAuthorizationadmin ops: signer issystem_operator(seewithdraw,capTable.archive) per DAML privileges.readAson 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).
Related pages
Source
src/OcpClient.tssrc/OcpClient.ts— OpenCapTableMethods typings (mirror ofOpenCapTablesurface)