This document provides instructions for AI coding agents (GitHub Copilot, Claude Code, and others) to follow when assisting with development in this repository.
apps/holds the Scala backends (packages underorg.lfdecentralizedtrust.splice) and React frontends:validator(also serves the wallet and ANS backends),sv,scan,splitwell, the sharedcommon(apps-common;apps/common/svis SV-specific), andapp(apps-app: the release bundle, entry pointSpliceApp.scala, and the integration tests).- Stores ingest the ledger update stream asynchronously. Automation runs as
*Triggerclasses that react to ledger state and submit follow-up commands. daml/holds the Daml packages, one sbt project each.token-standard/defines the CIP-0056 token standard APIs, whichdaml/splice-amuletimplements.canton/is a copy of open-source Canton, not Splice code. Avoid editing it; if you must, record the change inCANTON_CODE_CHANGES.md(see "Bumping Our Canton fork" in MAINTENANCE.md).cluster/holds the Helm charts, Pulumi deployment code, and Docker Compose setup.
Every contribution must be tested in an automated test. For further details see the Testing README.
- Run
sbtinside the direnv/nix dev shell, e.g.direnv exec . sbt -batch "apps-scan/testOnly <FullyQualifiedSuite>".-batchavoids the interactive prompt; redirect output to a file and grep for^\[error\]andTests:. - Test code compiles with
-Xfatal-warningsand-Wunused:privates/-Wunused:params, so an unused private member or parameter failsTest/compile. - To format specific files:
sbt "scalafmtOnly <files...>". - Integration tests under
apps/app/src/testneed a running Canton from./start-canton.sh, including suites based onIntegrationTestWithIsolatedEnvironment. Without it they fail withjava.io.FileNotFoundException: canton.tokens.- Start only what the test needs:
./start-canton.sh -d -wfor wallclock tests,-sfor simtime tests. Add-p localto use a local Postgres instead of Docker. start-canton.shmanages Postgres itself; do not start it separately withscripts/postgres.sh. Stop with./stop-canton.sh(pass the same Postgres mode, e.g../stop-canton.sh local).- Restart Canton (
./stop-canton.shthen./start-canton.sh) whenevernix/canton-sources.jsonchanges, e.g. after a checkout or rebase: it pins the Canton version thatstart-canton.shruns. - Run a single test with
sbt "apps-app/testOnly <FullyQualifiedSuite> -- -z \"<test name>\"". - Suites named
*FrontendIntegrationTestdrive the frontends through Selenium and also need./start-frontends.sh.
- Start only what the test needs:
- Do not run bare
sbt test; usetestOnlyand leave the full suite to CI. - CI runs
sbt checkErrorsafter tests, which fails on WARN/ERROR lines in the logs underlog/that don't matchproject/ignore-patterns/. The logs are not rotated, so stale errors from earlier runs also show up; clearlog/before a run you want to check. The main logs arelog/canton_network_test.clog(test run and Splice apps) andlog/canton.clog/log/canton-simtime.clog(Canton). sbt formatFixapplies scalafmt, scalafix, npm fixes and copyright headers (make formatadditionally formats cluster code).sbt lintruns the static checks; note that itsscalafixAllstep can rewrite files.- If dependency resolution fails with a 404 for a Canton snapshot artifact (e.g.
com.daml:bindings-java:<version>-snapshot...), the pinned snapshot has likely expired; rebase onto the latestmainand reload direnv.
- Stores ingest asynchronously: never assert on store state immediately after an action. Use
actAndCheck,eventuallyoreventuallySucceeds. - All test cases of a suite extending
IntegrationTestshare one environment (IntegrationTestWithIsolatedEnvironmentcreates one per test case). UseperTestCaseName(...)for party and other names to avoid collisions between test cases. - Wrap actions that are expected to log warnings or errors in
loggerFactory.assertLogs(or a relatedSuppressingLoggermethod); otherwisecheckErrorsfails in CI. - Simulated-time tests mix in
TimeTestUtiland move the clock withadvanceTimeand related helpers.
Refer to the main README on migrations. Most importantly, never modify a deployed migration script, not even its comments; add a new migration instead.
If you make intentional changes in Daml code, run sbt damlDarsLockFileUpdate and commit the updated dars.lock file along with your dar changes.
All Daml changes must be backwards-compatible. See the Smart Contract Upgrading Reference from the Canton docs.
When adding to enums, make sure to only add further nullary constructors to types that only have nullary constructors.
Backends must keep working while only an older Daml version is voted in. Guard version-specific behavior, and tag tests that need a newer package with the matching scalatest tag from org.lfdecentralizedtrust.splice.util.scalatesttags (e.g. SpliceAmulet_0_1_19) so that the compatibility runs skip them. See "Daml Version Bump" and the sections after it in DEVELOPMENT.md.
When you change a Daml package, bump the version of that package and of every package that (transitively) depends on it, e.g. with git fetch origin && sbt damlBumpPackageVersions. Then run sbt updateDarResources; CI checks that the generated DarResources.scala files are up to date.
- To represent Daml
Numerics for any user facing APIs (console commands), usescala.math.BigDecimal. - To represent Daml
Numerics in Protobuf, usestrings. Conversions to and fromstrings should occur viaorg.lfdecentralizedtrust.splice.util.Proto.encode/tryDecode. - When interacting with the Ledger API, convert Scala
BigDecimals to JavaBigDecimals. - Refer to the
wallet.tapcommand implementation for the canonical handling of Daml Numerics.
- All Protobuf definitions must use
proto3. - Avoid wrapping primitive types in a message structure unless future extensibility will likely be required.
- Use a plural name for
repeatedfields. - Use
stringfields with a suffixcontract_idto store contract ids. - Use
stringfields with a suffixparty_idto store party ids.
- Name flags as
enableXXXinstead ofdisableXXXto avoid a double negation. - Scala config fields are camelCase and map to kebab-case HOCON keys automatically.
- Only environment variables are fully supported for overriding configuration, not system properties.
- When adding an app config option, check whether it also needs wiring through
cluster/images/*/app.conf,cluster/compose/,cluster/helm/,cluster/pulumi/anddocs/src/. After cluster changes, regeneratecluster/expected/withmake update-expected.
- Place
.protofiles insrc/main/protobuf. - Prefer having a single
.protodefinition per service. - Refer to generated Protobuf classes with a package prefix, e.g.,
v0.MyMessageinstead ofMyMessage.
- Use
listXXX,acceptXXX,rejectXXX,withdrawXXXfor managing proposals, requests etc. - Use the term
amount, notquantityornumber. - Use
sender/receiver, notpayer/payee.
Frontend code projects are managed via npm workspaces.
The generated Daml TS bindings (apps/common/frontend/daml.js) and OpenAPI clients (*/openapi-ts-client) are not npm dependencies or workspaces: apps/generated-packages.json lists them and apps/scripts/link-generated-packages.mjs links them into node_modules on npm install. A new OpenAPI client must be added to that file; a Daml version bump updates its aliases (sbt damlBumpPackageVersions does this).
To add a new package to the workspace:
- Register its directory in the root-level
apps/package.jsonworkspaces key. The directory must contain its ownpackage.json. - Add the new package to
build.sbt. - Ensure the package contains at least the scripts
build,fix,check, andstart. - The new package will need its own
tsconfig.jsonfile that inherits from the root tsconfig.
The common-frontend package (@canton-network/splice-common-frontend) contains common code in apps/common/frontend. You can add reusable utility functions, React components, or shared config here. Ensure anything added is exposed via the library's entrypoint, index.ts.
- Use Scala types wherever possible.
- Delay the conversion to Java types until the last possible point.
- Convert from Java to Scala as early as possible.
- To convert, import
scala.jdk.CollectionConverters.*and use theasScalaandasJavamethods.
- Production code bans partial
Iterablemethods such ashead,last,maxandreduce(wartremoverIterableOps); useheadOption,maxOption,foldLeftetc. instead. The exception is for theNonEmptytype, which features total versions of these such ashead1.
- Every commit needs a DCO
Signed-off-by:line; usegit commit -s. - PRs are squash-merged, so the PR title and description become the commit message.
- On branches in this repository, CI only runs if the last commit message contains
[ci](or[static]for static checks only). See TESTING.md for other tags and for cluster tests.