C# client libraries for interacting with Canton participant nodes via the Ledger API (gRPC) and Participant Query Store (PQS).
| Package | 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.Auth |
Bearer-token providers (ITokenProvider) for the clients — OAuth2 client-credentials with automatic refresh, static token, or unauthenticated |
Packages are published to GitHub Packages.
# 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;
// Configure the client
var options = new LedgerClientOptions
{
GrpcAddress = "https://localhost:5001",
AccessToken = "your-jwt-token" // Optional
};
// Create clients
using var ledgerClient = new LedgerClient(options);
using var adminClient = new AdminClient(options);
// Allocate a party
var party = await adminClient.AllocatePartyAsync("alice");
Console.WriteLine($"Party: {party.Party}");
// Create a contract (using generated types from Daml.Codegen.CSharp)
var contractId = await ledgerClient.CreateAsync(
new MyTemplate("field1", "field2"),
actAs: party.Party);using Canton.Ledger.Pqs.Client;
// Configure the PQS client
var pqsOptions = new PqsClientOptions
{
ConnectionString = "Host=localhost;Database=pqs;Username=pqs;Password=pqs"
};
var pqsClient = new PqsClient(pqsOptions, logger);
// Query all active contracts of a template type
var agreements = await pqsClient.QueryAsync<Agreement>();
// Query with type-safe filters
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 contract = await pqsClient.FetchByIdAsync<Agreement>(contractId);
// Check if a contract exists
var exists = await pqsClient.ExistsAsync<Agreement>(contractId);Canton.Ledger.Auth ships as a dependency of Canton.Ledger.Grpc.Client. Register a token provider before adding the clients:
using Canton.Ledger.Auth;
// 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.Auth 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
- 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 startup
These packages integrate seamlessly with Daml.Codegen.CSharp:
// Generate C# from your Daml contracts
// daml-codegen-csharp ./my-contracts.dar -o ./Generated
// Use generated types with the ledger client
var asset = new Asset(owner: party.Party, name: "My Asset", value: 100m);
var contractId = await ledgerClient.CreateAsync(asset, actAs: party.Party);
// Exercise choices
var command = ExerciseCommand.For(contractId, Asset.Transfer.Create("Bob::..."));
await ledgerClient.ExerciseAsync(command, actAs: party.Party);
// Query the same contracts via PQS
var assets = await pqsClient.QueryAsync<Asset>(
Filter.Field<Asset>(a => a.Owner, party.Party));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.1.x | 3.4.x |
# 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