Establish @fairmint/canton-dev-tools with LocalNet CLI and test helpers (ENG-1635 PR1) - #1
Conversation
Extract LocalNet lifecycle from canton-node-sdk into @fairmint/canton-dev-tools with product CLI commands, reusable testing exports, artifact guards, and CI.
Hardcode scribe/protocol defaults in bin/canton-dev-tools so npx start matches package.json pins, extend pack/unit pin-parity checks, document oauth2 vs shared-secret auth, add CI-only dar-lifecycle fixture scaffolding, and run LocalNet smoke diagnostics before teardown on failure.
Sparse-fetch splice-test-token-v2 DAR with SHA/package-id asserts, export unit-testable TokenRules/mint/transfer/accept builders for provider-less accounts, add LocalNet integration skeleton and smoke workflow steps.
Central evidence covering node-sdk, fairmint-sdk, ocp-canton-sdk, privy-sdk, and canton-dev-tools publish surfaces.
Wire validate→upload/vet→createParty→TokenRules→OfferMint→Accept→ TransferFactory_Transfer→Accept→holdings assert using Splice splice-test-token-v2 only, plus LocalNet validator client helper.
The bin is a shell script; running it under node broke localnet:start.
Prefer OAuth validator for createParty, use package-name template ids for ACS, and disclose pending offers with readAs so bob can Accept.
Drop docs/sdk-package-boundary-audit.md (references private repos) and README link; note ENG-1635 ownership in COMPATIBILITY.md. Treat Ledger HTTP 401 as reachable in CIP-56 smoke (oauth2 parity).
There was a problem hiding this comment.
Pull request overview
Establishes the canonical LocalNet CLI and testing package supporting the canton-node-sdk migration.
Changes:
- Adds LocalNet lifecycle CLI and centralized compatibility pins.
- Adds reusable CIP-56/CIP-112 testing and transfer-smoke helpers.
- Adds fixture, packaging, unit-test, and CI safeguards.
Reviewed changes
Copilot reviewed 34 out of 39 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
tsconfig.json |
Configures strict TypeScript builds. |
test/unit/testing/helpers.test.ts |
Tests shared helper basics. |
test/unit/testing/cip56.test.ts |
Tests CIP-56 builders and parsing. |
test/unit/scripts/package-artifacts.test.ts |
Tests package exclusions. |
test/unit/scripts/localnet-cloud.test.ts |
Tests LocalNet restart handling. |
test/unit/scripts/canton-dev-tools.test.ts |
Tests CLI pins and aliases. |
test/integration/localnet/cip56-transfer.test.ts |
Adds LocalNet transfer integration smoke. |
src/testing/transactionHelpers.ts |
Adds transaction inspection helpers. |
src/testing/testConfig.ts |
Adds test configuration and retry helpers. |
src/testing/localnetLedgerClients.ts |
Adds role-specific LocalNet clients. |
src/testing/index.ts |
Exports testing APIs. |
src/testing/cip56/transferSmoke.ts |
Implements transfer-smoke orchestration. |
src/testing/cip56/index.ts |
Exports CIP-56 APIs. |
src/testing/cip56/ids.ts |
Defines template and interface IDs. |
src/testing/cip56/fixture.ts |
Defines fixture provenance and validation. |
src/testing/cip56/commands.ts |
Builds ledger commands. |
src/testing/cip56/accounts.ts |
Builds account and context records. |
src/index.ts |
Exposes testing helpers at package root. |
scripts/run-cip56-transfer-smoke.ts |
Runs the transfer smoke. |
scripts/localnet-cloud.sh |
Implements LocalNet lifecycle operations. |
scripts/fetch-splice-test-token-v2.ts |
Fetches and verifies the reference DAR. |
scripts/check-package-artifacts.ts |
Guards the published package surface. |
scripts/build-dar-lifecycle-fixture.sh |
Builds the internal DAR fixture. |
README.md |
Documents installation and usage. |
package.json |
Defines package metadata and commands. |
jest.config.js |
Configures unit tests. |
fixtures/splice-test-token-v2/README.md |
Documents the Splice fixture. |
fixtures/splice-test-token-v2/.gitignore |
Excludes downloaded DARs. |
fixtures/dar-lifecycle/README.md |
Documents lifecycle fixtures. |
fixtures/dar-lifecycle/daml/DarLifecycle/Marker.daml |
Adds a minimal DAML template. |
fixtures/dar-lifecycle/daml.yaml |
Configures the DAML fixture. |
eslint.config.mjs |
Configures linting. |
docs/sdk-package-boundary-audit.md |
Records package-boundary findings. |
COMPATIBILITY.md |
Documents centralized compatibility pins. |
bin/canton-dev-tools |
Adds the distributable CLI wrapper. |
.prettierrc |
Configures formatting. |
.gitignore |
Excludes generated artifacts. |
.github/workflows/localnet-smoke.yml |
Adds scheduled LocalNet smoke CI. |
.github/workflows/ci.yml |
Adds build, test, and packaging CI. |
Suppressed comments (1)
src/testing/cip56/commands.ts:124
- The transfer builder has the same numeric serialization failure: valid small numeric inputs become exponent notation via
String, which the Ledger's DAML Decimal decoder rejects. Restrict amounts to strings or apply a validated fixed-point conversion.
amount: String(params.amount),
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Continue role-check fallback across auth candidates and clear memoized client promises on rejection. Preserve caller LocalNet env overrides via bin defaults, honor FAIRMINT_TEST_* URL overrides, cap retry sleep to the deadline, and harden Bash 3.2 / scoped npx recursion guards.
URL-only FAIRMINT_TEST_*_URL overrides now use Keycloak password-grant defaults instead of accidentally emitting a shared-secret bearer token.
Use base-unit decimal helpers instead of String(number)/toFixed(1), run LocalNet smoke on PRs, and pin Splice/Scribe to MainNet 0.6.14.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 35 out of 40 changed files in this pull request and generated 1 comment.
Suppressed comments (2)
src/testing/cip56/numeric.ts:59
- This separately exported formatter can bypass the parser and format a base-unit value wider than Daml Numeric's 38-digit precision. For example,
formatDamlNumericFromBaseUnits(10n ** 38n, 10)returns an invalid 29-digit integer instead of rejecting it. Validate the base-unit width here as well.
const digits = absolute.toString().padStart(scale + 1, '0');
src/testing/cip56/numeric.ts:43
- This parser enforces fractional scale but not Daml Numeric's 38-digit precision. At the default scale 10, a 29-digit integer is accepted and then emitted by the command builders even though
Numeric 10permits only 28 integer digits, so the supposedly validated value is rejected only by the Ledger. Reject integer parts wider than38 - scale.
This issue also appears on line 59 of the same file.
if (fractionRaw.length > scale) {
throw new Error(
`Daml Numeric ${JSON.stringify(amount)} has more than ${scale} fractional digits`
);
}
Align integration assertions with normalized Numeric strings, reject non-representable number amounts, and strip private/TODO fixture notes.
Do not treat a missing Daml toolchain as a successful fixture build.
Use return instead of exit so the RETURN trap removes incomplete clones.
Wire prepare-release with package.json floor / first-publish 0.1.0 behavior and publish.yml using trusted publishing (no NPM_TOKEN on publish).
Bash parses `set -eou pipefail` as `set -o u`, which fails and applies none of the options. Use `set -euo pipefail` in Canton and Splice healthcheck patches so failed http_check calls fail the script.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 9bdb80e. Configure here.
Validator createUser maps insufficient reserved traffic to HTTP 429. Wait and retry with a fresh party prefix until auto top-up lands so CIP-56 LocalNet smoke is not flaky right after readiness.
Strip setup-node's empty _authToken line before npm publish so Trusted Publisher OIDC can run. On first publish (no tags), use recent commit history instead of empty main..HEAD.

Summary
Establishes
@fairmint/canton-dev-tools@0.1.0as the shared owner of Fairmint Canton LocalNet lifecycle and reusable integration-test helpers (ENG-1635).Ready for merge once CI on tip is green and the Trusted Publisher checklist below is done (or accepted as a post-merge blocker before first publish succeeds).
Included
canton-dev-tools/ legacycanton-localnet):start,readiness,diagnostics,teardown(+ legacy aliases)@fairmint/canton-dev-toolsand@fairmint/canton-dev-tools/testingCOMPATIBILITY.md.dar,libs/**, andfixtures/**splice-test-token-v2@0.6.11)main:.github/workflows/publish.yml+scripts/prepare-release.ts(OIDC Trusted Publishing; noNPM_TOKENon the publish step; first expected version0.1.0)Companion consumer PR: Fairmint/canton-node-sdk#398
Test plan
npm install && npm run build && npm run lint && npm test && npm run pack:checkbuild-and-test) green on tipnpm run fixture:splice-test-token-v2:fetchverifies SHAprepare-releaselanded on tip; OIDC npmrc + first-release changelog fixes on tip (9d83dfd)Human checklist — npm Trusted Publisher (required before first successful publish)
Auto-publish is implemented and will run on merge to
main. A human must configure the Trusted Publisher once so the first publish can succeed:@fairmint/canton-dev-tools(create the package under@fairmintif it does not exist yet), or use org Add Trusted Publisher:Fairmintcanton-dev-toolspublish.yml(exact; no path prefix)@fairmintorg / maintainers as needed.Publish to NPMworkflow onmainsucceeds and tagsv0.1.0(or the prepared floor version).Notes from the workflow:
id-token: write); do not setNODE_AUTH_TOKEN/NPM_TOKENon the Publish step.NPM_TOKENonly to fetch private@fairmint/*peers.--provenance.Follow-ups (post-merge)
@fairmint/canton-dev-toolsdev-docsLocalNet page for Dev Tools ownershipNote
Medium Risk
Large greenfield package that becomes the shared LocalNet pin owner and PR regression gate (Docker smoke); publish workflow needs one-time npm Trusted Publisher setup before first release.
Overview
Introduces
@fairmint/canton-dev-toolsas Fairmint’s shared owner of Canton LocalNet lifecycle and integration-test helpers (ENG-1635).The
canton-dev-tools/canton-localnetCLI wrapsscripts/localnet-cloud.shwith product commands (start,readiness,diagnostics,teardown), automatic cn-quickstart cache fetch, and central default pins (quickstart ref, Splice/Scribe0.6.14, protocol35, oauth2 auth) documented inCOMPATIBILITY.md. TypeScript is exported from the main entry and@fairmint/canton-dev-tools/testing(LocalNet client config, ledger helpers, and CIP-56 / CIP-112 command builders plusrunCip56TransferSmokeagainst Splice TestTokenV2).CI adds standard build/lint/test/
pack:check(forbidsfixtures/**,*.dar,libs/**in the npm tarball and verifies CLI pins), a PR LocalNet smoke workflow (start → readiness → CIP-56 transfer → teardown), andpublish.ymlonmainwith npm OIDC trusted publishing,prepare-releaseversioning, and git tags.Internal-only fixtures (DAR lifecycle sources, SHA-pinned TestTokenV2 fetch) support smoke tests but are excluded from publish.
Reviewed by Cursor Bugbot for commit 9d83dfd. Bugbot is set up for automated code reviews on this repo. Configure here.