C# client libraries for interacting with Canton participant nodes via the Ledger API (gRPC) and Participant Query Store (PQS).
All packages are versioned in lockstep and published to nuget.org.
| Package | Version | Description |
|---|---|---|
Canton.Ledger.Grpc |
Generated gRPC stubs from Canton Ledger API protos | |
Canton.Ledger.Grpc.Client |
High-level client with Daml.Runtime integration |
|
Canton.Ledger.Pqs.Client |
Type-safe query client for the Participant Query Store (PQS) | |
Canton.Ledger.Kernel |
Transport-neutral client kernel the clients consume as peers — token providers (ITokenProvider, OAuth2 client-credentials/static/unauthenticated), the OpenTelemetry ActivitySource naming convention, and an opt-in Polly retry pipeline |
|
Canton.Ledger.OpenTelemetry |
OpenTelemetry SDK integration — registers the LedgerClient/AdminClient/PqsClient ActivitySources (plus Npgsql tracing) with a single AddCantonLedgerInstrumentation() call |
|
Daml.Runtime.Grpc |
Bridge between proto Value/Record and Daml.Runtime DamlValue/DamlRecord |
# gRPC Ledger API client
dotnet add package Canton.Ledger.Grpc.Client
# PQS query client
dotnet add package Canton.Ledger.Pqs.Clientusing Canton.Ledger.Grpc.Client;
using Canton.Ledger.Kernel.Authentication;
using Daml.Runtime.Contracts;
using Daml.Runtime.Data;
using Daml.Runtime.Outcomes;
// Configure the client (auth is supplied via an ITokenProvider, not a token string)
var options = new LedgerClientOptions
{
GrpcAddress = "https://localhost:5001",
};
// ITokenProvider.None runs unauthenticated — for local dev with an open participant.
// For production, register a token provider (see Authentication below) or use the
// dependency-injection path, which injects the ITokenProvider for you.
using var ledgerClient = new LedgerClient(options, ITokenProvider.None);
using var adminClient = new AdminClient(options, ITokenProvider.None);
// Allocate a party
var party = await adminClient.AllocatePartyAsync("alice");
var submitter = new Party(party.Party);
// Create a contract from a generated Daml template type.
// TryCreateAsync returns ExerciseOutcome<ContractId<T>> — switch on it instead of catching.
var outcome = await ledgerClient.TryCreateAsync(
new MyTemplate("field1", "field2"),
submitter);
var contractId = outcome switch
{
ExerciseOutcome<ContractId<MyTemplate>>.One ok => ok.Result,
ExerciseOutcome<ContractId<MyTemplate>>.DamlError err => throw new InvalidOperationException(err.ErrorId),
_ => throw new InvalidOperationException(outcome.GetType().Name),
};using Canton.Ledger.Pqs.Client;
using Daml.Runtime.Contracts;
// Configure the PQS client (single-argument constructor)
var pqsOptions = new PqsClientOptions
{
ConnectionString = "Host=localhost;Database=pqs;Username=pqs;Password=pqs"
};
var pqsClient = new PqsClient(pqsOptions);
// Query all active contracts of a template type
var agreements = await pqsClient.QueryAsync<Agreement>();
// Query with type-safe filters — field names resolve from codegen [DamlField] metadata
var partyId = "party::alice";
var filtered = await pqsClient.QueryAsync<Agreement>(
Filter.Or(
Filter.Field<Agreement>(a => a.Initiator, partyId),
Filter.Field<Agreement>(a => a.Counterparty, partyId)));
// Fetch a single contract by ID
var contractId = new ContractId<Agreement>("...");
var contract = await pqsClient.FetchByIdAsync<Agreement>(contractId);
// Check if a contract exists
var exists = await pqsClient.ExistsAsync<Agreement>(contractId);Canton.Ledger.Kernel ships as a dependency of Canton.Ledger.Grpc.Client. Register a token provider before adding the clients:
using Canton.Ledger.Kernel.Authentication;
// OAuth2 client-credentials with automatic refresh and caching
services.AddCantonAuth(configuration.GetSection("Canton:Auth"));
// ...or a fixed token for short-lived processes
services.AddCantonStaticAuth("eyJ...");{
"Canton": {
"Auth": {
"Domain": "my-tenant.eu.auth0.com",
"ClientId": "my-client-id",
"ClientSecret": "my-client-secret",
"Audience": "https://canton.network/"
}
}
}When no ITokenProvider is registered, the clients run unauthenticated (ITokenProvider.None) and log a warning at construction. See the Canton.Ledger.Kernel README for the full options reference, including custom token endpoints (e.g. Keycloak).
- Create contracts from generated Daml template types
- Exercise choices on contracts
- Submit batched commands atomically
- Full async/await support
- Allocate and manage parties
- Create and manage users
- Grant and revoke user rights
- Query active contracts by template type
- Type-safe filters using C# expressions — field names derived from generated bindings
- Parameterized SQL queries — no SQL injection by construction
- Composable
Filter.Or/Filter.Andcombinators - OpenTelemetry tracing via
ActivitySource
Authentication: OAuth2 client-credentials flow with thread-safe TTL token caching and automatic refresh- Static token and unauthenticated modes behind a single
ITokenProviderabstraction IServiceCollectionintegration with options validation at startupTelemetry: the sharedActivitySourcenaming convention;Resilience: the opt-in Polly retry pipeline
These packages integrate seamlessly with Daml.Codegen.CSharp:
// Generate C# from your Daml contracts
// daml-codegen-csharp ./my-contracts.dar -o ./Generated
using Canton.Ledger.Grpc.Client;
using Canton.Ledger.Kernel.Authentication;
using Daml.Runtime.Commands;
using Daml.Runtime.Contracts;
using Daml.Runtime.Data;
using Daml.Runtime.Outcomes;
var options = new LedgerClientOptions { GrpcAddress = "https://localhost:5001" };
using var ledgerClient = new LedgerClient(options, ITokenProvider.None);
var owner = new Party("Alice::1234...");
// Create a contract from a generated template type
var asset = new Asset(owner, "My Asset", 100m);
var createOutcome = await ledgerClient.TryCreateAsync(asset, owner);
var contractId = createOutcome switch
{
ExerciseOutcome<ContractId<Asset>>.One ok => ok.Result,
_ => throw new InvalidOperationException(createOutcome.GetType().Name),
};
// Exercise a choice — ExerciseCommand.For takes the contract id, the choice name,
// and the encoded choice argument (a DamlValue, here a record via ToRecord()).
var command = ExerciseCommand.For(
contractId,
new ChoiceName("Transfer"),
new Asset.Transfer(NewOwner: "Bob::5678...").ToRecord());
var exerciseOutcome = await ledgerClient.TryExerciseAsync<ContractId<Asset>>(command, owner);
// Query the same contracts via PQS
var pqsClient = new PqsClient(new PqsClientOptions
{
ConnectionString = "Host=localhost;Database=pqs;Username=pqs;Password=pqs",
});
var assets = await pqsClient.QueryAsync<Asset>(
Filter.Field<Asset>(a => a.Owner, owner.Id));See the architecture overview for how the codegen pipeline, the Daml.Runtime library, and the Canton.Ledger.* client packages fit together.
This library targets Canton Ledger API v2. The proto files are automatically downloaded from Maven Central during build.
| Library Version | Canton Version |
|---|---|
| 0.4.x | 3.4.x |
| 0.2.x | 3.4.x |
| 0.1.x | 3.4.x |
From 0.4.x, the Canton.Ledger.* package minor tracks the Daml.Runtime / Daml.Ledger.Abstractions minor line — package 0.N.x embeds Daml.* 0.N.x — so the Daml runtime line is legible straight off the package version (the 0.3.x line is skipped to realign). Patch and -preview.N suffixes evolve independently. See ADR 0013.
# Clone the repository
git clone https://github.com/peacefulstudio/canton-ledger-api-csharp.git
cd canton-ledger-api-csharp
# Build
dotnet build
# Run tests
dotnet test
# Create NuGet packages
dotnet pack -c Release| Package | Description | Status |
|---|---|---|
Canton.Ledger.Rest |
REST/JSON API client | Planned |
Contributions are welcome from anyone in the Canton and Daml communities. See CONTRIBUTING.md for the dev setup, the testing requirements, the branch model, and the PR checklist.
Canton.Ledger.* is currently developed and maintained by Peaceful Studio OÜ
(Estonia). The project is licensed under Apache-2.0 with the explicit intent of
community ownership: if and when adoption warrants neutral governance, Peaceful
Studio commits to transferring this repository to a community-led organisation
under the same license terms. Contributions welcome from anywhere in the Canton
and Daml ecosystems; no CLA required.
Apache-2.0. Copyright 2026 Peaceful Studio OÜ. See LICENSE and NOTICE for details.
- daml-codegen-csharp - Generate C# from Daml contracts
- Canton - Distributed ledger platform