Canton.Ledger.Rest.Client
0.4.1-preview.1
Prefix Reserved
dotnet add package Canton.Ledger.Rest.Client --version 0.4.1-preview.1
NuGet\Install-Package Canton.Ledger.Rest.Client -Version 0.4.1-preview.1
<PackageReference Include="Canton.Ledger.Rest.Client" Version="0.4.1-preview.1" />
<PackageVersion Include="Canton.Ledger.Rest.Client" Version="0.4.1-preview.1" />
<PackageReference Include="Canton.Ledger.Rest.Client" />
paket add Canton.Ledger.Rest.Client --version 0.4.1-preview.1
#r "nuget: Canton.Ledger.Rest.Client, 0.4.1-preview.1"
#:package Canton.Ledger.Rest.Client@0.4.1-preview.1
#addin nuget:?package=Canton.Ledger.Rest.Client&version=0.4.1-preview.1&prerelease
#tool nuget:?package=Canton.Ledger.Rest.Client&version=0.4.1-preview.1&prerelease
Canton.Ledger.Rest.Client
HTTP client for the Canton Ledger API over the JSON Ledger API (/v2/...). RestLedgerClient is a full implementation of the transport-neutral Daml.Ledger.Abstractions.ILedgerClient and the Canton participant surface Canton.Ledger.Abstractions.ICantonLedgerClient, so business logic written against those interfaces runs unchanged over HTTP or gRPC.
Key Types
| Type | Purpose |
|---|---|
RestLedgerClient |
ILedgerClient + ICantonLedgerClient over the JSON Ledger API — reads, writes, bounded streams, and the Canton-only submit / reassignment / discovery / point-read / transaction-tree / traffic-cost surface |
RestLedgerClientOptions |
Config: HttpAddress (required, e.g. http://localhost:7575), UserId (optional; the participant derives it from the caller's token when omitted), Retry (opt-in, disabled by default) |
HealthCheckBuilderExtensions.AddRestLedgerClient |
IHealthChecksBuilder extension probing the participant over HTTP via GET /v2/state/ledger-end |
LedgerResultTooLargeException |
Thrown when a bounded read exceeds the participant's http-list-max-elements-limit (HTTP 413) |
MalformedTransactionTreeException |
Thrown when the node ids on a transaction's events cannot describe a tree, so no hierarchy can be reconstructed from them. Declared in Canton.Ledger.Abstractions and shared with the gRPC transport, so one catch covers both |
The raw, per-service Refit interfaces live in the Canton.Ledger.Rest.Client.Raw namespace behind the CANTONREST001 experimental diagnostic; consume them only through AddRestLedgerRawApis when you need an endpoint the adapter does not surface.
Registration
// Config-based — binds RestLedgerClientOptions from the given section
services.AddRestLedgerClient(configuration.GetSection("Canton:Rest"));
// Action-based
services.AddRestLedgerClient(options => options.HttpAddress = "http://localhost:7575");
AddRestLedgerClient registers a single RestLedgerClient resolvable as both ILedgerClient and ICantonLedgerClient (backed by the same instance), so a consumer injects the full Canton surface from REST exactly as from gRPC. Authentication reuses the shared Canton.Ledger.Abstractions.ITokenProvider — register one (for example AddCantonStaticAuth(...) or client-credentials auth) and the client attaches the bearer token to every request.
Add AddRestLedgerRawApis(...) alongside it to also register the opt-in raw Refit surface.
Reads, writes, and streams
- Writes (
TryCreateAsync,TryExerciseAsync,SubmitAndWaitAsync,TrySubmitAndWaitForTransactionAsync) submit through/v2/commands/submit-and-wait[-for-transaction]. TheTry*methods return a structuredExerciseOutcome(aDamlErroron a structured participant error, anInfraErroron a transport failure or per-call timeout);SubmitAndWaitAsyncthrowsLedgerOperationException, matching the gRPC transport's throwing contract. - Transaction trees —
TrySubmitAndWaitForTransactionTreeAsyncandGetUpdateTreeByOffsetAsyncreturn a committed transaction with its parent/child hierarchy intact: which exercise caused which sub-creates and sub-exercises.TrySubmitAndWaitForTransactionTreeAsyncis anICantonLedgerClientmember, so a consumer reaches it through the injected interface;GetUpdateTreeByOffsetAsyncis a concrete member onRestLedgerClient, because its gRPC counterpart is internal. Both always ask the participant for the ledger-effects view, since hierarchy is only meaningful over creates and exercises. The participant reports that hierarchy as node ids on the ordinary event list — each exercise states the highest node id in the subtree it caused — so the tree is rebuilt from the same response the flat read decodes, not a second request. Node ids that cannot describe a tree fail loudly rather than yielding a silently wrong tree: anInfraErroroutcome on the submit path, and on the point read anInvalidOperationExceptioncarrying theMalformedTransactionTreeExceptionas itsInnerException— catch the base type there, not the derived one. Node-id gaps left by the participant's own party filtering are normal and tolerated, and an event whose parent exercise was filtered out attaches to the nearest enclosing exercise the parties can still see, or surfaces as a root when none remains. Project a tree back to the flattened shape withTransactionTreeExtensions.ToTransactionResult()rather than submitting twice — neither shape is a superset of the other on the wire. - Bounded reads —
SubscribeActiveAsyncis an ACS snapshot ending in a terminal checkpoint;SubscribeAsync/SubscribeLedgerEffectsAsyncare bounded offset-range reads over one blocking call. - Interface subscriptions carry the participant-computed view. Subscribing an interface marker projects the matching
interfaceViewsentry'sviewValueonto each row, not the implementing template'screateArgument; a view the participant could not compute surfaces asUnclassified(InterfaceViewUnavailable)rather than an empty payload.QueryActiveAsync<TInterface, TView>materializes that snapshot into typed view records, exactly as over gRPC. - Command completions —
CompletionStreamAsyncreadsPOST /v2/commands/completions, whose success body is a JSON array, and yieldsCommandAccepted/CommandRejected/Checkpointper entry. One call covers one participant-bounded window rather than an endless tail, so a caller follows the stream by reopening it from the last offset it observed;CompletionStreamLimitandCompletionStreamIdleTimeouton the options bound that window (limitandstream_idle_timeout_mson the wire), and leaving them unset defers to the participant. A non-success response ends the enumeration with a terminalStreamErrorcarrying the HTTP status code — the in-band fault contract the gRPC transport honours for this method — and a success body that will not decode (malformed JSON, or a completion carrying an unparseable offset or deduplication duration) ends it the same way, withStatusCode0since the transport reported no failure. A transport failure that never reached the participant still throws. - Traffic-cost estimation —
EstimateTrafficCostAsync(submission, timeout, cancellationToken)asks the participant what a submission would consume in synchronizer traffic before committing to it, overPOST /v2/interactive-submission/preparewith cost estimation requested. Nothing reaches the ledger — the participant interprets the commands, answers, and the prepared transaction is discarded — so the call costs about what a submission costs, but the caller's token needs only read rights for theactAsparties rather than act rights. The answer projects into the sharedCanton.Ledger.Abstractions.TrafficCostEstimate(EstimatedAt,ConfirmationRequestCost,ConfirmationResponseCost,TotalCost, all in bytes), the same record the gRPC client returns. A participant that sends no estimation — one with traffic control disabled, for instance — yieldsnullrather than a zeroed record; an estimation that is present and reports zero is a genuine zero-cost estimate. The method is anICantonLedgerClientmember, so a consumer prices a submission through the injected interface whichever transport is registered. Two per-transport differences: a rejected request throwsLedgerOperationExceptionwith the participant's category and error id, where gRPC throwsRpcException; and the cost does not reach a span, because this client's spans are emitted per HTTP request by the pipeline handler rather than per client method. - An open-ended live tail is not yet available over HTTP.
SubscribeAsync/SubscribeLedgerEffectsAsyncwithtoOffset: nullthrowNotSupportedException— that read lands over a future WebSocket transport. TheSupportsUnboundedStreamingcapability probe reportsfalseuntil then. - Errors are parsed, not passed through raw. A participant's non-success response on any call,
GetLedgerEndAsyncincluded, is decoded into the participant's category, error id and message before it reaches the caller — as aLedgerOperationExceptionon the throwing methods, or anExerciseOutcome.DamlError/InfraErroron theTry*methods.
Retry
Off by default. Opt in to have transient transport failures retried with exponential backoff and jitter, the same Canton.Ledger.Kernel pipeline the gRPC client uses:
services.AddRestLedgerClient(options =>
{
options.HttpAddress = "http://localhost:7575";
options.Retry = new RetryOptions { Enabled = true, MaxRetryAttempts = 3, Delay = TimeSpan.FromMilliseconds(200) };
});
The retry handler sits outermost in the HTTP pipeline, so every attempt resolves a fresh bearer token and emits its own client span, plus a RestLedgerClient.RetryAttempt span carrying retry.attempt and retry.delay_ms. So a retried attempt can replay the request body, the handler buffers request content in memory before the first attempt — enabling retry therefore costs one in-memory copy of each request body, which is worth weighing against a large DAR upload. Retried requests reuse the command_id fixed above the retry boundary, so ledger-side deduplication makes a resubmission idempotent — the pipeline itself confers no idempotency.
Two asymmetries against the gRPC pipeline are deliberate:
- Only exceptions are retried — a refused/reset/DNS-failed connection (
HttpRequestException) and a client-side request timeout, the HTTP analogues of gRPCUnavailable/DeadlineExceeded. A participant that answers with429,503, or a gateway5xxis a response, not an exception, and is surfaced to the caller unretried. - No duplicate-command recovery. The gRPC client maps a retried
DUPLICATE_COMMANDrejection back to success by point-reading the committed transaction from the rejection'scompletion_offset; the JSON API does not serve that metadata, so a first attempt that commits while its response is lost surfaces the resubmission'sDUPLICATE_COMMANDto the caller.
Health checks
services.AddHealthChecks().AddRestLedgerClient();
Probes the participant with GET /v2/state/ledger-end, which is not gated behind participant_admin, so a least-privilege deployment still reports healthy. The check resolves the concrete RestLedgerClient, so a host wiring both transports gets a check that probes the HTTP endpoint specifically rather than whichever transport won the ILedgerClient registration.
Tracing
The client emits an OpenTelemetry HTTP client span per request. Subscribe to it by name:
tracing.AddSource(RestLedgerClient.ActivitySourceName);
Canton.Ledger.OpenTelemetry's AddCantonLedgerInstrumentation() registers this source alongside every other Canton client source.
Related Packages
Canton.Ledger.Abstractions— the transport-neutral Canton participant contracts this client implementsCanton.Ledger.Grpc.Client— the gRPC client implementing the same surfaceCanton.Ledger.Testing— in-memory fakes for unit-testing against these contractsCanton.Ledger.Rest— the raw Refit surface this adapter is built on
| Product | Versions 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. |
-
net10.0
- Canton.Ledger.Abstractions (>= 0.4.1-preview.1)
- Canton.Ledger.Kernel (>= 0.4.1-preview.1)
- Canton.Ledger.Rest (>= 0.4.1-preview.1)
- Daml.Ledger.Abstractions (>= 0.4.1-preview.1)
- Daml.Runtime (>= 0.4.1-preview.1)
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.11)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.11)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 10.0.11)
- Microsoft.Extensions.Http (>= 10.0.11)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.11)
- Microsoft.Extensions.Options (>= 10.0.11)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.11)
- Microsoft.Extensions.Options.DataAnnotations (>= 10.0.11)
- Refit (>= 15.2.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.4.1-preview.1 | 49 | 8/29/2026 |