CBOR serialization, transaction building, wallet management, Ouroboros mini-protocols, and a pure managed Plutus VM — everything you need to build on Cardano in .NET.
dotnet add package Chrysalis --prereleaseOr install individual packages:
dotnet add package Chrysalis.Codec --prerelease # CBOR serialization + source generation
dotnet add package Chrysalis.Network --prerelease # Ouroboros mini-protocols (N2C/N2N)
dotnet add package Chrysalis.Tx --prerelease # Transaction building + fee calculation
dotnet add package Chrysalis.Wallet --prerelease # Key management + address handling
dotnet add package Chrysalis.Plutus --prerelease # Pure managed UPLC/CEK machine| Package | Description |
|---|---|
| Chrysalis.Codec | Attribute-driven CBOR serialization with source-generated encoders/decoders |
| Chrysalis.Codec.CodeGen | Source generator for compile-time CBOR dispatch + CIP-0057 blueprint codegen |
| Chrysalis.Network | Ouroboros N2C/N2N mini-protocols with pipelined ChainSync + BlockFetch |
| Chrysalis.Tx | TransactionBuilder, MintBuilder, OutputBuilder, fee/collateral calculation |
| Chrysalis.Plutus | Pure C# UPLC interpreter — 999/999 conformance tests, no native dependencies |
| Chrysalis.Wallet | BIP-39 mnemonic, BIP-32 HD key derivation, Bech32 addresses |
| Chrysalis.Crypto | Ed25519 signatures, Blake2b hashing |
Drop an Aiken-compiled plutus.json into your project and get fully typed, serializable C# types at compile time.
<!-- .csproj -->
<ItemGroup>
<PackageReference Include="Chrysalis.Codec" Version="*-*" />
<PackageReference Include="Chrysalis.Codec.CodeGen" Version="*-*"
OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
<AdditionalFiles Include="plutus.json" />
</ItemGroup>The source generator reads CIP-0057 blueprint schemas and emits records with full CBOR serialization — types appear in IntelliSense immediately:
using MyProject.Blueprint;
// Construct from values — serializes byte-identical to Aiken's cbor.serialise
var credential = VerificationKeyCredential.Create(PlutusBoundedBytes.Create(keyHash));
var address = Address.Create(credential, None<ICredential>.Create());
var datum = SimpleDatum.Create(address, PlutusInt.Create(1_000_000),
PlutusBoundedBytes.Create(tag), PlutusTrue.Create());
byte[] cbor = CborSerializer.Serialize(datum);
// Deserialize back into the generated type
SimpleDatum decoded = CborSerializer.Deserialize<SimpleDatum>(cbor);Define types manually (without a blueprint)
[PlutusData(0)]
public partial record MyDatum(
[CborOrder(0)] byte[] Owner,
[CborOrder(1)] ulong Amount
) : CborRecord;
[CborSerializable]
[CborUnion]
public abstract partial record MyRedeemer : CborRecord;
[PlutusData(0)]
public partial record Spend([CborOrder(0)] long OutputIndex) : MyRedeemer;
[PlutusData(1)]
public partial record Cancel() : MyRedeemer;TransactionBuilder builder = TransactionBuilder.Create(pparams);
builder.AddInput("a1b2c3d4...#0");
builder.AddOutput("addr_test1qz...", Lovelace.Create(5_000_000))
.WithInlineDatum(myDatum)
.WithMinAda(pparams.AdaPerUTxOByte)
.Add();
// Multi-asset outputs
IValue value = Value.FromLovelace(2_000_000)
.WithToken(policyHex, assetNameHex, 100);
builder.AddOutput("addr_test1qz...", value).Add();
// Minting
builder.AddMint(policyHex, assetNameHex, 1_000);
// Sign and submit
ITransaction signed = tx.Sign(paymentKey, stakeKey);
string txHash = await provider.SubmitTransactionAsync(signed);Value arithmetic
IValue total = value1.Merge(value2);
IValue remaining = total.Subtract(spent);
ulong? qty = value.QuantityOf(policyHex, assetNameHex);
Dictionary<string, ulong> assets = value.ToAssetDictionary();Read inline datums
MyDatum? datum = output.InlineDatum<MyDatum>();
ReadOnlyMemory<byte>? rawCbor = output.InlineDatumRaw();Mnemonic mnemonic = Mnemonic.Generate(24);
PrivateKey accountKey = mnemonic.GetRootKey().DeriveCardanoAccountKey();
PrivateKey paymentKey = accountKey.DerivePaymentKey();
PublicKey stakingPub = accountKey.DeriveStakeKey().GetPublicKey();
Address address = Address.FromPublicKeys(
NetworkType.Testnet, AddressType.BasePayment,
paymentKey.GetPublicKey(), stakingPub);// N2C: local node via Unix socket
NodeClient node = await NodeClient.ConnectAsync("/ipc/node.socket");
await node.StartAsync(networkMagic);
var tip = await node.LocalStateQuery.GetTipAsync();
var utxos = await node.LocalStateQuery.GetUtxosByAddressAsync([addressBytes]);
await node.LocalTxSubmit.SubmitTxAsync(signedTxBytes);
// N2N: remote node via TCP with pipelined sync
PeerClient peer = await PeerClient.ConnectAsync("relay.cardano.org", 3001);
await peer.StartAsync(networkMagic);
await peer.ChainSync.FindIntersectionAsync([Point.Origin], ct);Pure managed UPLC interpreter — no Haskell, no Rust, no FFI. 999/999 conformance tests covering Plutus V1-V3, all 94 builtins, and BLS12-381 cryptographic primitives.
IReadOnlyList<EvaluationResult> results = ScriptContextBuilder.EvaluateTx(
body, witnessSet, utxos, SlotNetworkConfig.Preview);
foreach (var r in results)
Console.WriteLine($"[{r.RedeemerTag}:{r.Index}] mem={r.ExUnits.Mem} steps={r.ExUnits.Steps}");The transaction builder uses the VM automatically — no external evaluator needed.
Benchmarks against Pallas (Rust) and Gouroboros (Go) on Conway-era blocks.
N2N (TCP) — Pipelined ChainSync from origin:
| Headers Only | Full Blocks + Deser | |
|---|---|---|
| Chrysalis (.NET) | ~35,000 blk/s | ~9,500 blk/s |
| Gouroboros (Go) | ~15,000 blk/s | N/A |
| Pallas (Rust) | N/A | ~720 blk/s |
N2C (Unix Socket) — 10,000 blocks, sequential:
| With Deserialization | Network Only | |
|---|---|---|
| Chrysalis (.NET) | ~3,050 blk/s | ~3,080 blk/s |
| Pallas (Rust) | ~3,040 blk/s | ~3,010 blk/s |
| Gouroboros (Go) | ~2,600 blk/s | — |
N2C is bottlenecked by the node — all three converge around the same throughput. Chrysalis and Pallas are neck and neck.
How it's fast
- Batch burst pipelining — send N header requests, drain N responses, BlockFetch the batch
- Zero-copy deserialization —
ReadOnlyMemory<byte>throughout, no intermediate allocations - Source-generated CBOR dispatch — compile-time probe-based union resolution via PeekState/PeekTag
- System.IO.Pipelines — backpressure-aware async I/O with minimal buffer copies
AMD Ryzen 9 9900X3D, .NET 10. Full results in benchmarks/BENCHMARKS.md.
| Era | Serialization | Block Processing | Tx Building | Script Eval |
|---|---|---|---|---|
| Byron | ✅ | ✅ | — | — |
| Shelley | ✅ | ✅ | ✅ | — |
| Allegra | ✅ | ✅ | ✅ | — |
| Mary | ✅ | ✅ | ✅ | — |
| Alonzo | ✅ | ✅ | ✅ | ✅ |
| Babbage | ✅ | ✅ | ✅ | ✅ |
| Conway | ✅ | ✅ | ✅ | ✅ |
- Fork the repository
- Create a feature branch:
git checkout -b feature/my-feature - Commit your changes:
git commit -m 'feat: add my feature' - Push and open a Pull Request
MIT — see LICENSE.md.
Built by SAIB Inc for the Cardano community