Skip to content

OCP Canton SDK

Quickstart

Install the SDK, wire OcpClient, and read an issuer as typed Open Cap Format (OCF) data.

Install

npm install \
  @open-captable-protocol/canton \
  @fairmint/canton-node-sdk \
  @fairmint/open-captable-protocol-daml-js

Before you run the example below, have two things ready:

  • A running Canton node (or local sandbox).
  • The issuer party ID (and the issuer contract ID when you want to read).

On-chain contracts (open source)

The ledger templates behind OCP live in open-captable-protocol-daml (wiki). NPM @fairmint/open-captable-protocol-daml-js ships typings built from that repo. See Architecture for how Canton, the contracts, and this SDK fit together.


Minimal example

Every cap table starts with the OCP Factory — a shared on-chain contract that registers your issuer party and grants it permission to create a cap table. The flow is three steps:

  1. Authorize — call the factory with your issuer party. You get back an IssuerAuthorization contract.
  2. Create — exercise that authorization to create the issuer and its cap table together.
  3. Read — fetch the issuer (and everything else) through OcpClient.

On custom networks (localnet, staging): Before you can authorize issuers, you need to deploy a factory. On mainnet and devnet the factory is already deployed — skip this. For everything else, create one first:

import { createFactory } from '@open-captable-protocol/canton';

const factory = await createFactory(canton.ledger, {
  systemOperator: 'YOUR_SYSTEM_OPERATOR_PARTY',
});

const ocp = new OcpClient({
  ledger: canton.ledger,
  factory: { contractId: factory.contractId, templateId: factory.templateId },
});
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 });

// Step 1 — register the issuer party with the OCP Factory
const authorization = await ocp.OpenCapTable.issuerAuthorization.authorize({
  issuer: 'REPLACE_WITH_ISSUER_PARTY',
});

// Step 2 — create the issuer + cap table in one transaction
const { command, disclosedContracts } = ocp.OpenCapTable.issuer.buildCreate({
  issuerAuthorizationContractDetails: authorization,
  issuerParty: 'REPLACE_WITH_ISSUER_PARTY',
  issuerData: {
    id: 'issuer-1',
    legal_name: 'Acme Corp',
    country_of_formation: 'US',
    formation_date: '2024-01-01',
  },
});
await ocp.createBatch({ actAs: ['REPLACE_WITH_ISSUER_PARTY'] })
  .addBuiltCommand({ command, disclosedContracts })
  .submitAndWaitForTransactionTree();

// Step 3 — read it back
const { data: issuer } = await ocp.OpenCapTable.issuer.get({
  contractId: 'REPLACE_WITH_ISSUER_CONTRACT_ID',
});
console.log(issuer.object_type); // ISSUER

Swap each REPLACE_* value for the real IDs from your environment. Once an issuer exists on the ledger you can skip straight to step 3.