Canton.Ledger.Testing 0.6.0

Prefix Reserved
dotnet add package Canton.Ledger.Testing --version 0.6.0
                    
NuGet\Install-Package Canton.Ledger.Testing -Version 0.6.0
                    
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.Testing" Version="0.6.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Canton.Ledger.Testing" Version="0.6.0" />
                    
Directory.Packages.props
<PackageReference Include="Canton.Ledger.Testing" />
                    
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.Testing --version 0.6.0
                    
#r "nuget: Canton.Ledger.Testing, 0.6.0"
                    
#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.Testing@0.6.0
                    
#: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.Testing&version=0.6.0
                    
Install as a Cake Addin
#tool nuget:?package=Canton.Ledger.Testing&version=0.6.0
                    
Install as a Cake Tool

Canton.Ledger.Testing

In-memory test doubles and event/result builders for unit-testing business logic against the Canton Ledger API client surfaces without a live participant and without a mocking framework. Part of the Canton .NET SDK.

FakeLedgerClient implements the neutral Canton participant surface Canton.Ledger.Abstractions.ICantonLedgerClient (and therefore the transport-neutral Daml.Ledger.Abstractions.ILedgerClient it extends). FakeAdminClient, FakeTokenProvider and FakePqsClient implement the equally neutral Canton.Ledger.Abstractions.IAdminClient, Canton.Ledger.Abstractions.ITokenProvider and Canton.Ledger.Abstractions.IPqsClient. Every fake therefore stands in for a contract declared in one neutral package, and this package depends only on Canton.Ledger.Abstractions/Daml.Ledger.Abstractions/Daml.Runtime — no transport, no Canton.Ledger.Kernel, no PostgreSQL driver.

Installation

dotnet add package Canton.Ledger.Testing --version 0.6.0

Every fake replays canned data staged ahead of time — none of them is a semantic ledger/PQS simulator (no contract-key uniqueness or consuming-choice archival). FakePqsClient does evaluate the PqsFilter it is given against the staged contracts, with the semantics of the SQL a live PQS runs. The one exception is FakeLedgerClient's ledger end: it starts at the offset staged through WithLedgerEnd and advances by one offset per committed write, so a test can read the end, write, read it again, and get a bounded (fromOffset, toOffset] window that actually contains the write. The Canton participant surface (ICantonLedgerClient — fire-and-forget submission, the completion stream, synchronizer/version discovery, offset/id point reads, tree-shaped submission, traffic-cost estimation, interactive submission and package preference) lives directly on FakeLedgerClient: seed completion events, connected synchronizers, the Ledger API version, point-read transactions, transaction trees, the traffic-cost estimate, the prepared and executed interactive submissions and the preferred packages the same way, so tests swap Fake ⇆ REST ⇆ gRPC behind one interface. SubmitAsync/SubmitReassignmentAsync echo the submission's command id (minting one when omitted).

Key types

Type Purpose
FakeLedgerClient Configurable in-memory ICantonLedgerClient (and thus ILedgerClient). Build it with the fluent builder from FakeLedgerClient.Create(). Any member, Daml type, or Canton read you did not stage throws a descriptive NotSupportedException.
FakeLedgerClientBuilder WithLedgerEnd (the ledger end before any write — stage an event for a bounded window opened around the nth write at that offset plus n), WithActiveContracts<T> (the staged snapshot must end on a single terminal Checkpoint or StreamError, as a participant's does — WithMalformedActiveContracts<T> stages one that deliberately does not), WithContractEvents<T>, WithLedgerEffects<T>, WithExerciseResult<TResult>, WithCreateResult<TTemplate>, WithSubmissionOutcome, WithTransactionTree, WithReassignmentResult<T>, WithCompletionEvents, WithConnectedSynchronizers, WithLedgerApiVersion, WithTrafficCostEstimate (staging null replays a participant that served no estimation), WithPreparedSubmission, WithExecutedSubmission, WithExecutedTransaction, WithPreferredPackages, WithPackagePreference (staging null replays no satisfying package), WithUpdateByOffset, WithUpdateById, WithUpdateTreeByOffset, WithContract<T>, WithContractLifecycle<T>, WithDisclosure<T> (the answer GetDisclosureAsync<T> returns for a contract id typed as a template or an interface; an id with no staged disclosure reads as null), WithActiveContractsPages<T>, WithUpdatesPages, WithPrunedOffsets, the interface counterparts WithActiveInterfaceContracts<TInterface, TView>, WithInterfaceEvents<TInterface, TView> and WithInterfaceLedgerEffects<TInterface, TView>, WithThrowingCallFailure (stage a LedgerOperationException that every throwing member raises from its task, as that same instance; Try* members and streams keep returning what you staged for them. The fake raises the instance as staged and does not derive its CommitState from the member called, so build it with the LedgerOperationException(message, status, commitState, category, errorId, metadata, innerException) constructor to carry a Status and the CommitState suited to the members under test: a failed read is NotCommitted), then Build().
LedgerEvents Factories for AcsSnapshotEntry<T> variants, all constrained where T : ITemplate, IDamlRecord<T>: Created (typed T payload plus a ContractKey? key), Checkpoint, StreamError (an optional DamlErrorCategory?, string? errorId and source Exception? after the TransportStatus and message), Unclassified (a nullable LedgerOffset? and an UnclassifiedKind).
ContractEvents Factories for ContractStreamEvent<T> variants, all constrained where T : ITemplate, IDamlRecord<T>: Created and Assigned (typed T payload plus a ContractKey? key), Archived, Unassigned, Exercised, Checkpoint, StreamError (an optional DamlErrorCategory?, string? errorId and source Exception? after the TransportStatus and message), Unclassified (an UnclassifiedKind).
LedgerOutcomes Factories for ExerciseOutcome<T> variants: One, None, Many, DamlError, InfraError (an optional DamlErrorCategory? before the source Exception?), CommittedUndecodable (the command committed but its response could not be decoded; carries the update id when one was read).
LedgerResults Factories for TransactionResult, SubmitAndWaitResult, Contract<T>.
FakeAdminClient Configurable in-memory IAdminClient. Build it with FakeAdminClient.Create(). Query-style members you did not stage throw NotSupportedException; the void command members (GrantUserRightsAsync, RevokeUserRightsAsync, UploadDarAsync, ValidateDarAsync) always succeed.
FakeAdminClientBuilder WithParticipantId, WithAllocatedParty, WithParties, WithUser, WithUsers, WithUserRights, WithKnownPackages, WithPackage, WithVettedPackages, WithExternalPartyTopology, WithAllocatedExternalParty, then Build().
FakePqsClient Configurable in-memory IPqsClient. Build it with FakePqsClient.Create(). Query results are staged per Daml type; an unstaged type throws NotSupportedException. Filtered queries return only the staged contracts the filter matches, and QueryOneAsync returns null when none do. Paged queries order the staged contracts by contract id (ordinal comparison) before slicing, as PqsClient does, whatever order you staged them in; stage hex-like ids when a test depends on which contracts land on which page. Unpaged queries return contracts in staging order.
FakePqsClientBuilder WithQueryResults<T>, WithInterfaceQueryResults<TInterface, TView>, then Build().
FakeTokenProvider In-memory ITokenProvider. FakeTokenProvider.WithToken(token) for the happy path, FakeTokenProvider.WithFailure(exception) to exercise auth-failure paths. No builder — both factories fully configure the fake.

Usage

Stage the active-contract snapshot your business logic will read, keyed by Daml type, then drive it through the fake:

using Canton.Ledger.Testing;
using Daml.Ledger.Abstractions;
using Daml.Runtime;
using Daml.Runtime.Contracts;
using Daml.Runtime.Data;

var owner = new Party("bob");
var asset = new DemoAsset(new Party("issuer"), owner, "GOLD", 42m);

ILedgerClient client = FakeLedgerClient.Create()
    .WithActiveContracts(
        LedgerEvents.Created(
            new ContractId<DemoAsset>("cid1"),
            asset,
            key: null,
            LedgerOffset.At(1),
            (SynchronizerId)"sync1",
            [owner]),
        LedgerEvents.Checkpoint<DemoAsset>(LedgerOffset.At(2)))
    .Build();

// SubscribeActiveAsync<DemoAsset> now replays the staged entries:
await foreach (var entry in client.SubscribeActiveAsync<DemoAsset>(owner))
{
    // ... exercise the code under test ...
}

// A member you never staged fails loudly instead of returning empty/null:
// client.TryExerciseAsync<string>(...)  ->  NotSupportedException("...WithExerciseResult<String>...")

Every unconfigured member and every unstaged Daml type throws a NotSupportedException whose message names the builder call needed to stage it, so a test never silently exercises unconfigured behaviour.

Seed the completion stream to drive fire-and-forget submission logic (SubmitAsync then observe the completion), using the neutral CommandAccepted/CommandRejected/Checkpoint events:

using Canton.Ledger.Abstractions;
using Canton.Ledger.Testing;
using Daml.Runtime;
using Daml.Runtime.Commands;
using Daml.Runtime.Data;

var owner = new Party("bob");
var completion = new Completion(
    new CommandId("cmd-1"), Offset: LedgerOffset.At(1), ActAs: [owner],
    new SynchronizerTime("sync1", DateTimeOffset.UtcNow),
    SubmissionId: null, UserId: null, DeduplicationPeriod: null,
    PaidTrafficCost: 0, TraceContext: null);

ICantonLedgerClient client = FakeLedgerClient.Create()
    .WithCompletionEvents(
        new CompletionStreamEvent.Checkpoint(LedgerOffset.Begin),
        new CompletionStreamEvent.CommandAccepted(completion, "update-1"))
    .Build();

await foreach (var completionEvent in client.CompletionStreamAsync(owner))
{
    // ... assert your code correlates the accepted completion by command id ...
}

Seed the submit-and-wait write path — the command shape every generated choice helper submits through — with the ExerciseOutcome<TransactionResult> your code under test should observe. TrySubmitAndWaitForTransactionAsync (both overloads) replies with the staged outcome for every submission; stage a success or a failure to drive either branch:

using Canton.Ledger.Testing;
using Daml.Ledger.Abstractions;
using Daml.Runtime;
using Daml.Runtime.Commands;
using Daml.Runtime.Contracts;
using Daml.Runtime.Data;
using Daml.Runtime.Outcomes;

ILedgerClient happy = FakeLedgerClient.Create()
    .WithSubmissionOutcome(LedgerOutcomes.One(LedgerResults.Transaction(
        "update-1",
        LedgerOffset.At(5),
        [
            new CreatedContract(
                "event-1",
                "cid1",
                DemoAsset.TemplateId,
                DamlRecord.Create(),
                WitnessParties: [],
                Signatories: [],
                Observers: []),
        ],
        archivedContractIds: [],
        (CommandId)"cmd-1")))
    .Build();

ILedgerClient failing = FakeLedgerClient.Create()
    .WithSubmissionOutcome(LedgerOutcomes.DamlError<TransactionResult>(
        DamlErrorCategory.InvalidGivenCurrentSystemStateOther,
        "UNHANDLED_EXCEPTION", "assertion failed", new Dictionary<string, string>()))
    .Build();

Stage an admin client the same way:

using Canton.Ledger.Abstractions;
using Canton.Ledger.Testing;
using Daml.Runtime.Data;

IAdminClient admin = FakeAdminClient.Create()
    .WithParticipantId("participant1")
    .WithUser(new UserDetails("alice", new Party("alice::1220")))
    .Build();

var participantId = await admin.GetParticipantIdAsync();
var alice = await admin.GetUserAsync("alice"); // staged UserDetails
var bob = await admin.GetUserAsync("bob");     // null — never staged, not an error

Stage a PQS client's query results per Daml type:

using Canton.Ledger.Abstractions;
using Canton.Ledger.Testing;
using Daml.Runtime.Contracts;

IPqsClient pqs = FakePqsClient.Create()
    .WithQueryResults(new Contract<Holding>(new ContractId<Holding>("cid1"), holding))
    .Build();

var holdings = await pqs.QueryAsync<Holding>();

And a token provider for either the happy path or an auth-failure path:

using Canton.Ledger.Abstractions;
using Canton.Ledger.Testing;

ITokenProvider ok = FakeTokenProvider.WithToken("test-bearer-token");
ITokenProvider failing = FakeTokenProvider.WithFailure(new InvalidOperationException("token endpoint unreachable"));

Stage a failed throwing call to test your failure handling. Both transports report a failed call as a LedgerOperationException, so one staged failure covers either; build it with the constructor that takes a TransportStatus and a CommitState when the test branches on them:

using Canton.Ledger.Testing;
using Daml.Ledger.Abstractions;
using Daml.Runtime.Outcomes;

var noAnswer = new LedgerOperationException(
    "read got no answer",
    new TransportStatus.NoResponse(),
    CommitState.NotCommitted,
    DamlErrorCategory.TransientServerFailure,
    errorId: null,
    metadata: null,
    innerException: new TimeoutException("no response"));

ILedgerClient failing = FakeLedgerClient.Create()
    .WithThrowingCallFailure(noAnswer)
    .Build();

var act = () => failing.GetLedgerEndAsync(); // throws noAnswer from its task

Zero mocking-framework dependency

None of the fakes in this package pull in Moq / NSubstitute, so it composes with any (or no) mocking framework in your own test project. Every fake — FakeLedgerClient, FakeAdminClient, FakeTokenProvider and FakePqsClient — depends only on Canton.Ledger.Abstractions, Daml.Ledger.Abstractions, and Daml.Runtime.

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

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.6.0 75 10/7/2026
0.6.0-preview.4 98 10/7/2026
0.6.0-preview.3 165 10/1/2026
0.6.0-preview.2 400 9/30/2026
0.6.0-preview.1 197 9/28/2026
0.5.0-preview.3 208 9/24/2026
0.5.0-preview.2 125 9/22/2026
0.5.0-preview.1 206 9/8/2026
0.4.1-preview.1 106 8/29/2026