Canton.Ledger.Kernel 0.7.1

Prefix Reserved
dotnet add package Canton.Ledger.Kernel --version 0.7.1
                    
NuGet\Install-Package Canton.Ledger.Kernel -Version 0.7.1
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Canton.Ledger.Kernel" Version="0.7.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Canton.Ledger.Kernel" Version="0.7.1" />
                    
Directory.Packages.props
<PackageReference Include="Canton.Ledger.Kernel" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Canton.Ledger.Kernel --version 0.7.1
                    
#r "nuget: Canton.Ledger.Kernel, 0.7.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Canton.Ledger.Kernel@0.7.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Canton.Ledger.Kernel&version=0.7.1
                    
Install as a Cake Addin
#tool nuget:?package=Canton.Ledger.Kernel&version=0.7.1
                    
Install as a Cake Tool

Canton.Ledger.Kernel

Part of the Canton .NET SDK. The transport-neutral client kernel for Canton participant nodes: the Authentication, Telemetry, Resilience, Streams, Trees, Results, and Wire modules — the token providers that implement Canton.Ledger.Abstractions.ITokenProvider, the OpenTelemetry ActivitySource naming convention, an opt-in Polly retry pipeline, and the decisions both transports must make identically once their own wire vocabulary is decoded: rebuilding a transaction's hierarchy from its node ids, folding an exercise outcome, and naming a participant body that could not be read. Both the gRPC client and the JSON client consume this package as peers — neither depends on the other. Authentication sits at the bottom of the kernel's namespace DAG (it depends on neither of the other modules), so it can later be extracted into its own package without a breaking change.

Installation

dotnet add package Canton.Ledger.Kernel --version 0.7.1

Key Types

Type Purpose
StaticTokenProvider Returns a fixed token string. Use for short-lived processes or testing. Implements Canton.Ledger.Abstractions.ITokenProvider
ClientCredentialsProvider OAuth2 client-credentials flow with thread-safe TTL cache (SemaphoreSlim + Volatile reads/writes)
ClientCredentialsOptions Config: Domain, ClientId, ClientSecret, Audience, TokenEndpoint, SafetyMargin, AllowInsecureTokenEndpoint, TokenAcquisitionTimeout, TokenFailureCooldown, Tls
Telemetry.LedgerActivitySourceNames The well-known source names of every Canton client (All plus one constant each), so a host can register the whole set without referencing a concrete client assembly
Telemetry.LedgerActivityTagNames The SDK-owned daml.* / canton.* / retry.* span attribute names the clients emit (All plus one constant each), so a dashboard query, sampling rule or redaction filter can name one without hardcoding the string
Resilience.RetryOptions Config for the opt-in retry pipeline, set via LedgerClientOptions.Retry / RestLedgerClientOptions.Retry. Enabled defaults to false
Resilience.RawCallReplay Allow() returns a scope inside which raw calls (the gRPC CreateCallInvoker() escape hatch, the REST raw Refit tier) are re-sent by the retry pipeline. Outside it they are sent once

Usage

Client-credentials (OAuth2) via DI

services.AddCantonAuth(configuration.GetSection("Canton:Auth"));
{
  "Canton": {
    "Auth": {
      "Domain": "dev-peaceful.eu.auth0.com",
      "ClientId": "my-client-id",
      "ClientSecret": "my-client-secret",
      "Audience": "https://daml.com/jwt/aud/participant/<participant-id>"
    }
  }
}

Audience, when set, must equal the participant's target-audience; by default Canton accepts https://daml.com/jwt/aud/participant/<participant-id>. Leave it unset only if your identity provider stamps that audience into the token itself.

Domain accepts either a bare hostname (e.g. dev-peaceful.eu.auth0.com) or an absolute https URL (e.g. https://auth.example.com, or https://auth.example.com/tenant-a for per-tenant subpaths). If Domain is a bare hostname, it is treated as https://{hostname}. If it is an absolute URL, that URL is used as the base. In both cases, /oauth/token is appended, preserving any existing path (https://auth.example.com/tenant-a → https://auth.example.com/tenant-a/oauth/token). Userinfo, query, and fragments are rejected. ClientCredentialsProvider caches tokens until expires_in - SafetyMargin (default 30s) and concurrent callers share a single HTTP request during refresh. After a failed fetch, calls fail at once with that failure, without reaching the identity provider, for a cooldown window of TokenFailureCooldown.InitialDelay (500 ms by default) that doubles per consecutive failure up to MaxDelay (30 s) and resets on success; set TokenFailureCooldown.Enabled to false to fetch on every call that finds no cached token. It never re-sends a ledger call or a token request; it only makes calls fail fast for the window after a failed token fetch.

Plaintext http endpoints (whether from Domain or TokenEndpoint) are rejected by default, because the token request POSTs the client secret in cleartext — anyone on the network path could read it. To accept the risk against a local endpoint (e.g. http://localhost:8080 during development), set AllowInsecureTokenEndpoint to true; the provider then logs a warning at construction.

To override the token endpoint (e.g., non-standard OAuth2 servers like Keycloak's /realms/{realm}/protocol/openid-connect/token):

{
  "Canton": {
    "Auth": {
      "TokenEndpoint": "https://custom.example.com/oauth/token",
      "ClientId": "...",
      "ClientSecret": "..."
    }
  }
}

Static token via DI

services.AddCantonStaticAuth("eyJ...");

Action-based configuration

services.AddCantonAuth(options =>
{
    options.Domain = "https://auth.example.com";
    options.ClientId = "my-client-id";
    options.ClientSecret = "my-client-secret";
    options.Audience = "https://daml.com/jwt/aud/participant/<participant-id>";
});

Registration precedence

Only unkeyed ITokenProvider registrations participate in the default selection; keyed providers remain independent. The unauthenticated gRPC and REST registration paths install the exact ITokenProvider.None singleton instance as their fallback. AddCantonAuth and AddCantonStaticAuth replace that exact unkeyed fallback with their explicit provider, so either may be called before or after those unauthenticated client registrations. Both preserve every pre-existing non-None unkeyed provider without resolving or constructing it.

Unauthenticated access

When no ITokenProvider is registered, AddLedgerClient/AddAdminClient register ITokenProvider.None as a default. Clients detect this and skip the Authorization header. Use this for local development with unauthenticated Canton nodes.

OpenTelemetry ActivitySource naming (Canton.Ledger.Kernel.Telemetry)

Every client's ActivitySource follows one naming convention, applied internally to Canton.Ledger.Kernel and its transports. LedgerActivitySourceNames and LedgerActivityTagNames expose the resulting well-known names, so a host registers listeners or writes dashboard queries without hardcoding strings:

using var listener = new ActivityListener
{
    ShouldListenTo = source => LedgerActivitySourceNames.All.Contains(source.Name)
};

BCL System.Diagnostics.Activity only — no OpenTelemetry SDK dependency.

Opt-in retry pipeline (Canton.Ledger.Kernel.Resilience)

RetryOptions configures the retry pipeline each transport builds for itself, via LedgerClientOptions.Retry (gRPC) or RestLedgerClientOptions.Retry (REST):

using Canton.Ledger.Kernel.Resilience;

services.AddLedgerClient(options =>
{
    options.Retry = new RetryOptions
    {
        Enabled = true,
        MaxRetryAttempts = 3,
        Delay = TimeSpan.FromMilliseconds(200),
    };
});

RetryOptions.Enabled defaults to false, in which case the transport applies no retry behavior — a genuine no-op. The pipeline is transport-neutral: it references no Grpc.Core or System.Net.Http types, and knows nothing about ledger command semantics. A retried command can double-submit unless the caller reuses a stable command_id/submission_id for ledger-side dedup — the pipeline itself confers no idempotency.

Raw calls are the exception to "retried when enabled": the gRPC CreateCallInvoker() escape hatch and the REST raw Refit tier are sent exactly once, because the SDK cannot know whether a call it did not build is idempotent. A caller that knows better makes the call inside using (RawCallReplay.Allow()) { ... }, awaiting it before the scope is disposed.

Internals

  • Wire.ExternalPartyIdentity — recomputes an external party's public-key fingerprint the way Canton does (SHA-256 over a four-byte hash purpose of 12 and the key bytes, as a 1220-prefixed multihash; an Ed25519 SubjectPublicKeyInfo contributes its raw 32-byte key) and checks a generated topology against it, so both transports fail closed on a fingerprint or party namespace that is not the caller's key, and on an allocated party that is not the one the allocation's onboarding transaction is for, which ExternalPartyTopologyVerifier.ExpectedPartyOf derives as the mapping's hint in the namespace of its signing key. Internal — visible to the transports via InternalsVisibleTo, not part of the package's public surface
  • Wire.PreparedSubmissionIntegrity and Wire.PreparedTransactionHasher — recompute the hash of a prepared transaction the way Canton does under hashing schemes V2 and V3 and refuse a participant's hash that differs, so both transports fail closed on a prepared transaction whose hash is not the one the signer would sign; any other scheme is refused too. Internal — visible to the transports via InternalsVisibleTo, not part of the package's public surface
  • Wire.ExternalPartyTopologyVerifier — decodes the topology transactions a participant generated for an external party and fails closed unless they are exactly the onboarding Canton 3.6 generates for the request: one PartyToParticipant add-replace at serial 1 for hint::fingerprint, the requested key alone at signing threshold 1, each requested participant with its requested permission plus exactly one generating participant, Canton's confirmation threshold (every confirming participant when the request leaves it at zero), and no field the vendored protos do not describe (each transaction must re-serialize to the bytes received). Wire.TopologyMultiHash recomputes the multi-hash the caller signs (SHA-256 with hash purpose 11 per transaction, then purpose 55 over the sorted, de-duplicated, length-prefixed hashes), so a multi-hash that covers anything else is refused too. A difference raises MalformedResponseException. Internal, like ExternalPartyIdentity
  • Topology/Proto/ — the Canton topology, crypto and versioned-message protos (com/digitalasset/canton/...), vendored unmodified from Canton tag v3.6.1 (commit 2fcea1a4826fd63b6a9ea554c13cb685ae8f2ee2, community/base/src/main/protobuf/), with scalapb/scalapb.proto from ScalaPB v0.11.14, the version Canton builds with. They compile to internal C# message types only (no gRPC services), so the package's public surface carries no topology API. Bump them together with the Canton line the verifier targets
  • Streams.StreamEventClassifier — the single implementation of the no-silent-drop stream-classification policy that both the gRPC and HTTP projectors delegate to: which decoded shape becomes which UnclassifiedKind, the missing-synchronizer rule, and the decode-failure fallback with its warning. Wire decoding stays per-transport; the classifier consumes a DecodedStreamEvent<TSynchronizerScope> and hands back the synchronizer scope it validated, so no projector can reach a SynchronizerId without passing the rule. Internal — visible to the transports via InternalsVisibleTo, not part of the package's public surface
  • Streams.IStreamArms<TEvent, TMarker, TPayload> with ContractArms<T> and InterfaceArms<TInterface, TView> — the union constructors for the two stream families, one static builder per arm over transport-neutral values, so each transport's projection core builds ContractStreamEvent<T> or InterfaceStreamEvent<TInterface, TView> directly. Internal, like the classifier
  • Streams.ContractSnapshotEntryArms<T> and InterfaceSnapshotEntryArms<TInterface, TView> — convert a projected stream event into the family's active-contract snapshot entry (Created with its disclosure, Unclassified, and an Unassigned downgraded to an Unclassified UnassignedEvent), shared by both transports. Internal
  • IHttpClientFactory named client "CantonAuth" — no using on the HttpClient (factory manages handler lifetime)
  • TimeProvider for testable time (pass FakeTimeProvider in tests)
  • Volatile.Read/Volatile.Write for cache fields — write order: token before expiry (matches read order)
  • Validates expires_in > 0 and access_token non-empty after deserialization
  • Token endpoint is resolved once at construction — unresolvable options (no TokenEndpoint, invalid Domain, plaintext http without AllowInsecureTokenEndpoint) throw InvalidOperationException when the provider is constructed, not at the first token request
  • Canton.Ledger.Abstractions — transport-neutral contract layer that declares ITokenProvider
  • Canton.Ledger.Grpc.Client — gRPC client that consumes ITokenProvider
  • Canton.Ledger.Pqs.Client — PQS query client
Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (4)

Showing the top 4 NuGet packages that depend on Canton.Ledger.Kernel:

Package Downloads
Canton.Ledger.Grpc.Client

High-level gRPC client for the Canton Ledger API with integration to Daml.Runtime types.

Canton.Ledger.Pqs.Client

Typed query client for the Canton Participant Query Store (PQS). Provides expression-based, SQL-injection-safe queries over active Daml contracts via PostgreSQL.

Canton.Ledger.Rest.Client

HTTP (JSON Ledger API) client for the Canton Ledger API — a full ILedgerClient / ICantonLedgerClient implementation over the transport-neutral Daml.Ledger.Abstractions interfaces.

Canton.Ledger.OpenTelemetry

Opt-in OpenTelemetry wiring for the Canton Ledger API clients. AddCantonLedgerInstrumentation() registers the gRPC, admin, REST, and PQS client ActivitySources plus Npgsql's own "Npgsql" ActivitySource. It reaches those names through Canton.Ledger.Kernel, so tracing one transport never drags in another's client assembly. The only assembly in this repo that depends on the OpenTelemetry SDK — the client libraries themselves emit only BCL System.Diagnostics.Activity.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.7.1 60 10/23/2026
0.7.0 231 10/14/2026
0.7.0-preview.1 421 10/13/2026
0.6.0 323 10/7/2026
0.6.0-preview.4 173 10/7/2026
0.6.0-preview.3 211 10/1/2026
0.6.0-preview.2 428 9/30/2026
0.6.0-preview.1 222 9/28/2026
0.5.0-preview.3 245 9/24/2026
0.5.0-preview.2 162 9/22/2026
0.5.0-preview.1 273 9/8/2026
0.4.1-preview.1 142 8/29/2026
0.4.0-preview.1 250 7/18/2026
0.2.0-preview.1 136 7/6/2026