Skip to content

Proposal: Rust SDK for Canton Network#407

Open
dmitryk-cf wants to merge 9 commits into
canton-foundation:mainfrom
dmitryk-cf:main
Open

Proposal: Rust SDK for Canton Network#407
dmitryk-cf wants to merge 9 commits into
canton-foundation:mainfrom
dmitryk-cf:main

Conversation

@dmitryk-cf

@dmitryk-cf dmitryk-cf commented Jun 2, 2026

Copy link
Copy Markdown

Development Fund Proposal Submission

Proposal file:
/proposals/2026-06-Nodejumper-rust-sdk.md

Summary

This proposal requests funding for a production-grade, open-source Rust SDK for Canton Network: an async (tokio) Ledger API client over gRPC and JSON, type-safe code generation from DAR packages, built-in CIP-56 token-standard support, and JWT/OIDC authentication, shipped as a dpm component and published on crates.io under Apache-2.0.

Canton's funded language coverage today is Go and Python (#38), C#/.NET (#46), and the TypeScript dApp SDK (#69). Rust is the one unfilled quadrant, and it is the language of the cohort that sits closest to the network: indexers, validators, oracle relays, and market-making engines. Those teams currently drop to raw gRPC/JSON and re-implement command de-duplication, codegen, and CIP-56 handling per project, or depend on a partial community crate with no codegen, conformance, or maintainer.

In short:

  • Go, C#, TypeScript, and Python have SDKs; Rust is the gap.
  • This delivers the Rust member of that set, built on the same official daml-lf-archive foundation and integrated through dpm, so it extends the toolchain rather than competing with it.

We are a GSF-sponsored Canton validator and will maintain the SDK long-term, which is the part that matters most for a language binding.

What this delivers

  • an async canton-ledger client (gRPC + JSON) with correct change-ID command de-duplication, streaming, structured errors, and OpenTelemetry;
  • canton-codegen: DAR → typed Rust via the official daml-lf-archive, Smart-Contract-Upgrade-aware, shipped as dpm codegen-rust;
  • a documented Daml-LF → Rust type mapping;
  • built-in CIP-56 support (holdings, transfer instruction, allocations, choice-context, ledger-derived createdEventBlob);
  • a typed PQS client (no hand-written JSONB SQL);
  • external/interactive submission with a pluggable signer (HSM/KMS-compatible);
  • pre-built canton-splice-* crates for the protocol DARs (cargo add canton-splice-wallet), refreshed on each Canton/Splice release;
  • a reference application, a conformance suite, an independent security review, and docs;
  • a working PoC at Milestone 1 (a real transaction submitted and read on DevNet).

Codegen and Smart Contract Upgrade

Code generation reads Daml-LF through the official daml-lf-archive decoder, not a bespoke DAR parser, so it tracks Daml/LF additions as they ship. Generated types carry package id and version plus the PackageMap, so consumers resolve the correct template version under Smart Contract Upgrade; a version bump regenerates compatible code rather than breaking silently. Codegen is invoked as a dpm component (dpm codegen-rust), not a competing standalone CLI.

Checklist

  • Proposal file added under /proposals/
  • Milestones and funding amounts defined (1,500,000 CC across 4 milestones, ~6 months)
  • Acceptance criteria included (adoption- and demonstration-based)
  • Alignment with Canton priorities described

Notes for Reviewers

The scope is deliberately narrow: a Rust client SDK and codegen for the Ledger API plus CIP-56, distributed through dpm. It does not add a smart-contract language, a wallet, an indexer, a DEX, or admin/topology tooling.

The main points:

  • Fills the one unfilled language quadrant. Go ( Funding Proposal by NODERS: Go SDKs and Python DAZL Contributions for Canton #38), C#/.NET (Proposal: C# / .NET SDK for Canton Network #46), and TypeScript (Development Fund Proposal: Canton Network dApp SDK and Tooling #69) are precedent; this is the same category for Rust, the language of the infrastructure cohort (indexers, validators, oracle relays, market-making).
  • **Rust has real, measurable adoption, on-chain and off. Around 2.3M developers worked in it over the past year (~700K call it their primary language), commercial use is up ~69% in three years, and roughly 45% of organisations now make non-trivial use of it. On-chain it's the same story, Solana now leads every chain in active developers, with Polkadot and Sui in the top tier, all Rust-based, and Solana's developer base is up ~60% over two years. That's exactly the cohort closest to a ledger, and today it has no maintained way into Canton.
  • Extends, does not replace. It consumes the published Ledger API protos, the JSON Ledger API, and daml-lf-archive unchanged, ships codegen as a dpm component, and requires no protocol, ledger-contract, or CIP-56/CIP-0112 changes.
  • Consolidates existing community work. The DLC-link/canton-lib crate is a useful low-level partial (some ledger calls, registry/wallet helpers) with no codegen, conformance, or maintenance guarantee. We consolidate that surface into a complete, versioned, maintained SDK and will engage its authors so the ecosystem converges rather than forks.
  • Not just an OpenAPI/gRPC generator. Generators produce transport stubs. The value is de-duplication semantics, DAR-versioned typed bindings (SCU-aware), CIP-56 choice-context and disclosed-contract handling, typed PQS access, and idiomatic async — the per-project wrapper layer every existing SDK was funded to remove.
  • De-risked. A working PoC lands on DevNet at Milestone 1, and a Rust SDK maintained by a standing Canton validator addresses the maintenance risk that language bindings live or die on.

@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown

SIG labels auto-detected and applied: canton-apis

If this is incorrect, you can ask the reviewers to update the labels.

@github-actions

github-actions Bot commented Jun 2, 2026

Copy link
Copy Markdown

Champion identified Canton Foundation

The committee will verify this champion during review.

@dmitryk-cf dmitryk-cf changed the title docs(proposal): add Rust SDK for Canton Network Proposal: add Rust SDK for Canton Network Jun 2, 2026
@dmitryk-cf

Copy link
Copy Markdown
Author

@dmitryk-cf dmitryk-cf changed the title Proposal: add Rust SDK for Canton Network Proposal: Rust SDK for Canton Network Jun 2, 2026
@srikanth-bitdynamics srikanth-bitdynamics self-assigned this Jun 5, 2026
@srikanth-bitdynamics

Copy link
Copy Markdown
Contributor

@dmitryk

  1. The proposal’s typed PQS client compiles typed predicates to JSONB-path queries over DA’s PostgreSQL projection. That is an interesting developer convenience layer, but it is meaningfully more fragile than wrapping the Ledger API. It depends on PQS schema/behavior and on JSONB representation details that are not described here as a stable public contract. This may still be useful, but it reads more like an opportunistic app-side convenience library than a core Rust SDK primitive, and it probably deserves either a stronger compatibility story or a narrower scope.

  2. Is the dpm component path already supported enough that dpm codegen-rust is mostly packaging work, or does this proposal also depend on still-evolving dpm component behavior?

  3. Which abstraction level is the client targeting?

@dmitryk-cf

Copy link
Copy Markdown
Author

@srikanth-bitdynamics

Thanks for review

1 - On the PQS client: it depends on PQS's supported surface, not its internals. The PostgreSQL schema is an implementation detail that evolves (managed by Flyway) and we never touch it directly. We build on the PQS SQL API - the active/creates/archives/exercises functions plus the documented Daml-LF JSON encoding of the payload - which DA calls the stable, supported interface and the only artifacts readers should use. Our typed predicates are standard JSONB filters over that, and decoding/versioning use the representative package_id/package_version it exposes, which also keeps it correct under Smart Contract Upgrade. It's an optional crate, decoupled from the core client and version-pinned, so if a query pattern ever needs a surface PQS doesn't guarantee, we constrain that pattern rather than drop the feature.

2 - On the dpm component: it's mostly packaging on top of a documented, supported mechanism, not a bet on still-evolving behaviour. The component model is stable from SDK 3.5 onward, components are published as OCI artifacts and opted into via the components field, a local/custom codegen component is an explicitly supported shape, and codegen-java/codegen-js are already first-class. dpm codegen-rust follows that same pattern. As extra insurance the codegen also runs as a standalone binary, so even if the component contract shifts the generator keeps working and we just adjust the wrapper.

3 - On abstraction level: it's a thin typed client plus ergonomics plus opt-in resilience, which is the split Canton itself recommends. Writes and streaming go through the Ledger API (gRPC and JSON), reads go through PQS, and the SDK keeps no in-process Active Contract Set cache or event-sourced state of its own. Concretely that's typed, codegen-aware wrappers over the Ledger API, streaming-first, structured errors, an opt-in retry pipeline that preserves command_id so de-duplication stays correct, and OpenTelemetry. The participant owns authoritative state and PQS exposes it queryably, so the SDK stays a client rather than a framework.

@srikanth-bitdynamics srikanth-bitdynamics moved this from Incoming to In Review (Champion Assigned) in Dev Fund Incoming Jun 7, 2026
@srikanth-bitdynamics

Copy link
Copy Markdown
Contributor

Hey @dmitryk-cf , thanks for the reply. This answers my question.

Note to voting team:

Given that SDK support for other languages is something that is approved for many languages and i think RUST is missing out on the list so this proposal covers that.

Also this proposal covers many concerns raised by core team for other proposals on high level so I'm moving this to ready to vote to let core team look into technicalities

@srikanth-bitdynamics srikanth-bitdynamics moved this from In Review (Champion Assigned) to Ready for Vote in Dev Fund Incoming Jun 7, 2026
@srikanth-bitdynamics

Copy link
Copy Markdown
Contributor

@dmitryk-cf One probably important thing I forgot to mention is on Adoption metrics. I don't see clear path for it there so its good to add adoption metrics and also more weightage for funding on that adoption.

@dmitryk-cf

Copy link
Copy Markdown
Author

@srikanth-bitdynamics
Thanks - agreed. I've restructured around it.
Milestone 4 now carries 70% of the funding, paid per Featured App in production on mainnet plus a metrics-gated completion tranche. Details are in the latest commit (Funding + Milestone 4).

@monsieurleberre

Copy link
Copy Markdown
Contributor

Nice to see a Rust SDK in the mix 👌 One thing you'll probably hit on codegen: we wrapped daml-lf-archive instead of reimplementing the Daml-LF decoder — it means upstream LF changes come for free and the output can't drift from other Daml tools. Catch is it's JVM. Inside dpm that's free (dpm needs a JVM anyway); it only bites if you want a self-contained cargo install outside dpm — then it's a JDK in your toolchain, or a native Rust decoder with the drift risk the wrap avoids.

Maybe you already had thought about that but... 🤔 which way are you leaning?

@dmitryk-cf

Copy link
Copy Markdown
Author

Nice to see a Rust SDK in the mix 👌 One thing you'll probably hit on codegen: we wrapped daml-lf-archive instead of reimplementing the Daml-LF decoder — it means upstream LF changes come for free and the output can't drift from other Daml tools. Catch is it's JVM. Inside dpm that's free (dpm needs a JVM anyway); it only bites if you want a self-contained cargo install outside dpm — then it's a JDK in your toolchain, or a native Rust decoder with the drift risk the wrap avoids.

Maybe you already had thought about that but... 🤔 which way are you leaning?

Yeah, I've been thinking about exactly this, and the plan is the same: wrap daml-lf-archive rather than reimplement the decoder. The drift-free property is the whole point, so a native Rust decoder is not part of the proposed scope.

For the JVM catch, most of it is absorbed by how the SDK is distributed. The pre-built canton-splice-* crates are plain Rust with no JVM at all, so the common case is just cargo add without running codegen directly. The JVM only comes in when someone generates bindings from their own DAR: inside dpm, it is already part of the expected toolchain; outside dpm, it is a build-time JDK/JVM requirement, never a runtime dependency.

So: wrap daml-lf-archive, keep codegen as a build-time step, and make the no-JVM prebuilt crates the default path. Appreciate you flagging it.

@daniel-t-p

Copy link
Copy Markdown

Nice, a maintained Rust SDK is exactly what's missing here. Big part of our team at VertexPoint Labs works in Rust, and right now using Canton means going straight to raw gRPC and rewriting the same Ledger API + codegen layer on every service. Would definitely pick this up

@hythloda hythloda moved this from Ready for Vote to Voting Live in Dev Fund Incoming Jun 12, 2026
@Denend

Denend commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Looks good to me. Rust is already widely used by infrastructure teams + funding is heavily tied to actual adoption.

A maintained Rust SDK with codegen and token-standard support would also save teams from rebuilding the same Canton integrations over and over


### 3. Architectural Alignment — Extending Daml / Canton into the Rust Ecosystem

The default approach is to extend what exists rather than introduce parallel infrastructure. This proposal does so at two layers.

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 love this statement!!!

- Documented Daml-LF → Rust type mapping; first `canton-splice-*` reference crates; sample app using generated bindings.
- **Verification:** demonstrate codegen → submit command → observe transaction → query ACS on both gRPC and JSON paths; an SCU version bump regenerates compatible code.

### Milestone 3: CIP-56 Token Standard, External Signing, PQS Client, Conformance & Security Review

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.

The Token Standard II specification was just ratified. I think you should add it since it will be very important going forward.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks, added first-class V2 (CIP-0112) support to canton-token (Account model, allocation/executor settlement, V2 transfer-event parsing) alongside V1, and reflected it in milestone 3 and the canton-splice-* crates.

dmitryk added 2 commits June 16, 2026 16:42
… SDK proposal — Account model, allocation/executor settlement, committed allocations & iterated settlement, and EventLog parsing across abstract, mechanics, M3, and acceptance gates
@shaul-da

Copy link
Copy Markdown

@dmitryk-cf we discussed this in the committee meeting (you're welcome to join these in coordination with @hythloda whenever your grants are being discussed). I'm generally very supportive of a Rust SDK but we'd need to have a few individuals from companies building on Canton to comment here on the PR and:

  1. express their intent to use the Rust SDK
  2. commit to be design partners who will provide ongoing input and feedback throughout the duration of the grant
  3. commit the time to review milestone acceptance criteria, since the voting committee members don't have the Rust experience to review the quality of deliverables ourselves

@noah-dfns

Copy link
Copy Markdown

Very nice to see a rust-sdk being developed. We do like to use rust especially around core cryptographic functions at DFNS!

dmitryk added 2 commits June 22, 2026 19:26
…lient Standard — scope v1 to the core client surface (codegen incl. contract keys, TLS, error classification, command recovery, resilient streams, OTel metrics, signing) and explicitly defer the rest (admin/topology, in-memory ACS, multi-sync management, batching, HA, node-health)
@dmitryk-cf

Copy link
Copy Markdown
Author

Posting a quick update on where the proposal stands after a round of changes.

Alignment with the Ledger Client Standard. I went through the Ledger Client Standard doc and reworked the proposal to scope against it directly, so the Rust client lands on the same surface and the same patterns as the other language SDKs. v1 now maps to the core client surface in the Standard, and everything beyond that is written up as an explicit, named roadmap.

Filled the v1 gaps. Going through the Standard surfaced a few things the earlier draft was light on, and those are now part of v1: TLS, error classification and retry, command recovery, resilient and resumable streams, OpenTelemetry tracing and metrics, configurable structured logging, and codegen that covers contract keys and the full dependency closure with both JSON and gRPC codecs.

Token Standard V2 (CIP-0112). V2 is now a first-class target throughout, alongside CIP-56 (V1). Since it ships as new package versions and the codegen is SCU aware, both are reachable from the same client without a separate code path. I also spelled out the token coverage so it isn't ambiguous: one-step transfers, pre-approvals, allocate and transfer, and instrument inspection.

Conformance. The M3 conformance suite is now mapped back to the Standard, so every v1 capability has a matching check, plus a published compatibility matrix.

Why Nodejumper. I also updated this section to better cover our background and prior work (Rust and previous contributions).

@shaul-da

Copy link
Copy Markdown

Very nice to see a rust-sdk being developed. We do like to use rust especially around core cryptographic functions at DFNS!

@noah-dfns one of the things we're currently struggling with in the Tech & Ops committee of the foundation is finding the right people who (1) have the right technical skills (2) intend to use the work output of the grant, and (3) are willing to commit the time to be design partners for the grant. The problem is that it's easy for us to approve a grant, but once we get to evaluating the milestones' acceptance criteria, we need companies that are motivated to use the work product and assess whether the acceptance criteria have been met to their satisfaction.

So the question is - do you think DFNS can commit to:

  1. Being a design partner - continuously reviewing the progress from early design all the way through production use of the Rust SDK
  2. Reviewing ACs for all milestones and advising committee members whether they've been met to your satisfaction
  3. Using the Rust SDK in production, assuming all of your feedback is incorporated

This is a soft commitment with no enforcement mechanism, but we really need a few potential users to drive the feedback loops alongside the people developing the feature. It's also a good filtering mechanism for the voting committee to know how robust the demand is.

@shaul-da

Copy link
Copy Markdown

@srikanth-bitdynamics and @daniel-t-p see my previous comment, same question to you. Ideally we have 3-4 design partners

@daniel-t-p

Copy link
Copy Markdown

@shaul-da We would be happy to participate. We plan to use the Rust SDK in our application and can provide feedback during development.

@dasormeter

Copy link
Copy Markdown

Nice to see a Rust SDK in the mix 👌 One thing you'll probably hit on codegen: we wrapped daml-lf-archive instead of reimplementing the Daml-LF decoder — it means upstream LF changes come for free and the output can't drift from other Daml tools. Catch is it's JVM. Inside dpm that's free (dpm needs a JVM anyway); it only bites if you want a self-contained cargo install outside dpm — then it's a JDK in your toolchain, or a native Rust decoder with the drift risk the wrap avoids.

Maybe you already had thought about that but... 🤔 which way are you leaning?

Yeah, I've been thinking about exactly this, and the plan is the same: wrap daml-lf-archive rather than reimplement the decoder. The drift-free property is the whole point, so a native Rust decoder is not part of the proposed scope.

For the JVM catch, most of it is absorbed by how the SDK is distributed. The pre-built canton-splice-* crates are plain Rust with no JVM at all, so the common case is just cargo add without running codegen directly. The JVM only comes in when someone generates bindings from their own DAR: inside dpm, it is already part of the expected toolchain; outside dpm, it is a build-time JDK/JVM requirement, never a runtime dependency.

So: wrap daml-lf-archive, keep codegen as a build-time step, and make the no-JVM prebuilt crates the default path. Appreciate you flagging it.

So in this proposed design is the only planned jvm dependency for wrapping the Daml LF decoder, but otherwise rust codegen would be operating off the protobuf?

@dmitryk-cf

Copy link
Copy Markdown
Author

Nice to see a Rust SDK in the mix 👌 One thing you'll probably hit on codegen: we wrapped daml-lf-archive instead of reimplementing the Daml-LF decoder — it means upstream LF changes come for free and the output can't drift from other Daml tools. Catch is it's JVM. Inside dpm that's free (dpm needs a JVM anyway); it only bites if you want a self-contained cargo install outside dpm — then it's a JDK in your toolchain, or a native Rust decoder with the drift risk the wrap avoids.

Maybe you already had thought about that but... 🤔 which way are you leaning?

Yeah, I've been thinking about exactly this, and the plan is the same: wrap daml-lf-archive rather than reimplement the decoder. The drift-free property is the whole point, so a native Rust decoder is not part of the proposed scope.
For the JVM catch, most of it is absorbed by how the SDK is distributed. The pre-built canton-splice-* crates are plain Rust with no JVM at all, so the common case is just cargo add without running codegen directly. The JVM only comes in when someone generates bindings from their own DAR: inside dpm, it is already part of the expected toolchain; outside dpm, it is a build-time JDK/JVM requirement, never a runtime dependency.
So: wrap daml-lf-archive, keep codegen as a build-time step, and make the no-JVM prebuilt crates the default path. Appreciate you flagging it.

So in this proposed design is the only planned jvm dependency for wrapping the Daml LF decoder, but otherwise rust codegen would be operating off the protobuf?

Yeah, exactly right on the runtime side. Let me just draw out the LF nuance.

There are two separate lanes here. The runtime client (commands, transactions, streams over the Ledger API) is pure Rust off the gRPC protos via tonic/prost, with no JVM anywhere in that path. The only place daml-lf-archive shows up is codegen, and even there it's build-time only. So yes, that LF decoder wrap is the single JVM dependency in the design.

On the "otherwise off the protobuf" part: that's spot on for the Ledger API lane, and it's almost true for LF too, since the DALFs inside a DAR are protobuf-encoded as well, you can even decode the wire bytes with prost. The catch is that the wire decode is the easy part. The protobuf definitions don't actually enforce all the rules of the LF spec, and the real value of daml-lf-archive is that it abstracts over the LF version and applies that spec correctly. That semantic layer is what moves with every LF release, so decoding the raw proto ourselves would just sign us up for the exact drift the wrap is there to avoid. It's also the same library that hands us the full dependency closure (a DAR of main plus its dependencies), which codegen needs anyway.

So it stays the full daml-lf-archive rather than a hand-rolled parser.

@monsieurleberre

Copy link
Copy Markdown
Contributor

Nice to see a Rust SDK in the mix 👌

...

So it stays the full daml-lf-archive rather than a hand-rolled parser.

Here is a humble contribution (I am not a Scala specialist at all), this is the JVM part in use on the C# codegen, I believe it might be reusable for your Rust SDK.

https://github.com/peacefulstudio/daml-codegen-csharp/tree/main/jvm-helper

I am more than happy to get this reviewed and improved, to move to a separate repo, to relocate to the canton foundation GH org if anyone feels that it is more appropriate, etc...

@noah-dfns

Copy link
Copy Markdown

Very nice to see a rust-sdk being developed. We do like to use rust especially around core cryptographic functions at DFNS!

@noah-dfns one of the things we're currently struggling with in the Tech & Ops committee of the foundation is finding the right people who (1) have the right technical skills (2) intend to use the work output of the grant, and (3) are willing to commit the time to be design partners for the grant. The problem is that it's easy for us to approve a grant, but once we get to evaluating the milestones' acceptance criteria, we need companies that are motivated to use the work product and assess whether the acceptance criteria have been met to their satisfaction.

So the question is - do you think DFNS can commit to:

  1. Being a design partner - continuously reviewing the progress from early design all the way through production use of the Rust SDK
  2. Reviewing ACs for all milestones and advising committee members whether they've been met to your satisfaction
  3. Using the Rust SDK in production, assuming all of your feedback is incorporated

This is a soft commitment with no enforcement mechanism, but we really need a few potential users to drive the feedback loops alongside the people developing the feature. It's also a good filtering mechanism for the voting committee to know how robust the demand is.

@shaul-da we would be willing to participate.

@srikanth-bitdynamics

Copy link
Copy Markdown
Contributor

@shaul-da We are also happy to review and be a design partner

@tomastauber-da tomastauber-da left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

could/would Admin API be also covered, e.g. for manually constructing topology transactions?


#### Daml-LF → Rust type mapping

The mapping is documented in full and covers: `Int64` → `i64`; `Numeric n` → a fixed-precision decimal type (`rust_decimal`/`bigdecimal`) preserving scale; `Text` → `String`; `Bool` → `bool`; `Party`/`ContractId a` → newtypes; `Time`/`Date` → typed wrappers; records → structs; variants → enums; enums → enums; `List a` → `Vec<T>`; `Optional a` → `Option<T>`; `TextMap`/`GenMap` → typed maps. Edge cases (high-precision `Numeric`, nested generics) are specified in the docs so behavior is predictable.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Time/Date could potentially use chrono

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

or something similar

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good call. Plan is to back those with chrono: Time to chrono::DateTime and Date to chrono::NaiveDate, with time available behind a feature flag.

- Package dependencies are generated transitively: `daml-lf-archive` returns the main package plus its dependency closure, so a DAR that references types from other packages produces bindings for the whole graph rather than failing on missing references.
- Contract keys are generated as typed key types, so consumers can exercise choices by key and prefetch keys for accelerated execution, per the Ledger Client Standard.
- Generated types carry package id and version plus the PackageMap, so consumers resolve the correct template version under Smart Contract Upgrade. A version bump regenerates the bindings rather than breaking silently.
- Generated objects expose both JSON and gRPC (proto) codecs, matching the serialization the Ledger API's JSON and gRPC endpoints require.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I assume the JSON code would be via serde impls, but maybe worth explicitly mentioning it

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Yep, exactly. JSON is via serde (derived Serialize/Deserialize) matching the Daml-LF JSON encoding, and gRPC via prost. I'll make that explicit in the mapping.

Comment on lines +29 to +39
**Full delivery of this proposal will result in:**

- An idiomatic, async Rust client for the Canton Ledger API (gRPC and JSON transports), published on crates.io, with TLS (mutual and server-side), error classification (retriable vs non-retriable), retry, command de-duplication and recovery, resilient/resumable streams, and OpenTelemetry tracing and metrics.
- A DAR → Rust code generator that produces type-safe bindings for templates, choices, interfaces, and contract keys, built on `daml-lf-archive` and aware of Smart Contract Upgrade (SCU).
- Built-in token-standard support for both CIP-56 (V1) and Token Standard V2 (CIP-0112): holdings, transfer instruction, allocations, choice-context, disclosed contracts, plus the V2 additions (account structures, allocation/executor settlement, committed allocations and iterated settlement, and the V2 `EventLog`/transfer-event parsing).
- JWT/OIDC authentication with presets for common identity providers.
- Pre-built `canton-splice-*` crates for the protocol DARs (amulet, wallet, token-standard V1 and V2, validator-lifecycle, dso-governance), refreshed on each Canton/Splice release.
- Integration with `dpm` as a `dpm codegen-rust` component.
- A reference application, a conformance test suite verified against the Ledger Client Standard, an independent security review, and documentation.

**Out of scope for v1 (roadmapped, not excluded):**

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

It'd be worth mentioning the compiler / platform targets that are in scope and how.
For example, would WASM be covered and how? (e.g. just JSON Ledger API or gRPC via grpc-web as well?)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I've added a targets section to the proposal.

The v1 supported and CI-tested matrix is native Tier-1 Rust: Linux, macOS and Windows on both x86_64 and aarch64, plus x86_64 musl for static container builds. That's where the SDK's audience runs (indexers, validators, oracle relays, settlement services), and there it's the full surface, gRPC over HTTP/2 via tonic plus JSON.

On WASM: the generated bindings are plain Rust (serde/prost) with no native deps, so they compile to wasm32 and are usable from WASM as-is, and that part is in v1. Browser transport is the piece I've put on the roadmap rather than v1. In the browser, unary JSON Ledger API calls work over fetch (reqwest's wasm backend), JSON streaming goes over WebSocket (web-sys), and gRPC needs grpc-web through a proxy (e.g. tonic-web-wasm-client) since native HTTP/2 isn't available there. That path mainly serves the front-end / dApp cohort, which is really the TypeScript SDK's lane, and it pulls in a grpc-web proxy plus a separate browser test matrix. So I've kept it demand-gated rather than claim it for v1 and keep the tested matrix honest.


#### Key dependencies

`tonic`/`prost` (gRPC), `tokio`, `reqwest` (JSON transport), `daml-lf-archive` (LF decoding, via a thin JVM helper), `rust_decimal`/`bigdecimal`, `serde`, OpenTelemetry.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I assume the PQS client would use sqlx or something like that?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Yep, sqlx is the plan: async, connection pooling, and safe bind-parameter binding.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I'll add it to the dependency list

`canton-codegen` reads Daml-LF via `daml-lf-archive`, walks the package, and emits idiomatic Rust:

- Templates become structs with typed fields; choices become typed exercise builders.
- Interfaces and views are generated as traits/types.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

What would be the use case of having interfaces generated as traits? They are similar concepts, but from the Ledger client perspective in Rust, one won't likely implement or use the Daml interfaces as traits... one will usually just exercise some contract choices via an interface they implement, so it's primarily about input and output data of that invocation ?

@dmitryk-cf dmitryk-cf Jun 26, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Agreed, interfaces are really data-first: the typed view and the choice arg/result of the invocation, plus a toInterface contract-id conversion on templates that implement them. I'll update the proposal to make that explicit.

@dmitryk-cf

Copy link
Copy Markdown
Author

Nice to see a Rust SDK in the mix 👌

...

So it stays the full daml-lf-archive rather than a hand-rolled parser.

Here is a humble contribution (I am not a Scala specialist at all), this is the JVM part in use on the C# codegen, I believe it might be reusable for your Rust SDK.

https://github.com/peacefulstudio/daml-codegen-csharp/tree/main/jvm-helper

I am more than happy to get this reviewed and improved, to move to a separate repo, to relocate to the canton foundation GH org if anyone feels that it is more appropriate, etc...

This is great, thank you. Reusing it makes a lot of sense and I'm fully on board with the shared setup you described. Happy to help move it along.

…t, target platforms, chrono Time/Date, serde/prost codecs, sqlx PQS, data-first interfaces
@dmitryk-cf

Copy link
Copy Markdown
Author

could/would Admin API be also covered, e.g. for manually constructing topology transactions?

I've split the Admin API: party allocation/management and topology read (party-to-participant mappings, namespace delegations, vetted packages) are in v1. The part you mention — manually constructing/signing/submitting topology transactions — is on the roadmap for now.

If there's demand for it, it can be added to v1. I've structured it as a named module specifically so it can move forward on request. It's also additive rather than a separate foundation: topology transactions already surface on the Ledger API update stream (which v1 reads), and external-party topology signing reuses the same hash-signing machinery v1 builds for interactive submission.

@YashBit

YashBit commented Jun 28, 2026

Copy link
Copy Markdown

Strong proposal, I also look forward to the creation of this SDK as I love coding in Rust. Rust also brings in value in the following ways due to the construction of the language:

  • Ownership-enforced concurrency. The borrow checker rules out data races in the common Canton pattern: streaming events, computing derived state, and submitting commands at once. Rust will eliminate any inconsistencies at compile time.
  • Thin, auditable serialisation via prost. Codegen from Canton's .proto files keeps the serde/prost path narrow and reviewable.

Looking forward to the creation of this project.

@kevmuko

kevmuko commented Jun 28, 2026

Copy link
Copy Markdown

Hi, Kevin from K2F Labs here.

Our team has already built much of the listed items in this proposal internally, and would like to be involved in this proposal and upstreaming the work as a milestone with DA's help. Our Rust SDK toolchain featuring codegen, transcoding JSON/proto, and runtime components has been running in production for >6mo for our self-custodial wallet and DEX. The implementation was intentionally designed similar to the original Java source code for better maintainability and eventual upstreaming. There are some missing features from the proposal such as a PQS client, wallet signing in the client, and dpm integration that would be valuable to have as well.

Summary:

11 crates, layered from raw protobuf up to a high-level gRPC client. The dependency flow runs bottom-up: proto → LF model → transcoding → client/api → core.


  1. Protobuf / generated types (foundation)

ledger-proto — Generated gRPC stubs + types for the entire Canton Ledger API v2 and Canton admin surface (prost + tonic).

  • Ledger API v2: command, state, update, interactive submission, testing services
  • Canton admin v30: participant, sequencer, mediator, topology, pruning, crypto, health, time
  • BFT-ordering, synchronizer/connection protocols, plus vendored Google/gRPC well-known types

daml-lf-proto — Generated Rust types from the Daml-LF .proto schema (daml_lf, daml_lf_1, daml_lf_2). The low-level package format that DARs encode.


  1. Daml-LF model & transcoding

daml-lf — Daml-LF package model.

  • Dar / Archive / ArchivePayload — extract and read .dar (zip) archives
  • Parser — parse Daml-LF protobuf into an intermediate ast (typed IR)

daml-transcoder — Descriptor-driven transcoding between JSON ⇄ proto Value.

  • Two-phase: build-time Schema/Resolver resolve LF types into Descriptors; runtime Decoder/Encoder codecs convert formats through an intermediate Value
  • JsonCodec + ProtoCodec, composed by Transcoder for one-call format conversion
  • Resolver / PackageInfo for resolving user-defined types across packages

daml-error — Path-tracking error context.

  • ContextError wraps errors with a path of PathSegments pointing at where in a nested structure decoding failed; IntoContext / ResultContext extension traits build that path as errors bubble up. Shared by the transcoder and runtime.

  1. Daml runtime & codegen

daml — Runtime library that generated Daml code depends on.

  • Traits: Template, Interface, Record, Variant, Enum, Choice(Descriptor/Decoder), Contract(Descriptor/Decoder)
  • Codecs: ValueCodec (proto), JsonCodec (JSON), with builder/extractor helpers + NumericScale
  • Events: Created, Exercised, EventDecoder, EventFilter, Transaction
  • Commands: Update, CreateAnd, ByKey, ToInterface
  • Primitives (types): Party, Text, ContractId, etc. (Daml newtypes)
  • A prelude module (generated code emits use ::daml::prelude::*) and a proto re-expo

daml-derive — Proc-macro crate. #[derive(Event)] generates EventDecoder + EventFilter impls for user enums whose variants wrap Created / Exercised<Choice> (dispatches decoding by template ID).

daml-codegen — Daml-to-Rust code generator (library + main.rs CLI).

  • Reads .dar archives → parses LF → generates a complete typed Rust crate
  • Module-per-concept generators: template, choice, contract, interface, record, enum, variant, plus rust_type mapping and ident sanitization.

  1. Ledger API domain types & client

ledger-api — JSON-serializable Rust domain types mirroring Ledger API v2 protobuf messages, with bidirectional proto conversion via ProtoCodec. Modules: command, event, transaction, update, state, reassignment, identifier, crypto, interactive. (utoipa-annotated for OpenAPI.)

ledger-client — High-level gRPC Client (interceptor-generic) for the Ledger API. Supported features:

  • Commands: submit_and_wait, submit_and_wait_for_transaction (auto-fills empty command_id with a fresh UUID)
  • Contracts: get_contract_by_id, get_active_contract_by_id, list_active_contracts (+ _with_bounds), DisclosedContractSet
  • Transactions: list_transactions (typed, generic over EventDecoder); EventFormatBuilder for filters (party/template/interface IDs, interface views, created-event blobs, wildcard, verbose)
  • Interactive submission: prepare_submission, execute_submission, execute_submission_and_wait, estimate_traffic_cost
  • Party management: lookup, get_participant_id, external-party topology generation + allocation
  • Packages: list_package_ids, get_package, list_vetted_packages
  • Synchronizer: find_synchronizer_by_alias
  • Plus thin accessors to every raw Ledger/admin service client (command, completion, inspection, submission, event-query, package mgmt, pruning, user mgmt, version, etc.)

ledger-core — Higher-level infrastructure over the client (depends on sherpa-core).

  • transcoder::TranscoderProvider — self-refreshing: polls the package service in a background Worker, keeps a ready-to-use Transcoder
  • package service/resolver/metadata — resolves #package-name identifiers to concrete package_ids via cached vetting state

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

Projects

Status: Voting Live

Development

Successfully merging this pull request may close these issues.