Shared Canton LocalNet CLI, DAML package tooling, and integration-test helpers for Fairmint packages.
This package owns the versioned LocalNet lifecycle (start, readiness, diagnostics, teardown), reusable TypeScript helpers used by Canton integration tests, and shared DAML multi-package scripts (prepare-build, DAR policy, Splice DAR sync). Introduced by ENG-1635.
Canonical documentation lives in this repository:
- LocalNet — CLI, prerequisites, consumer setup, and ready endpoints
- COMPATIBILITY.md — pinned Quickstart / Splice / scribe / protocol versions and auth defaults
- DAML package CLI — multi-package build, DAR policy, and Splice DAR sync
- TypeScript helpers —
@fairmint/canton-dev-tools/testing
Implementation: bin/canton-dev-tools and scripts/localnet-cloud.sh. CI smoke: .github/workflows/localnet-smoke.yml.
Fairmint-internal cross-repo CI notes (non-canonical): dev-docs canton-localnet.
npm install -D @fairmint/canton-dev-toolsPeer dependency: @fairmint/canton-node-sdk (for TypeScript helpers).
@fairmint/canton-dev-tools owns Fairmint's shared Canton LocalNet lifecycle. The CLI wraps Canton Network Quickstart with pinned versions from COMPATIBILITY.md.
- Docker and Compose v2
- First start pulls images and bootstraps Splice (~10–15 minutes on a cold cache)
- Host aliases
scan.localhost,sv.localhost, andwallet.localhost(the CLI adds them with passwordlesssudowhen available; otherwise add manually)
In npm consumer packages the CLI fetches cn-quickstart at the pinned ref into ~/.cache/fairmint/canton-localnet (override with CANTON_LOCALNET_CACHE_DIR / CANTON_LOCALNET_QUICKSTART_DIR). In a git checkout with libs/cn-quickstart, submodule init is used instead.
npx canton-dev-tools start
npx canton-dev-tools readiness
npx canton-dev-tools diagnostics
npx canton-dev-tools teardownAuth defaults to oauth2 (Keycloak). Consumer CI that signs HS256 JWTs should set CANTON_LOCALNET_AUTH_MODE=shared-secret (see COMPATIBILITY.md).
npm run localnet:start
npm run localnet:readiness
npm run localnet:diagnostics
npm run localnet:teardown
npm run localnet:cip56-transfer # Splice TestTokenV2 CIP-56 / CIP-112 smokeExpose localnet:* scripts that delegate to the binary. Shared-secret auth is typical for CI:
{
"scripts": {
"localnet": "CANTON_LOCALNET_AUTH_MODE=shared-secret canton-dev-tools",
"localnet:start": "npm run -s localnet -- start",
"localnet:readiness": "npm run -s localnet -- readiness",
"localnet:teardown": "npm run -s localnet -- teardown"
}
}Prefer Dev Tools pin defaults; only set CANTON_LOCALNET_* overrides for intentional experiments.
After start / readiness:
| Service | URL |
|---|---|
| Ledger JSON API | http://localhost:3975/v2/version |
| Scan | http://scan.localhost:4000/api/scan/v0/dso-party-id |
| Validator | http://localhost:3903/ (200/401) |
See npx canton-dev-tools diagnostics and .github/workflows/localnet-smoke.yml for the CI smoke path.
Run from a DAML multi-package repo root (multi-package.yaml + per-package daml.yaml). Packages are discovered automatically — no repo-local packages.ts required for these commands. Keep consumer-specific PACKAGE_DEFS / generated npm metadata in the consumer when needed.
npx canton-dev-tools install-dpm-sdks
npx canton-dev-tools prepare-build
npx canton-dev-tools verify-dars
npx canton-dev-tools backup-dar --package WrappedAssets-v01 --version 0.0.1
npx canton-dev-tools check-dar-version-policy --all
npx canton-dev-tools check-dar-version-policy --extra-policy-paths scripts/codegen,libs/splice
npx canton-dev-tools check-upgrade-compat
npx canton-dev-tools sync-splice-dars
npx canton-dev-tools codegen-js
npx canton-dev-tools bundle-dependencies
npx canton-dev-tools create-root-index
npx canton-dev-tools fix-splice-refs --target lib
npx canton-dev-tools prepare-release --changelog-repo Fairmint/canton-assetsbackup-dar / version-policy / upgrade-compat skip Test packages by default. Pass --package with the daml.yaml name, source dir, or a fuzzy alias (e.g. wrappedAssets).
Generic DAML → JS bindings steps for packages that declare codegen.js in daml.yaml:
dpm codegen-jsin eachgenerated/build/<pkg>(expectsprepare-buildalready done)- Stamp generated
package.jsonname/version from the repo root - Write per-package
index.js/index.d.ts - Fix Splice namespace refs on generated
lib/trees (optional@fairmint/*→__bundled__rewrite when present)
Config-driven stdlib/Splice bundling and merged published lib/ creation. Driven by
daml-js-bundle.json (or --config / package.json → cantonDevTools.damlJsBundle).
No product package names are hardcoded in canton-dev-tools — consumers select presets and
describe their root index in JSON.
npx canton-dev-tools bundle-dependencies [--root <dir>] [--config <path>]
npx canton-dev-tools create-root-index [--root <dir>] [--config <path>]
npx canton-dev-tools fix-splice-refs --target libBuilt-in presets (stdlib / Splice only):
| Preset id | Bundles |
|---|---|
da-internal-template |
ghc-stdlib-DA-Internal-Template (always applied) |
featured-app-v1 |
splice-api-featured-app-v1 |
featured-app-v2 |
splice-api-featured-app-v2 (only when amulet needs it) |
amulet |
splice-amulet-<pin> |
da-time-types |
daml-stdlib-DA-Time-Types |
da-types |
daml-prim-DA-Types |
da-set-types |
daml-stdlib-DA-Set-Types |
splice-token-v1 |
token burn/mint, metadata, holding, allocation*, transfer-instruction |
splice-token-standard-utils |
splice-token-standard-utils-<pin> |
Pins (optional): pins.amulet (default 0.1.19), pins.tokenStandardUtils (default 2.0.0).
Example daml-js-bundle.json (assets-like shape; product names belong in the consumer config):
{
"generatedJsDir": "generated/js",
"presets": [
"da-internal-template",
"featured-app-v1",
"featured-app-v2",
"amulet",
"da-time-types",
"da-types",
"da-set-types",
"splice-token-v1",
"splice-token-standard-utils"
],
"pins": {
"amulet": "0.1.19",
"tokenStandardUtils": "2.0.0"
},
"rootIndex": {
"outputDir": "lib",
"sourcePackage": { "namePrefix": "WrappedAssets" },
"copy": ["DA", "Splice", "__bundled__", "WrappedAssets"],
"namespaces": ["WrappedAssets", "DA", "Splice"],
"templateConstants": {
"WRAPPED_ASSETS_TEMPLATES": {
"burnMintFactory": {
"from": "./WrappedAssets/BurnMint/module",
"binding": "WrappedAssetsBurnMintFactory"
},
"burnOffer": {
"from": "./WrappedAssets/BurnOffer/module",
"binding": "BurnOffer"
},
"wrappedAsset": {
"from": "./WrappedAssets/Holding/module",
"binding": "WrappedAsset"
},
"frozenWrappedAsset": {
"from": "./WrappedAssets/Holding/module",
"binding": "FrozenWrappedAsset"
}
}
},
"postBundlePresets": [
"da-time-types",
"da-types",
"splice-token-v1",
"splice-token-standard-utils",
"da-set-types"
]
}
}Or point at the file from package.json:
{
"cantonDevTools": {
"damlJsBundle": "./daml-js-bundle.json"
}
}Library imports:
import {
runCodegenJs,
bundleDependencies,
createRootIndex,
fixSpliceRefs,
resolveDamlJsBundleConfig,
BUNDLE_PRESET_IDS,
} from '@fairmint/canton-dev-tools/daml';Example consumer scripts:
{
"scripts": {
"prepare-build": "canton-dev-tools prepare-build",
"codegen": "npm run build && canton-dev-tools codegen-js && canton-dev-tools bundle-dependencies && canton-dev-tools create-root-index && canton-dev-tools fix-splice-refs --target lib",
"prepare-release": "canton-dev-tools prepare-release"
}
}NFT / CapTable merge hooks stay consumer-local (out of scope for Phase 2).
Optional publish suffixes in root package.json (multi-package repos):
{
"cantonDevTools": {
"codegenPublishSuffixes": {
"OpenCapTableReports-v01": "reports",
"WrappedAssets-v01": null
}
}
}null publishes as the root package name. A single codegen package defaults to the root name.
Library imports (Phase 1 helpers):
import {
runCodegenJs,
createPackageIndexes,
updateGeneratedPackagesFromRoot,
fixSpliceRefs,
collapseManifestLines,
verifyPackageImports,
applyGeneratedImportRewrites,
} from '@fairmint/canton-dev-tools/daml';By default, auto-selection only treats package daml.yaml / daml/ sources and dars/<package>/
backups (plus lock-entry diffs) as package input changes. Repos such as OCP also need shared
inputs (scripts/codegen/, libs/splice/) to select packages. Configure extra relative prefixes
with this precedence (first wins):
- CLI
--extra-policy-paths <csv>(repeatable; overrides config entirely, including[]) package.json→cantonDevTools.darVersionPolicyWatchPaths- repo-root
canton-daml-tooling.json→darVersionPolicyWatchPaths [](no extra watches)
{
"cantonDevTools": {
"darVersionPolicyWatchPaths": ["scripts/codegen", "libs/splice"]
}
}{
"darVersionPolicyWatchPaths": ["scripts/codegen/", "libs/splice/"]
}Paths must be relative and contained (no .. / absolute escapes). A change under any configured
prefix selects all managed packages for the policy check.
By default, sync uses the packaged pin at config/default-splice-dars.json (MainNet Splice
0.6.14 / commit 398919a5b13479877fd61587003ba7a4ba00091b, token-standard + amulet DARs).
Optional overrides, in order:
--config <path>CANTON_SPLICE_DARS_CONFIG- repo-root
splice-dars.json(per-repo pin when it must differ from the shared default)
{
"spliceRef": "<git-sha-or-tag>",
"requiredDars": [{ "file": "splice-amulet-0.1.16.dar", "sha256": "..." }],
"syncAdminProtos": true
}SPLICE_REF / SPLICE_REPO override the config values.
import {
prepareBuild,
discoverManagedPackages,
runCodegenJs,
} from '@fairmint/canton-dev-tools/daml';import {
buildLocalnetClientConfig,
getLocalnetParticipantAdminLedgerClient,
findCreatedContractId,
buildTransferFactoryTransferCommand,
SPLICE_TEST_TOKEN_V2_SHA256,
} from '@fairmint/canton-dev-tools/testing';CIP-56 / CIP-112 helpers target the Splice splice-test-token-v2 reference DAR.
Every push/merge to main runs .github/workflows/publish.yml, which auto-publishes @fairmint/canton-dev-tools to npm via OIDC Trusted Publishing.
package.jsonversion is a floor. Intentional minor/major bumps: set a version higher than npm's latest before merging; CI publishes that exact version.- Routine patches auto-increment from the higher of npm latest / existing tags.
npm install
npm run build
npm test
npm run pack:check