diff --git a/Casper.Network.SDK/CES/CESEvent.cs b/Casper.Network.SDK/CES/CESEvent.cs
index 5f359fa..6f406d0 100644
--- a/Casper.Network.SDK/CES/CESEvent.cs
+++ b/Casper.Network.SDK/CES/CESEvent.cs
@@ -40,6 +40,7 @@ public class CESEvent
/// The key in the global state that stores the event (dictionary item)
/// Set by ; null when parsed in isolation.
///
+ [JsonConverter(typeof(GlobalStateKey.GlobalStateKeyConverter))]
public GlobalStateKey TransformKey { get; init; }
///
diff --git a/Docs/Articles/CasperEventStandard.md b/Docs/Articles/CasperEventStandard.md
index 6d87478..393d258 100644
--- a/Docs/Articles/CasperEventStandard.md
+++ b/Docs/Articles/CasperEventStandard.md
@@ -244,3 +244,7 @@ if (buyEvent != null)
var json = JsonSerializer.Serialize(events);
Console.WriteLine(json);
```
+
+## Parsing CES events in the SSE stream
+
+See `Docs/Examples/CESParser/Program.cs` for a runnable sample that connects to the node SSE stream, listens to `TransactionProcessed` events, and pretty-prints parsed CES events.
diff --git a/Docs/Examples/CESParser/CESParser.csproj b/Docs/Examples/CESParser/CESParser.csproj
new file mode 100644
index 0000000..1dfa19e
--- /dev/null
+++ b/Docs/Examples/CESParser/CESParser.csproj
@@ -0,0 +1,12 @@
+
+
+
+ Exe
+ net8.0
+
+
+
+
+
+
+
diff --git a/Docs/Examples/CESParser/Program.cs b/Docs/Examples/CESParser/Program.cs
new file mode 100644
index 0000000..ba5f053
--- /dev/null
+++ b/Docs/Examples/CESParser/Program.cs
@@ -0,0 +1,106 @@
+using System;
+using System.Collections.Generic;
+using System.Text.Json;
+using System.Threading.Tasks;
+using Casper.Network.SDK;
+using Casper.Network.SDK.CES;
+using Casper.Network.SDK.SSE;
+
+namespace Casper.NET.SDK.Examples
+{
+ public static class CESParserExample
+ {
+ private static string nodeAddress = "http://127.0.0.1:11101/rpc";
+ private static string sseHost = "127.0.0.1";
+ private static int ssePort = 18101;
+
+ private static NetCasperClient casperSdk;
+ private static IReadOnlyList watchedSchemas;
+
+ public static void ListenEvents(int startFrom)
+ {
+ var sse = new ServerEventsClient(sseHost, ssePort);
+
+ sse.AddEventCallback(
+ EventType.TransactionProcessed,
+ "ces-parser-cb",
+ (SSEvent evt) =>
+ {
+ try
+ {
+ var transaction = evt.Parse();
+ var events = CESParser.GetEvents(
+ transaction.ExecutionResult.Effect,
+ watchedSchemas);
+
+ Console.WriteLine("TransactionProcessed: " + transaction.TransactionHash);
+
+ if (events.Count == 0)
+ return;
+
+ foreach (var parsedEvent in events)
+ {
+ Console.WriteLine("CES event:");
+ Console.WriteLine(PrettyPrintEvent(parsedEvent));
+ }
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e);
+ }
+ },
+ startFrom: startFrom);
+
+ sse.StartListening();
+
+ Console.WriteLine("Listening to TransactionProcessed events.");
+ Console.WriteLine("Press Enter to stop listening.");
+ Console.ReadLine();
+ Console.WriteLine("Terminating...");
+
+ sse.StopListening().Wait();
+
+ Console.WriteLine("Terminated");
+ }
+
+ public static async Task Main(string[] args)
+ {
+ var contractHash = args.Length > 0
+ ? args[0]
+ : "hash-1262d06e53125ea098187fb4d1d5b10a7afed48e5e5eef182ed992fc5b100349";
+
+ try
+ {
+ casperSdk = new NetCasperClient(nodeAddress);
+
+ Console.WriteLine("Loading CES schema for contract: " + contractHash);
+ var schema = await CESContractSchema.LoadAsync(casperSdk, contractHash);
+ watchedSchemas = new List { schema };
+
+ Console.WriteLine("Resolved contract hash : " + schema.ContractHash);
+ Console.WriteLine("Schema events URef : " + schema.EventsURef);
+ Console.WriteLine("Event types:");
+ foreach (var eventSchema in schema.Events)
+ {
+ Console.WriteLine("- " + eventSchema.Key);
+ }
+
+ ListenEvents(0);
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine(e);
+ }
+ }
+
+ private static string PrettyPrintEvent(CESEvent evt)
+ {
+ var options = new JsonSerializerOptions()
+ {
+ WriteIndented = true,
+ };
+
+ return JsonSerializer.Serialize(evt, options);
+ }
+ }
+}
\ No newline at end of file