Skip to content

Proposal: Denex Localnet#318

Merged
hythloda merged 1 commit into
canton-foundation:mainfrom
mgaare:denex/localnet
May 21, 2026
Merged

Proposal: Denex Localnet#318
hythloda merged 1 commit into
canton-foundation:mainfrom
mgaare:denex/localnet

Conversation

@mgaare
Copy link
Copy Markdown
Contributor

@mgaare mgaare commented May 8, 2026

Development Fund Proposal Submission

Proposal file:
./proposals/denex-localnet.md


Summary

denex-localnet is a Testcontainers-style SDK and CLI for running Canton Network LocalNets, collapsing what would currently require dozens of scattered config files into a single declarative YAML file. It orchestrates the full Canton stack — Super Validator, regular validators, Splice, Keycloak auth, and all associated web UIs — directly via the Docker API (no Docker Compose), with sensible defaults for ports, party hints, realm naming, and OAuth2 wiring.

It lowers the barrier to local development and integration testing: developers can spin up a fully-wired multi-validator network programmatically (via the cross-runtime SDK on Deno/Node/Bun) or interactively (via the Deno CLI), then query state, allocate parties, and provision users through clean high-level APIs. This enables programmatic and flexible integration for app developers, and makes Canton LocalNets viable as ephemeral fixtures in CI pipelines and SDK-driven test suites — a workflow that's currently very challenging with existing tooling.


Checklist

  • Proposal file added under /proposals/
  • Milestones and funding amounts defined
  • Acceptance criteria included
  • Alignment with Canton priorities described

Notes for Reviewers

(Add anything the Tech & Ops Committee should pay attention to.)

@mgaare mgaare requested a review from a team as a code owner May 8, 2026 00:03
@hythloda hythloda moved this from Incoming to Voting Live in Dev Fund Incoming May 8, 2026
# Username == password convention (so 'bob-trader' logs in with
# password 'bob-trader'). The Keycloak realm for this validator is
# 'Bob' (title-cased from validator name).
users:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From an implementation standpoint, have you seen the declarative API for users/parties? (https://docs.digitalasset.com/operate/3.4/howtos/configure/general/declarative_conf.html)

It would be nice if, as part of this, changes were implemented in lower levels of the stack so that components understand declarative APIs more natively.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hadn't seen that before. It looks interesting.

denex-localnet also provisions the users in keycloak from this config, and it's network-wide rather than localized to a single participant, so a 1-1 correspondence with the dynamic participant config feature probably isn't possible. It's worth thinking about using it in the implementation, though.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that mid term we wanted to decouple the user database from the validator instance in order to use it across the different node types and support direct integration with existing user management systems rather than to hand-roll our own. This is more of a heads-up, though.

@vadasnorbert
Copy link
Copy Markdown
Contributor

Based on our experience with Canton dev environments, a “Hardhat-like” tool could be very useful for Canton, and could significantly improve the developer experience for both new and experienced Canton builders.

The proposal also directly addresses one of the clearest pieces of feedback from the DevEx survey, i.e. the need for better local development frameworks and simpler environment setup. In addition, features described in the proposal, such as the single-file declarative configuration, programmatic API for lifecycle, or the runtime state inspection/discovery could potentially offer capabilities beyond those of existing frameworks. Personally, I am supportive of this proposal.

# Validators
# ---------------------------------------------------------------------------
# The Super Validator (SV) is IMPLICIT — always exactly one, created
# automatically. You only configure the *regular* validators here.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What exactly will constitute a validator here? Participant node & validator app?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes - participant node, validator, wallet ui, the full splice stack.

# transformation, because Splice enforces a 30-char limit on the derived
# participant node name (`{participantName}-validator_backend`, where
# `participantName = name.replace(/-/g, '_')`).
#
Copy link
Copy Markdown
Contributor

@daravep daravep May 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that multi-synchronizer support is coming (and is already usable for alpha development in 3.5). Does the grant here include running multiple synchronizers as well?

Also please note that recently PQS was approved as OSS, which means that it is likely going to become a standard piece of infrastructure for app provider backends: #67

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not at this point. I don't have a good enough sense of the use cases and shape of the requirements there to have a point of view on what/how to build.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Supporting PQS is a requirement. A large percentage of dApps use PQS.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the short term, supporting a Wallet Gateway will also be needed. See Proposal: Wallet Gateway Reference Implementation #109.

I would expect the pattern of running sidecar, Docker images to a validator will be a recurring pattern.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PQS, Wallet Gateway, and any other tools that a user would like to use with their app are supported the same way that apps themselves are supported: denex-localnet provides infrastructure-level configuration and primitives, and a rich API/CLI surface with which users can configure and run those higher-level components in whatever way they want. Whether to run PQS, Wallet Gateway, or any of a slew of other current or future Canton tools and applications, and how they are configured, is a matter of user choice.

I do think it would be a good idea to include some rich recipes in the documentation for how to configure and run apps and app tools on a denex-localnet.

Idempotency: re-calling with the same args is safe — each side (ledger, Keycloak, wallet) converges. Partial failures are recoverable; just call again.

---

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it make more sense to base the CLI here on a normal Typescript Client that can also be used in applications and mostly provide a factory to automatically discover and connect to the node? This would allow devs to only learn one syntax API and avoid creating a small subset that only works in a few common cases.

Copy link
Copy Markdown
Contributor Author

@mgaare mgaare May 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I quite follow what you're saying here.

denex-localnet provides two interaction surfaces, which have essential feature parity between them:

  • A TypeScript API (documented in this section) with which a user can create, configure, lifecycle, query status and metadata, and to an extent modify localnets from that user's own code
  • A CLI tool that can be used to do the same things

With those tools you can both create a new localnet or interact with a running localnet - and there could be more than one running. This section is showing how the TypeScript API can get a handle on a running localnet. The handle returned from this is the same type of object you get when creating a new localnet from a config.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I do see that one part of the Typescript API is interacting with the node lifecycle. However, the second part is ultimately interacting with the node state via Ledger API.

In your other proposal, you are also proposing a new Typescript Ledger interface: https://github.com/canton-foundation/canton-dev-fund/pull/238/changes#diff-4f2822a1b669831f32b246ebe4d0c799c9407081c67470b41c55ec781b794b5cR17-R18

So my question is how much overlap there is and how these two will be used in conjunction.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's some limited overlap, in particular for the mutations that localnet can do. Right now the beta version does not use the interface from our SDK for the things it needs to do via the ledger, mostly for some logistical/release coordination reasons. I would guess that once there's a finalized release of our SDK and npm distribution it's likely that'll get integrated into localnet, but don't know that for sure just yet. This is more in the realm of implementation detail.

Here is the vision for usage. denex-localnet is meant to stand up the Canton infrastructure-layer pieces that the application layer needs, providing a flexible way for users, applications or app test/run harnesses to configure and run that infrastructure, and supplying the information those applications would need to configure and bootstrap themselves (parties, users, network endpoints) - the sorts of things that often get plugged into say an app .env file. Beyond this initialization, consumers should generally handle all subsequent ledger interactions and state mutations on their own - perhaps by using the tools in our separate SDK, but that kind of decision is app- and usecase- specific and outside of denex-localnet's bounds.

subject to change. These are intended to help illustrate the design
thinking and capabilities of `denex-localnet`.

### Configuration Files
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How are you going to support passing custom configuration arguments to the individual nodes?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What kind of configuration do you have in mind?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New features and capabilities are often introduced via feature flags so people can try them out (either via canton.parameters.xyz or canton..node.parameters). Also, the resource consumption of the nodes can be tweaked there etc. Or the protocol version to be used etc: https://docs.canton.network/sdks-tools/development-tools/sandbox#dev-protocol

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those are interesting ideas and I'll have to give that some thought.

experience of navigating 80-120+ files across Splice LocalNet and CN Quickstart
into a single, readable configuration. Developers can bring up a customized
Canton network in minutes rather than hours, and integrate it programmatically
into their test suites and CI/CD pipelines.
Copy link
Copy Markdown
Contributor

@daravep daravep May 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How are you going to solve this, actually, technically. Do you plan to map this to docker? Do you plan to integrate this tightly with the splice releases?

How do you minimize resource usage? This has been an issue with localnet, so it's worth bringing it up here so you don't run into the same problem. Right now, a full localnet creates too many resource hungry JVM processes.

canton.jar itself does support running many nodes in a single jvm process (and in theory wouldn't be hard to support dynamically adding new nodes to a running process), but the validator app is a separate jar.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the working beta version of denex-localnet uses the Docker socket directly to create and manage the containers, using configs that it generates from the single denex-localnet config.

I think there is an effective minimum resources needed to run a localnet. We're running a single canton container, and single validator app container, each hosting all of the nodes. All told, between canton, all the splice apps, postgres, nginx, keycloak, right now a denex-localnet with 2 validators configured stands up 10 docker containers consuming around 5 GB of memory.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have some additional questions:

  1. Does that configuration include the synchronizer containers (e..g, mediator, sequencer)?
  2. How do you plan on keeping your configurations consistent with DevNet/TestNet/MainNet which may each have different config values?
  3. Regarding memory, I think you need to have a requirement that the memory footprint is equal to or less than the current LocalNet for the same app. Can you add this?
  4. If a dApp needs to include other services (e.g., LAPI client, Nginx running a UI), how easily will this framework incorporate this? For example, the dApp user needs to run a LAPI client, how will it know the port number for the LAPI for the target validator? How will the Docker vpn accommodate these non-LocalNet images?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Yes, it includes the synchronizer and the full Splice single-node Super Validator setup.
  2. I'm not sure which configurations you mean. Talking about, eg, the Amulet config and associated Splice configs that are part of network governance, or some other config?
  3. So I think this might be somewhat difficult, as splice and cn-quickstart localnet have a fixed number of participants, whereas denex-localnet has a configurable number of participants. Additionally, some of the other configuration options requested by @daravep in a separate comment, if really supported properly, would probably need to contemplate different low-level configuration options per-participant, which could in turn require denex-localnet to run those participants in a less memory-efficient way (separate JVM/containers per participant, potentially, rather than single multi-hosting).
  4. denex-localnet is designed to support flexible integration with apps. It binds host ports in a configurable range for the ledger API, admin API, and the various Splice UIs/apps. The TypeScript API and the CLI are designed to support both manual and programmatic querying for what those endpoints are against a running localnet, and integrating flows to generate app .env files that apps can hook into. It allows developers to choose whatever mechanism they want to run their own app level components locally, and multiple options for how they want to bootstrap and integrate them with the underlying Canton layer.

Idempotency: re-calling with the same args is safe — each side (ledger, Keycloak, wallet) converges. Partial failures are recoverable; just call again.

---

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I do see that one part of the Typescript API is interacting with the node lifecycle. However, the second part is ultimately interacting with the node state via Ledger API.

In your other proposal, you are also proposing a new Typescript Ledger interface: https://github.com/canton-foundation/canton-dev-fund/pull/238/changes#diff-4f2822a1b669831f32b246ebe4d0c799c9407081c67470b41c55ec781b794b5cR17-R18

So my question is how much overlap there is and how these two will be used in conjunction.


**Phase 2 Funding Requested: 1,250,000 CC**

## Phase 3: Hardening
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest that you add another work item to continuously align localnet with the splice release cadence. The artifacts you are coding against are not static (e.g. multi-sync ...). I would recommend putting an initial but low estimate in there, but refer to a possible extension grant if the effort exceeds the budget.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I had intended here to convey that idea with the "regular updates to incorporate Splice releases". Are you suggesting to go further and specify a release cadence that explicitly matches Splice's (currently weekly)?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies, looks like I've overlooked this. In that sense it seems all good.

primaryParty: ops
rights: [ParticipantAdmin]
# ---------------------------------------------------------------------------
# DAR packages to upload after startup
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A feature that we've found extremely helpful is to automatically forward log files to a single directory so that a tool like "lnav" can be used. Can you include this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll have to give that some thought, on the right way to handle this.

}

// One snapshot of everything
const snapshot = await net.getSnapshot();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please explain what getSnapthot does and what its boundaries are (e.g., include PQS or not)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This returns an object that shows the full configuration/metadata from the localnet: parties, users, endpoints, participants, DSO, uploaded Dars, etc. It's not talking about database or runtime state. Perhaps not the best named method.


```ts
// Allocate an additional party (createUser does this automatically for referenced hints)
const party = await net.allocateParty('alice', 'validator-1', 'Alice');
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What kind of party is this: internal or external? External parties are the focus because they support wallets.

Can you reuse the TypeScript signatures of the Wallet SDK and dApp SDK so that users don't need to learn several libraries that do the same thing? For example, see https://docs.canton.network/integrations/wallet/guidance#create-an-external-party-wallet.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are internal parties.

I will say that I have mixed feelings about including support for Mutations at all in the API. As you point out, that really starts to get into the domain of application logic rather than the infrastructure layer. There is a good chance of dropping this feature entirely, and limiting denex-localnet to only creating users and parties from config as part of the localnet initialization process.


---

## 4. Comparison with Existing Solutions: Denex Localnet vs. CN Quickstart / Splice LocalNet
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you have highlighted gaps. Will you be replacing LocalNet in Splice and CN QuickStart with denex-localnet? If not, why? I think that would make sense. After all, how many types of localnet do we need?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good tools are opinionated. While it's not for me to say whether denex-localnet is a good tool, it is an opinionated one. Although I have made an effort to focus the design such that the "blast radius" of its opinions are kept smaller, and as much useful choice is left up to the user, nonetheless people are free to accept them or not. Given that, I was not planning to try to promote this into Splice or QuickStart.

@hythloda hythloda moved this from Voting Live to Approved in Dev Fund Incoming May 21, 2026
@hythloda hythloda merged commit 7bcc7eb into canton-foundation:main May 21, 2026
1 check passed
@github-actions
Copy link
Copy Markdown

@mgaare, your proposal is missing a Special Interest Group (SIG) label. Adding the right SIG label ensures the relevant domain experts can find and review your proposal, Check more about SIGs.

Please add one of the following labels to this PR:

  • attestor-pools-daos-multisig
  • canton-apis
  • canton-protocol-multi-synchronizer
  • daml-tooling
  • dapp-integration
  • dar-app-management
  • defi-liquidity
  • defi-protocols
  • financial-workflows-composability
  • global-synchronizer-scaling
  • node-deployment-operations
  • onchain-governance
  • party-portability-data-resilience
  • regulatory-compliance
  • token-asset-standards
  • tokenomics
  • wallet-apps

Not sure which one fits? Pick the closest match to your proposal's domain. You can add a label from the right sidebar under "Labels".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Approved

Development

Successfully merging this pull request may close these issues.

6 participants